马黑黑 发表于 2022-12-13 17:28

旋转运动平滑增减速的实现

<style>
#papa { --ss: 4s; --deg: 360deg; margin: auto; position: relative; width: 100%; }
#papa > p { padding: 6px 0; }
.rotBox { margin: 10px auto 0; position: relative; width: 60px; height: 60px; display: grid; place-items: center center; font-size: 12px; cursor: pointer; background: conic-gradient(red,orange,yellow,green,teal,blue,purple);mask: radial-gradient(transparent 3px,red 0);-webkit-mask: radial-gradient(transparent 3px,red 0);border-radius: 50%; }
.rotBox:nth-of-type(1), .rotBox:nth-of-type(2) { animation: rot var(--ss) infinite linear; }
@keyframes rot { to { transform: rotate(var(--deg)); } }
</style>

<div id="papa">

        <p>CSS关键帧动画可以让盒子匀速旋转,但倘若引入缓动运动(增减速运动),不论我们通过改变旋转角度还是动画运行时长加以实现,得到的结果都是卡顿或跳帧——旋转对象在上下两个数值交替过程中的衔接瞬间难以做到自然过渡,即便加入合适的 transition 属性值。下面示例,初始时第一个光盘和第二个光盘都匀速旋转,每次点击光盘1都会随机改变其运行时长,每次点击光盘2都改变其旋转角度,肉眼可以发现,它们在相应数值改变时的旋转过渡并不平滑,或前后跳帧,或卡顿。</p>
        <span class="rotBox" title="光盘1 - 改变运行时长"></span>
        <span class="rotBox" title="光盘2 - 改变旋转角度"></span>
        <p>或许加入一些复杂的算法可以解决上面存在的问题,但处理起来很繁琐。所以,连贯运动着的缓动运动还是建议抛弃关键帧动画,采用JS定时器来动态驱动transform rotate,实际上是改变旋转的角度——给旋转对象在旋转过程中施以不同的角度。JS定时器有两大类,一类是 setInterval 和 setTimeout,另一类是基于屏幕刷新率的 rAF 即 requestAnimationFrame(请求关键帧动画)API。下面的两个光盘也都在匀速旋转,第一个是通过setTimeout驱动,第二个是通过 requestAnimationFrame 驱动,每次单击它们都会分别改变旋转速度,看一看旋转速度改变后的光盘旋转过渡状态,:</p>
        <span class="rotBox" title="光盘3 - setTimeout"></span>
        <span class="rotBox" title="光盘4 - rAF"></span>
        <p>肉眼不易看出二者在旋转速度改变之后的缓动衔接有什么区别,但如果多点几次、细细观察,还是能感觉到第二个光盘的整体旋转与变速后的衔接更自然、平滑,道理与各自的工作机制有关:setTimeout通过CPU时钟按指定间隔时间驱动对象的旋转,rAF则通过显卡的刷新率来实现驱动而不是通过计时。我们知道,计时驱动下的两个动作间衔接总是有间隔的,即便我们用了肉眼不能觉察的 1000/60 的间隔时间;而rAF,只要我们正在使用的显示器没有异常(刷新率在60赫兹上下),由它驱动的动画我们就看不出卡顿或跳帧从而达到平滑过渡的效果。</p>
        <p>set...类和 rAF类也都有缺点,主要有:一是,多次调用,它们都会比实际定义的速度快一点,动画运行的时间越长这种现象就会更明显,rAF尤甚(解决方法是及时通过回调ID清除,clearSetTimeout(id)/clearSetInterval(id)和cancelAnimationFrame(id));二是,set...类计时的精准度不稳定,长时间运行需要定时通过时间戳进行校对,而rAF类一切依赖系统的刷新率,动画运行的实际速度与刷新率呈正比关系——这意味着我们事实上并不能在代码中真实有效地按自己的预期控制动画的运行速度,除非我们也引入时间戳的算法来进行运算、控制。尽管如此,对于普通的动画,例如本帖中驱动光盘盒子旋转,定时器的使用仍然是合适的。</p>
</div>

<script>
let deg1 = 10, deg2 = 10, step1 = 2, step2 = 2;
let rotId1, rotId2;
let boxes = document.querySelectorAll('.rotBox');
boxes.onclick = () => boxes.style.setProperty('--ss' ,Math.random()*5 + 0.5 + 's');
boxes.onclick = () => boxes.style.setProperty('--deg' ,Math.random()*360 + 1 + 'deg');

