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

前言

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

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

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

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

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

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

没错,今天的主角出场了
就是用JS做一个类似的旋转缩放控件
先来看一哈最终效果,铛铛:

《JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇》
貌似手感不错?可以点此抢先体验(此版本仅限PC端玩家)

正文

本篇首先开发一个demo试试水,暂时用鼠标代替手指

实现思路如下:

  1. 画一个彩色圆环
  2. 监听鼠标移动事件(保证只能作用在圆环上)
  3. 实时计算鼠标当前的角度(相对于圆心)
  4. 对比当前角度和上一次角度,确定每一帧的旋转方向和距离,根据变化的角度值旋转圆环
  5. 缩放操作外部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])

颜色有点瓜皮:
《JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇》

2. 监听鼠标事件

监听大圆的mousemove事件,同时小圆阻止事件传播

bigCircle.addEventListener('mousemove', main)
smallCircle.addEventListener('mousemove', function(e) {
    e.stopPropagation()
})

创建大圆的监听函数main
这里设置鼠标左键按下时生效,顺便写一个打印语句

function main(e) {
    if(e.buttons === 1) {
        console.log('鼠标在圆环移动ing')
    }
}

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

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)
    }
}

《JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇》
如图可以看到,角度的变化是从9点钟方向的180度,顺时针递减360度,回到9点钟方向
正符合我们后续的需求

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

“大师果然牛皮,不知您的一身好武功是如何修来的?”
atan2笑而不语,一转身便消失在了无尽的夜色中,只留下了无尽的疑问……

既然如此,作为一个热爱技术的搬砖工,我决定自己找出真相~

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

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
    }
}

《JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇》
可以看到已经有了雏形,愉快地进入下一步

5. 操作外部dom

想要操作外部dom,需要的是一个线性变化的值
用脚趾头都能想到,当前最合适的无疑就是当前圆环角度circleAngle
然鹅仔细观察上一张图就会发现,当鼠标每次移动过9点钟方向时,圆环角度就会瞬间改变360度,回到初始值,并不能满足当前需求《JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇》
此处做一波改造,判断当角度的变化值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就会呈线性变化了

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

接下来的事就是找一张阿丽塔的美图了

<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)
    ...
}

大功告成!查看完整的代码示例请戳这里,在线体验请戳这里《JS仿《阿丽塔》中依德医生的旋转缩放控件 — DEMO篇》

后记

从《阿丽塔》上映那天起就开始酝酿这篇博客了,直到一个多月后的今天……

不多BB,下一篇博客将会在demo的基础上封装插件,有生之年见~

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

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

    原文作者:前端
    原文地址: https://segmentfault.com/a/1190000018732886
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