如何使用requestAnimationFrame()移动多个元素而不必为每个元素创建新函数

时间:2019-06-26 04:16:03

标签: javascript performance

作为序言:这是我计划重复使用并添加到各种网页的内容。因此,我不想为此使用任何JQuery,部分原因是我在其上使用的许多页面都不需要其他东西(使我对JQuery的使用有点像用面粉装满五十加仑的桶只是为了使用一个一茶匙的面粉),部分是因为它是为资源稀少的移动浏览器设计的。

JQuery缺乏效率似乎没什么大不了的,特别是因为its consolidated library的大小不足31MB,但是据this page称,这对JavaScript来说{{1} }在Chrome中的通话速度是JQueries等效的document.getElementById('foo');的16倍。此外,尽管$('#foo');在Firefox和Safari中的运行速度比Chrome快,但$('#foo');在Safari中的运行速度仍然快10倍,而在Firefox中却快了500倍。不过,如果您的资料来源相互矛盾,请随时将其发布在评论中,并对已经这样做的人进行评论。

我有一组导航链接,相对于它们距第一个导航链接有多远,我试图将其快速向上移动到一个位置。为了帮助您理解我的意思,这是我的导航栏的动画(使用JQuery slideUpslideDown函数完成),因此您可以了解其工作原理(&结巴): JQuery Animated Nav Bar

如果有帮助,我还将包括导航栏的HTML:

document.getElementById('foo');

我的JavaScript文件具有一个名为<nav> <ul id='nav'> <li><a href="index.php"><icon><img src="images/home-icon.png"></icon>Home</a></li> <li><a href="skillsets.php"><icon><img src="images/skills-icon.png"></icon>Skillsets</a></li> <li><a href="gallery.php"><icon><img src="images/gallery-icon.png"></icon>Gallery</a></li> <li><a href="about.php"><icon><img src="images/about-icon.png"></icon>About</a></li> <li><a href="contact.php" style='border-right:1px solid black;'><icon><img src="images/contact-icon.png"></icon>Contact</a></li> </ul> </nav> 的函数,该函数仅用于设置列表并将其折叠(通过移动除第一个导航链接以外的所有链接)。为了防止prepareList()及其使用的任何变量在HTML加载之前被初始化,它们全部包含在此函数中:

prepareList()

现在,我将包含我的document.addEventListener("DOMContentLoaded", function(event) {/*all the code*/}); JavaScript函数以及所有使用的变量的初始化和值。我已经对此进行了很好的评论,以使您仅从代码中的注释就可以理解它,但是我将在之后澄清我想做的事情:

prepareList()

如果您看不到我不能怪您的问题,那就是//I will consolidate the first three objects into one href variable when I get the program working var ul = document.getElementsByTagName("nav")[0].children[0], li = ul.children, href = new Array(li.length), is404 = true, isDown = false; var itterator; for(itterator = 0; itterator < li.length; itterator++) href[itterator] = li[itterator].children[0]; prepareList(); function prepareList() { //add a plus to the first nav link making it more obvious that it's the button to open the nav href[0].innerHTML = href[0].innerHTML + "<span> +</span>"; //hide the remaining nav links with animations that move them up, at a speed, //and to a postion, relative to how far they are from the first nav link var lastTick = +new Date(); /*This function will be called for each nav link so they move at the same time navElement is one of the li elements, number is the number (index) of the li element, and number is negative if the list is collapsing (moving up)*/ var tick = function(navElement, number) { var navElementStyleTop = parseFloat(navElement.style.top) + number * navElement.clientHeight * (new Date() - lastTick) / 1000; //navElement.style.top = (navElementStyleTop + number * navElement.clientHeight * (new Date() - lastTick) / 1000) + "px"; //milliseconds navElement.style.top = navElementStyleTop + "px"; lastTick = +new Date(); //requestAnimationFrame(tick) will only loop tick if the animation can be performed: http://www.javascriptkit.com/javatutors/requestanimationframe.shtml //setTimeout(tick, 1000/15) will force the animation to be performed if it's been more than 1/15th of a second in order to maintain persistance of motion if(number < 0){ if (navElementStyleTop > number * navElement.clientHeight) requestAnimationFrame(tick(navElement, number)) || setTimeout(tick, 1000/15); else //To prevent rounding errors from messing up the final position navElement.style.top = (number * navElement.clientHeight) + "px"; } else{ if (navElementStyleTop < 0) requestAnimationFrame(tick(navElement, number)) || setTimeout(tick, 1000/15); else //To prevent rounding errors from messing up the final position navElement.style.top = "0px"; } }; /*The for loop is to make this work no matter the number of links in the nav bar (as the function executed before this may add a link to the nav bar)*/ for(itterator = 1; itterator < li.length; itterator++){ //necessary for top to work href[itterator].style.position = "relative"; //give top a value so this doesn't rely on the browser defaulting it to 0 href[itterator].style.top = "0px"; tick(href[itterator], (-1) * itterator); } } 。为了使它不会抱怨递归,必须以这种方式执行:requestAnimationFrame(tick(navElement, number))。根据{{​​3}},requestAnimationFrame(tick)是异步的,表示“主线程不等待对requestAnimFrame的调用返回,然后再继续执行”。