boxes.onclick = () => {
        step1 = Math.abs(10 - Math.random() * 30) + 1;
        rotId1 = setTimeout(rotate1);
}

boxes.onclick = () => {
        step2 = Math.abs(10 - Math.random() * 30) + 1;
        rotId2 = requestAnimationFrame(rotate2);
}

let rotate1 = () => {
        deg1 += step1;
        boxes.style.transform = 'rotate('+ deg1 + 'deg)';
        if(rotId1 > 0) clearTimeout(rotId1);
        rotId1 = setTimeout(rotate1,17);
}

let rotate2 = () => {
        deg2 += step2;
        boxes.style.transform = 'rotate('+ deg2 + 'deg)';
        if(rotId2 > 0) cancelAnimationFrame(rotId2);
        rotId2 = requestAnimationFrame(rotate2);
}

rotId1 = setTimeout(rotate1,17);
rotId2 = requestAnimationFrame(rotate2);
</script>

好开心 发表于 2022-12-13 17:57

越点转的越快

马黑黑 发表于 2022-12-13 18:12

好开心 发表于 2022-12-13 17:57
越点转的越快

随机的,总有变慢的

红影 发表于 2022-12-13 19:31

通过比较,的确后面的改变速度没有卡顿的现象。

红影 发表于 2022-12-13 19:32

这个研究得太细了,大多数的人不太会注意到这点的吧{:4_173:}

小辣椒 发表于 2022-12-13 20:16

黑黑,这个实例是想说明什么呢?要运用到播放器按钮里面?

马黑黑 发表于 2022-12-13 21:19

小辣椒 发表于 2022-12-13 20:16
黑黑,这个实例是想说明什么呢?要运用到播放器按钮里面?

克罗地亚狂想曲那个帖子就用上的

马黑黑 发表于 2022-12-13 21:19

红影 发表于 2022-12-13 19:31
通过比较,的确后面的改变速度没有卡顿的现象。

对,感觉好好多

马黑黑 发表于 2022-12-13 21:21

红影 发表于 2022-12-13 19:32
这个研究得太细了,大多数的人不太会注意到这点的吧

用到就会注意到。克罗地亚狂想曲那帖子,我原想用频谱,会很好看的,最后想就用光盘吧,没想到做的时候碰上很多小问题,要一一解决,就有这个帖子

小辣椒 发表于 2022-12-13 21:21

马黑黑 发表于 2022-12-13 21:19
克罗地亚狂想曲那个帖子就用上的

那个效果我还以为是控制按钮。

红影 发表于 2022-12-13 22:24

马黑黑 发表于 2022-12-13 21:19
对,感觉好好多

非常顺滑。

红影 发表于 2022-12-13 22:26

马黑黑 发表于 2022-12-13 21:21
用到就会注意到。克罗地亚狂想曲那帖子,我原想用频谱,会很好看的,最后想就用光盘吧,没想到做的时候碰 ...

原来是那个帖子里碰到的问题的解决方案。即使是被音乐驱动的,也会卡顿么?

马黑黑 发表于 2022-12-13 23:41

红影 发表于 2022-12-13 22:26
原来是那个帖子里碰到的问题的解决方案。即使是被音乐驱动的,也会卡顿么?

方案一、二都行不通,唯有三四可以

马黑黑 发表于 2022-12-13 23:41

红影 发表于 2022-12-13 22:24
非常顺滑。

是的,rAF API能骗过人眼

红影 发表于 2022-12-14 12:48

马黑黑 发表于 2022-12-13 23:41
方案一、二都行不通,唯有三四可以

哦哦,知道了。

红影 发表于 2022-12-14 12:49

马黑黑 发表于 2022-12-13 23:41
是的,rAF API能骗过人眼

跟电影一样吧,那个也是能欺骗人眼的{:4_173:}

马黑黑 发表于 2022-12-14 12:54

红影 发表于 2022-12-14 12:49
跟电影一样吧,那个也是能欺骗人眼的

对,和蒙太奇一个道理

马黑黑 发表于 2022-12-14 12:54

红影 发表于 2022-12-14 12:48
哦哦,知道了。

{:4_181:}

红影 发表于 2022-12-14 16:26

马黑黑 发表于 2022-12-14 12:54
对,和蒙太奇一个道理

很多东西只要肉眼分辨不出,就可以当不存在。

红影 发表于 2022-12-14 16:26

马黑黑 发表于 2022-12-14 12:54


细微处有这么多讲究,很不容易{:4_187:}
页: [1] 2 3
查看完整版本: 旋转运动平滑增减速的实现