mui刷新不显示动画_requestAnimationFrame实现动画新手快速上路

一、平滑的动画

大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率。因此,最平滑动画的最佳循环间隔是1000ms/60 = 16.7ms 每帧。以16.7ms这个循环间隔重绘的动画是最平滑的,因为这个最接近浏览器的最高限速。

二、setInterval存在的问题

下面一段代码用setInterval实现了每隔半秒计数一次

let count = 0;// 每隔半秒计数一次document.getElementById('count').onclick = () => {     setInterval(increment, 500);}const increment = () => {      count++;      document.getElementById('count').innerText = count;}
然而使用setInterval会有问题,后一个间歇调用可能会在前一个间歇调用结束之前启动,还有可能会出现多个任务同时一次性执行。可以考虑一种特殊的情况:主线程正在执行一个耗时的任务,setInterval新任务A添加到异步任务队列中。又过了500ms,主线程依然非空闲,但是计数器的500ms到了,它会又往任务队列中添加一个任务A。任务队列里面堆积了很多的任务A。当主线程一旦空闲开始执行异步任务,那么这一系列的任务A就会迅速依次执行,本来每个A任务之间应该是有一个500ms的间隔的,但是由于这些A直接堆积不再由定时器控制,所以会一次执行完,中间不再符合预期的500ms间隔。

三、用setTimeout来模拟setInterval

为了解决setInterval这个问题,可以用setTimeout来模拟setInterval来实现相同效果。
let count2 = 0;// 每隔半秒计数一次document.getElementById('count2').onclick = () => {     increment2();}const increment2 = () => {  count2++;  document.getElementById('count2').innerText = count2;  setTimeout(increment2, 500);}

setTimout()的第二个参数告诉JavaScript再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行;如果队列不是空的,那么它就要等前面的代码执行完了以后再执行。

考虑特殊情况:主线程非空闲,setTimeout添加一个新任务A到任务队列中。等待主线程空闲后会执行刚刚添加的任务。当这个任务执行结束时,会再次添加一个新任务A到任务队列中。那么新的定时器中,任务总是在当前定时器任务执行完成后才会开启定新的任务,从而不会出现像setInterval中不断往任务队列中添加任务的情况,因此也就不会出现所有任务一次性执行完中间没有间隔的情况。

四、更精确的动画实现requestAnimationFrame

什么时候绘制下一帧是保证动画平滑的关键。但是setTimeout和setInterval都不精确。传入的第二个参数,实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务执行完成后再执行。 简言之,以毫秒表示的延迟时间,并不代表到时候一定会执行动画代码,而仅代表带时候会把代码添加到任务队列中。如果UI线程繁忙,那么即使把代码加入队列也不会立即执行。 此外,刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同的设备屏幕刷新频率可能会不同,而setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相等。 window.requestAnimationFrame()就很好了,它告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的  里时, requestAnimationFrame()  会被暂停调用以提升性能和电池寿命。 依旧是每隔半秒计数,用requestAnimationFrame()可以这样实现
// 每隔半秒计数一次let count = 0;document.getElementById('count').onclick = () => {     // 首次调用也最好这样写,如果直接increment()调用,timestamp首次打印出的值为NaN     window.requestAnimationFrame(increment);}let start = 0;const increment = (timestamp) => {  // requestAnimationFrame调用回调函数的时候,会传入一个时间戳  // 通过这个时间戳进行比对来实现自定义延迟  if (timestamp - start > 500) {      start = timestamp;      count++;      document.getElementById('count').innerText = count;  }  window.requestAnimationFrame(increment);}

五、肉眼可见的卡顿

你也许会有这样的疑问,如果不考虑主线程占用,给setTimeout设置毫秒值为16.7,和直接使用requestAnimationFrame相比,会有差别吗?效果证明还是有肉眼可见的卡顿。 以下代码实现了2个小球滚动的效果
            Document            * {            margin: 0;            padding: 0;        }        .box {            height: 100px;            width: 100px;            background-color: orange;            border-radius: 50%;            margin: 30px;        }    setTimeout:        requestanimationframe:        const box1 = document.querySelector("#box1");    const box2 = document.querySelector("#box2");    let maxDis = 1400;    let step = 0;    function box1Animate() {        if (step >= maxDis) return;        step += 5;        box1.style.transform = `translateX(${step}px)`;        setTimeout(box1Animate, 16.7);    }    box1Animate();    let start = 0;    let step2 = 0;    function box2Animate(timestamp) {        step2 += 5;        box2.style.transform = `translateX(${step2}px)`;        if (step2 < maxDis) {            window.requestAnimationFrame(box2Animate);        }    }    window.requestAnimationFrame(box2Animate);

六、新手快速上路

讲了那么多,相信你对requestAnimationFrame有了一定的理解。废话不多说,写一个小demo,带你快速上路。 先来看一下要实现的效果,标签滚动旋转,圆环放大缩小

c73eda7a27a845127345f94e158776bf.png

代码实现如下: HTML部分:
                    html,        body {            margin: 0;            padding: 0;            border: 0;            width: 100%;            height: 100%;        }        #container {            width: 100%;            height: 100%;            background-color: #000;        }        .item {            position: absolute;            width: 200px;            height: 60px;            border: 1px solid #00f8ff;            border-radius: 10px;            box-shadow: 1px 1px 36px rgb(8 255 236 / 50%);            color: #575757;            line-height: 60px;            transition: transform 0.5s;            background: #0cb6f8;            text-align: center;        }        
JS部分:
var arr = [];function createElement(num) {    var arr = [];    for(var i = 0; i < num; i++) {        var div = document.createElement("div");        div.innerHTML = `ITEM${i}`;        div.className = "item";        arr.push(div);    }    return arr;}function setPos(arr, num, radius, dRad) {    var w = document.querySelector("body").clientWidth;    var h = document.querySelector("body").clientHeight;    var rad = 2 * Math.PI / num;    for(var i = 0; i         var r = i * rad;        var div = arr[i];        var x = radius * Math.cos(r + dRad) + w / 2;        var y = radius * Math.sin(r + dRad) + h / 2;        div.style.transform = `translate(${x}px, ${y}px)`;    }}function init() {   arr = createElement(20);    var container = document.querySelector("#container");    for(var i = 0; i < arr.length; i++) {        var div = arr[i];        container.appendChild(div);     }    window.requestAnimationFrame(animate);}var dRad = 0;var radius = 400;function animate(timestamp) {    dRad+= 0.01;    radius++;    if(radius > 400) {        radius = 100;    }    setPos(arr, 20, radius, dRad);    window.requestAnimationFrame(animate);}init();

f1f590abd0de9adf1235a463e3af7b8b.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值