问题是我必须至少传递requestAnimationFrame到函数中,以便函数知道它正在使用哪个元素。我看到的使该函数不需要传入任何参数的唯一方法是每次都在for循环中对其进行初始化。我真的不喜欢这种解决方案,因为我希望能够重用此功能,以免浪费第一个链接后为每个导航链接重新创建它的周期(可能会占用很多内存)。因此,我想知道的是,是否有一种方法可以将number传递到所有的tick函数中,同时保持number的异步性质。

如果您有一种比JQuery的this StackOverFlow answerslideUp函数更有效的更好方法,那么我非常高兴。也许有人在线上有一个效率更高的JavaScript版本,但我还没有找到。

如果您需要更多信息或我有错别字,请随时在评论中让我知道。

1 个答案:

答案 0 :(得分:0)

我花了很长时间来解决这个问题,方法是将每次尝试将自己从tick()传递给自身的变量转换为外部变量。但是,如果我的回答如此简单,我就不会做到。但是,有一些优化对我在移动浏览器上的使用产生了巨大的影响。我进行的第一个也是最次要的优化是从导航中删除多余的元素:

<nav>
    <a href="index.php"><icon><img src="images/home-icon.png"></icon><span>Home</span></a>
    <a href="skillsets.php"><icon><img src="images/skills-icon.png"></icon><span>Skillsets</span></a>
    <a href="gallery.php"><icon><img src="images/gallery-icon.png"></icon><span>Gallery</span></a>
    <a href="about.php"><icon><img src="images/about-icon.png"></icon><span>About</span></a>
    <a href="contact.php"><icon><img src="images/contact-icon.png"></icon><span>Contact</span></a>
</nav>

应用于导航栏最后一个链接的样式属性通过将其应用于nav a:last-child类而移动到了外部CSS文件。应当注意,这阻止了我将每个链接中的图像和文本锚定到该链接的底部。这是必要的,因为它使每个导航链接看起来好像实际上都在向上滑动,而不是从底部断开。为了解决这个问题,我实际上将每个导航链接向上移动。这要求我在CSS文件中将nav a类设置为具有属性position:absolute。我发现这比更改除第一个导航链接以外的所有链接的高度要快得多,而每个导航链接的图像和文本均锚定在它们的底部。我相信这是因为我必须使用动态FLEX CSS属性来进行锚定。

以这种方式赋予每个导航链接一个绝对位置,使它们都具有相同的位置,并且每个链接都显示在前一个链接的顶部。这与我想发生的情况相反,因此在JavaScript中,我将它们下移并为每个z-index设置比另一个高的索引,最后一个nav链接没有这样的属性,因此具有z-index的0:

var href = document.getElementsByTagName("nav")[0].children;
//Place the first link on top of the second link, on top of the third link, etcetera
for(var itterator = 0, hrefLengthMinusOne = href.length - 1; itterator < hrefLengthMinusOne; itterator++)
    href[itterator].style.zIndex = hrefLengthMinusOne - itterator;
//Move the links down instantaneously (to be slid up later to help the user understand the nav bar is dynamic)
for(var itterator = 1, hrefLength = href.length, linkHeight = href[0].clientHeight; itterator < hrefLength; itterator++)
    href[itterator].style.top = itterator * linkHeight;

应该注意的是,如果一个人需要所有这些链接显示在z索引大于0的东西之上,那么所有要做的就是使父级{{1} }类,位于比该元素的z-index高的引用CSS文件中。

