如何使Vanilla Javascript动画不那么滞后

How to make Vanilla Javascript animation less laggy?

本文关键字:滞后 动画 何使 Vanilla Javascript      更新时间:2023-09-26

我有一段代码,应该用来移动这个图像(id="background"),我已经在本地下载了它,它非常大。当我将鼠标悬停在某个div的顶部时,它应该会移动。然后,这会更改不透明度CSS值,js会检测到该值,从而使图像移动。js代码如下:

setInterval(function(){
    var element = document.getElementById('background'),
        style = window.getComputedStyle(element),
        left = style.getPropertyValue('left');
    left = left.replace(/px/g, "")
    left = parseInt(left,10)
    if(getOpacity('rightbar') == .5){
        document.getElementById('background').style.left = left - 8 + 'px'
    }
    if(getOpacity('leftbar') == .5){
        document.getElementById('background').style.left = left + 8 + 'px'
    }
},10)

getOpacity(idName)函数如下所示:

function getOpacity(param){
    var element = document.getElementById(param),
        style = window.getComputedStyle(element),
        opacity = style.getPropertyValue('opacity');
    return opacity
}

所以问题是,无论我使用什么运动值或setInteveral时间,动画总是显得滞后。有没有一种方法可以用vanilla-js让它变得平滑,或者更好的方法是,取消不透明度检测,用CSS来完成这一切?

当我把上面的代码放在小提琴里时,它运行得很好,但当它真正运行全浏览器(在我的个人chrome窗口上)时,它看起来是这样的。

注意:我在4k显示器上运行这个完整的浏览器窗口,这对chrome来说太难处理了吗?

1。使用requestAnimationFrame而不是setInterval

这表示浏览器希望在下一次重绘之前执行某些操作。您提供的回调每帧执行一次。

(如果这很重要:requestAnimationFrame在IE9及以下版本中不起作用。)

2.在值之间,每帧不要增加固定值

当使用requestAnimationFrame和使用setInterval时,帧之间的时间差都不同。

您可以在开发者工具栏中使用类似的东西来验证这一点:

var last = new Date();
function onFrame(){
  var now = new Date();
  console.log(new Date() - last);
  last = now;
  requestAnimationFrame(onFrame);
}
onFrame();

开发者控制台将以毫秒为单位输出帧时间,如下所示:

16
17
17
15
19
...

如果在蒸发间隔上以固定的量增加位置(在不透明度等方面不太明显),动画将看起来锯齿状。因此,不执行left = left + 8;,而是根据当前时间计算您在动画中的位置,类似于以下内容:

var myElement = ...;
var initialOpacity = 1.0;
var targetOpacity = 0.5;
var duration = 2000;
var startTime = new Date();
function animation() {
  var delta = Math.min(1, (new Date() - startTime) / duration);
  // delta is now a number in the range [0 ... 1]
  myElement.style.opacity = initialOpacity + delta * (targetOpacity - initialOpacity);
  if (delta < 1) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);

是的,这个例子是不透明的,而不是位置,但你明白了——你的老师不能声称你是复制粘贴的;——)

3.不要在JS和CSS之间来回读写

假设图像的初始位置与视口无关(例如left: -10%),则无需读取每帧上的位置。当您的JavaScript是唯一更改left属性的东西时,为什么要从CSS中读取它?将其保存在一个变量中,并从JavaScript中将其设置为CSS。

var initialX = ComputeCssXPosition(myElement);
...
function animate() {
  ...
  myElement.style.left = computedNewXPosition;
}

如果您想在用户悬停元素时更改位置,请在JS中使用鼠标事件。

myElement.addEventListener('mouseover', function (ev) { ... });
myElement.addEventListener('mouseout', function (ev) { ... });

备选方案:使用CSS转换

Shomz已经在回答中提到了。

最好的方法是使用requestAnimationFrame而不是setInterval,并且不检查样式更改,而是使用鼠标悬停侦听器(太多通信:CSS->JS->CSS->JS…)。请参阅此处:https://jsfiddle.net/yqpun3eb/4/

但是,如果您仍然想使用setInterval,您可以简单地将转换CSS规则放在background元素上。例如:

#background {transition: left 0.2s linear}

这将平滑所有的值变化,因为CSS的性能要好得多,所以即使在4K屏幕上也应该很好。问题是您的更改可以跳跃8个像素。

在我的机器上使用0.2秒似乎效果不错:https://jsfiddle.net/yqpun3eb/3/


哦,顺便说一句,你想要好的性能,但你为什么用这个来强奸系统:

function getOpacity(param){
    var element = document.getElementById(param),
        style = window.getComputedStyle(element),
        opacity = style.getPropertyValue('opacity');
    return opacity
}

这不会创建额外的变量(无论如何你都不需要):

function getOpacity(param){
    return window.getComputedStyle(document.getElementById(param))
                 .getPropertyValue('opacity');
}

最后,这里有一个使用requestAnimationFrame的稍微优化的版本(我就是这样做的+使用监听器而不是读取样式值):https://jsfiddle.net/yqpun3eb/4/