前言前些天看了《阿丽塔》感叹酷炫特效的同时,不得不说这个片子灰常之热血!重新点燃了我粪斗的基情!!有那么几个瞬间仿佛自己回到了……OK,下面进入正题在依德医生刚捡回阿丽塔的那一段,有木有发现医生家的设备都很有意思~比如那个人皮缝纫机,其灵活程度堪比织网ing的蜘蛛说到蜘蛛,就想起了西游记里的蜘蛛精。今年下半年…………………………除了人皮缝纫机,当时还注意到他们屏幕的一个交互很有趣——没错,今天的主角出场了就是用JS做一个类似的旋转缩放控件先来看一哈最终效果,铛铛:貌似手感不错?可以点此抢先体验(此版本仅限PC端玩家)正文本篇首先开发一个demo试试水,暂时用鼠标代替手指实现思路如下:画一个彩色圆环监听鼠标移动事件(保证只能作用在圆环上)实时计算鼠标当前的角度(相对于圆心)对比当前角度和上一次角度,确定每一帧的旋转方向和距离,根据变化的角度值旋转圆环缩放操作外部dom1. 画圆环这里使用大圆套小圆组成圆环:大圆设置一个渐变背景色,小圆为纯白色为啥子不只用一个圆,然后设置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、上一次鼠标角度值lastMouseAnglevar 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年用户的手速,暂时设置这个值为300var MAX_CHANGE_ANGLE = 300function 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的基础上封装插件,有生之年见原文地址在此 ,欢迎来玩~