我对tick函数的更改如下:

nav

现在很多了,评论应该可以解释其中的大部分内容,但是重要的两件事是我不再使用/*Used to determine the height each link needs to get up to when moving down the menu (created because referencing navHeight is a LOT faster than referencing href[0].clientHeight 1000 times every time the menu needs to be slid down)*/ var navHeight = href[0].clientHeight; /*Put the first links span text into a variable so that we don't have to slice() off the last character every time to add on a '+' or '-' sign. It should be noted that navSpanText is not an HTML element (so changing it will not change the HTML span text). Because of that, & because href[0].children[1].textContent calls more than two child elements. navSpan is an HTML element, referencing navSpanText is over 60^2 times faster than href[0].children[1].textContent. but because it's only one call, it's still orders of magnitude faster than href[0].children[1].textContent*/ var navSpan = href[0].children[1]; var navSpanText = navSpan.textContent + " "; var navSpanTextLength = navSpanText.length - 1; //add a plus to the first nav link making it more obvious that it's the button to open the nav navSpan.textContent = navSpanText + "+"; /*Create a function (& appropriate variables) to hide the remaining nav links with animations that move them up, at a speed, and to a postion, relative to how far they are from the first nav link. These functions are created locally as closures so as not to make global functions that recreate these closures every time.*/ var currentTick; var lastTick = Date.now(); //System time before tick changed the DOM var hrefFactor = -navHeight; //navHeight when the menu needs to be slid down & -navHeight when the menu needs to be slid up var hrefPosition = navHeight; //Used to hold the new position of the first link so as not to have to grab it from the DOM every time var itterator; //Used in for loops to itterate through the nav links var tick = function() { //console.log("lastTick: " + lastTick + ", hrefPosition: " + hrefPosition + ", hrefFactor: " + hrefFactor); currentTick = Date.now(); hrefPosition += (currentTick - lastTick) * hrefFactor / 400; lastTick = currentTick; /* The for loop is to make this work no matter the number of links in the nav bar (as the function executed before this may add a link to the nav bar)*/ for(itterator = href.length - 1; itterator > 0; itterator--) href[itterator].style.top = hrefPosition * itterator; //requestAnimationFrame(tickUp) will only loop tickUp if the animation can be performed: http://www.javascriptkit.com/javatutors/requestanimationframe.shtml if ((hrefFactor > 0 && hrefPosition < navHeight) || (hrefFactor < 0 && hrefPosition > 0)) requestAnimationFrame(tick); else if (hrefFactor > 0) //To prevent rounding errors from messing up the final positions for(itterator = href.length - 1; itterator > 0; itterator--) href[itterator].style.top = navHeight * itterator; else //To prevent rounding errors from messing up the final postions for(itterator = href.length - 1; itterator > 0; itterator--) href[itterator].style.removeProperty("top"); }; //Actually slide the nav menu up tick(); //Add an event listener to make the menu slide up & down by tapping on the first link href[0].addEventListener('click', function(event){ event.preventDefault(); if(hrefFactor < 0){ lastTick = Date.now(); hrefPosition = 0; hrefFactor = navHeight; tick(); //replace the '+' with a '-' making it more obvious that it's now the button to close the nav navSpan.textContent = navSpanText + "-"; }else{ lastTick = Date.now(); hrefPosition = navHeight; hrefFactor = -navHeight; tick(); //replace the '-' with a '+' making it more obvious that it's now the button to open the nav navSpan.textContent = navSpanText + "+"; } }); new Date()速度是setTimeout(tick, 1000/15)的两倍(Date.now()调用近1000次时确实很重要),尽管new Date()可能会在某些处理器速度很慢的计算机上停止卡顿,但也会干扰处理器的其他部分页面,这对我很重要,因为这不是我仅有的JavaScript。

我还使用tick是大于还是小于零来查看最终的setTimeout(tick, 1000/15)hrefFactor还是hrefPosition。与使用两个不同的功能打开和关闭菜单相比,此过程要花费更多的时间,但是我认为节省内存是值得的。应该注意的是,我单独更改每个导航链接的位置会大大减慢滴答声,甚至比使用>=navHeight而不是<=0慢得多,但无法分配每个导航链接同时处于不同的位置。