乐趣区

JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇

前言
前些天看了《阿丽塔》感叹酷炫特效的同时,不得不说这个片子灰常之热血!重新点燃了我粪斗的基情!!有那么几个瞬间仿佛自己回到了……

OK,下面进入正题在依德医生刚捡回阿丽塔的那一段,有木有发现医生家的设备都很有意思~比如那个人皮缝纫机,其灵活程度堪比织网 ing 的蜘蛛说到蜘蛛,就想起了西游记里的蜘蛛精。今年下半年……

……………………除了人皮缝纫机,当时还注意到他们屏幕的一个交互很有趣——

没错,今天的主角出场了就是用 JS 做一个类似的旋转缩放控件先来看一哈最终效果,铛铛:
貌似手感不错?可以点此抢先体验(此版本仅限 PC 端玩家)
正文
本篇首先开发一个 demo 试试水,暂时用鼠标代替手指
实现思路如下:

画一个彩色圆环
监听鼠标移动事件(保证只能作用在圆环上)
实时计算鼠标当前的角度(相对于圆心)
对比当前角度和上一次角度,确定每一帧的旋转方向和距离,根据变化的角度值旋转圆环
缩放操作外部 dom

1. 画圆环
这里使用大圆套小圆组成圆环:大圆设置一个渐变背景色,小圆为纯白色为啥子不只用一个圆,然后设置 border 属性捏?因为我不喜欢因为 border 不能设置渐变色当然还有另一个作用:阻止事件(详见下文)
万事俱备,开始装逼目标是把圆环用绝对定位放到左上角,而且内圆要适中大小所以分别设置圆心和大小圆的半径为:
var CENTER = {x: 150, y: 150}, BIG_RADIUS = 150, SMALL_RADIUS = 70
创建 div 表示两个圆
var bigCircle = document.createElement(‘div’)
var smallCircle = document.createElement(‘div’)
document.body.appendChild(bigCircle)
document.body.appendChild(smallCircle)
封装一个函数方便给大小圆添加样式参数:center 圆心、radius 半径、bg 背景、isMove 是否运动(使用 CSS3 的变化和旋转,每 0.16 秒运动一次,即 60 帧)
function setCircleClass(center, radius, bg, isMove) {
this.style.position = ‘absolute’
this.style.left = center.x – radius + ‘px’
this.style.top = center.y – radius + ‘px’
this.style.width = radius * 2 + ‘px’
this.style.height = radius * 2 + ‘px’
this.style.borderRadius = ‘50%’
this.style.zIndex = 66666
this.style.background = bg
isMove && (this.style.transition = ‘transform linear .016s’)
}
调用函数添加样式
setCircleClass.apply(bigCircle, [CENTER, BIG_RADIUS, ‘linear-gradient(skyblue, darkorange)’, true])
setCircleClass.apply(smallCircle, [CENTER, SMALL_RADIUS, ‘#FFF’, false])
颜色有点瓜皮:
2. 监听鼠标事件
监听大圆的 mousemove 事件,同时小圆阻止事件传播
bigCircle.addEventListener(‘mousemove’, main)
smallCircle.addEventListener(‘mousemove’, function(e) {
e.stopPropagation()
})
创建大圆的监听函数 main 这里设置鼠标左键按下时生效,顺便写一个打印语句
function main(e) {
if(e.buttons === 1) {
console.log(‘ 鼠标在圆环移动 ing’)
}
}

3. 实时计算当前角度
接下来就是重头戏了,想要让圆环跟随鼠标转动,首先想到的绝壁是斜率在监听事件触发时,不停计算鼠标位置和圆心两点连线的斜率,通过对比本次的斜率和上一次斜率,即可得出圆环转动的方向方向有了,还要计算圆环移动的速度,然而斜率的变化并不是线性的,因此很难通过斜率的变化值来计算速度……所以光有斜率是没办法解决问题的,那么有木有其他线性变化的东西呢……没错,就是角度了~!
记得 Math 对象有一些三角函数方法,速速去查
正在眼花缭乱之时,突然眼角一闪,一个黑衣人从天而降,定睛一看,正是传说中的 atan2 函数“骚年,你要找的人正是在下”
打量了一番,发现这哥们不仅长得帅,而且手中还拿了一个神器:计算角度函数
function calcAngleDegrees(x, y) {
return Math.atan2(y, x) * 180 / Math.PI
}
卧槽,简直是踏破铁鞋,赶紧来战:
function main(e) {
if(e.buttons === 1) {
var angle = calcAngleDegrees((e.clientX – CENTER.x), (CENTER.y – e.clientY))
console.log(‘ 角度:’ + angle)
}
}
如图可以看到,角度的变化是从 9 点钟方向的 180 度,顺时针递减 360 度,回到 9 点钟方向正符合我们后续的需求

“大师果然牛皮,不知您的一身好武功是如何修来的?”atan2 笑而不语,一转身便消失在了无尽的夜色中,只留下了无尽的疑问……
既然如此,作为一个热爱技术的搬砖工,我决定自己找出真相~
本小节终。
4. 根据角度计算方向和距离,旋转圆环
有了角度值,下面就的问题就引刃而解了
先创建一个函数用来旋转圆环,参数:deg 当前圆环的角度
function rotate(deg) {
this.style.webkitTransform = ‘rotate(‘ + deg + ‘deg)’
this.style.mozTransform = ‘rotate(‘ + deg + ‘deg)’
this.style.msTransform = ‘rotate(‘ + deg + ‘deg)’
this.style.oTransform = ‘rotate(‘ + deg + ‘deg)’
this.style.transform = ‘rotate(‘ + deg + ‘deg)’
}
创建变量:当前圆环 (大圆) 角度值 circleAngle、当前鼠标角度值 mouseAngle、上一次鼠标角度值 lastMouseAngle
var circleAngle = 0, mouseAngle, lastMouseAngle
此处需初始化 lastMouseAngle,这个操作看似简单,实则使用正确的姿势可以避免一系列 bug 这里研究出来比较好的方法就是在鼠标移入圆环和在圆环中按下鼠标的时候赋值,感性趣的童鞋可以自行研究一下
bigCircle.addEventListener(‘mouseenter’, init)
bigCircle.addEventListener(‘mousedown’, init)

function init(e) {
lastMouseAngle = calcAngleDegrees((e.clientX – CENTER.x), (CENTER.y – e.clientY))
}
初始化 lastMouseAngle 之后,mouseAngle – lastMouseAngle 即为角度的增量
增量正负决定方向:正数为逆时针,负数为顺时针增量大小决定距离:绝对值即是圆环旋转的角度
由于顺时针旋转时增量为负,且 CSS 里 transform 属性为顺时针旋转增加角度所以当前圆环的角度计算公式为:circleAngle -= (mouseAngle – lastMouseAngle)
改造 main 函数:
function main(e) {
if(e.buttons === 1) {
mouseAngle = calcAngleDegrees((e.clientX – CENTER.x), (CENTER.y – e.clientY))
var changeMouseAngle = mouseAngle – lastMouseAngle
circleAngle -= changeMouseAngle
console.log(‘ 当前角度:’ + circleAngle)
rotate.call(this, circleAngle)
lastMouseAngle = mouseAngle
}
}
可以看到已经有了雏形,愉快地进入下一步
5. 操作外部 dom
想要操作外部 dom,需要的是一个线性变化的值用脚趾头都能想到,当前最合适的无疑就是当前圆环角度 circleAngle 了然鹅仔细观察上一张图就会发现,当鼠标每次移动过 9 点钟方向时,圆环角度就会瞬间改变 360 度,回到初始值,并不能满足当前需求此处做一波改造,判断当角度的变化值 changeMouseAngle 超过一定度数的时候,不执行后面的操作考虑到单身 20 年用户的手速,暂时设置这个值为 300
var MAX_CHANGE_ANGLE = 300
function main(e) {

var changeMouseAngle = mouseAngle – lastMouseAngle
if(Math.abs(changeMouseAngle) > MAX_CHANGE_ANGLE){
return lastMouseAngle = mouseAngle
}

}
这样 circleAngle 就会呈线性变化了

接下来的事就是找一张阿丽塔的美图了
<div class=”pic”>
<img src=”./alita.jpg”>
</div>
.pic {
width: 100px;
margin: 100px auto;
border-radius: 10px;
}
.pic img {
width: 100%;
border-radius: 10px;
}
最后写一段缩放代码:
var picDom = document.getElementsByClassName(‘pic’)[0]

function controlPic(value) {
this.style.width = 100 + 1 * value + ‘px’
}

function main(e) {

controlPic.call(picDom, circleAngle)

}
大功告成!查看完整的代码示例请戳这里,在线体验请戳这里
后记
从《阿丽塔》上映那天起就开始酝酿这篇博客了,直到一个多月后的今天……
不多 BB,下一篇博客将会在 demo 的基础上封装插件,有生之年见~

原文地址在此,欢迎来玩~

退出移动版