Heim  >  Artikel  >  Web-Frontend  >  Hochleistungsanimation und Seitenrendering mit Javascript

Hochleistungsanimation und Seitenrendering mit Javascript

黄舟
黄舟Original
2017-02-25 13:34:041309Durchsuche


Kein setTimeout, kein setInterval

Wenn Sie setTimeout oder setInterval verwenden müssen, um eine Animation zu implementieren, kann der Grund nur darin liegen, dass Sie eine genaue Steuerung der Animation benötigen. Aber ich denke, dass fortgeschrittene Browser und sogar mobile Browser zumindest zum jetzigen Zeitpunkt so beliebt sind, dass Sie einen Grund haben, bei der Implementierung von Animationen effizientere Methoden zu verwenden.

Was ist effizient?

Die Seite wird vom System (GPU oder CPU) bei jedem Framewechsel neu gezeichnet. Diese Art des Zeichnens unterscheidet sich jedoch vom Zeichnen von PC-Spielen. Die maximale Zeichenfrequenz ist durch die Bildwiederholfrequenz des Monitors (nicht der Grafikkarte) begrenzt, sodass die höchste Zeichenfrequenz in den meisten Fällen nur 60 Bilder pro Sekunde betragen kann. Bild pro Sekunde) (im Folgenden als fps bezeichnet), entsprechend 60 Hz der Anzeige. 60 fps ist der idealste Zustand. Bei täglichen Seitenleistungstests sind 60 fps ebenfalls ein wichtiger Indikator. Je näher, desto besser. Unter den Debugging-Tools von Chrome gibt es viele Tools zum Messen der aktuellen Frame-Nummer:

In der nächsten Arbeit werden wir diese Tools verwenden, um die Leistung unserer Seiten zu sehen Echtzeit.

60 fps sind sowohl Motivation als auch Druck, denn das bedeutet, dass wir nur 16,7 Millisekunden (1000/60) haben, um jedes Bild zu zeichnen. Wenn Sie setTimeout oder setInterval (im Folgenden gemeinsam als Timer bezeichnet) zur Steuerung des Zeichnens verwenden, treten Probleme auf.

Erstens ist die Verzögerungsberechnung des Timers nicht genau genug. Die Berechnung der Verzögerung basiert auf der integrierten Uhr des Browsers und die Genauigkeit der Uhr hängt von der Häufigkeit der Uhraktualisierungen (Timer-Auflösung) ab. Das Aktualisierungsintervall für IE8 und frühere IE-Versionen beträgt 15,6 Millisekunden. Angenommen, die von Ihnen festgelegte setTimeout-Verzögerung beträgt 16,7 ms. Dann muss sie zweimal um 15,6 Millisekunden aktualisiert werden, bevor die Verzögerung ausgelöst wird. Dies bedeutet auch eine unerklärliche Verzögerung von 15,6 x 2 – 16,7 = 14,5 Millisekunden.

            16.7ms
DELAY: |------------|

CLOCK: |----------|----------|
          15.6ms    15.6ms

Selbst wenn Sie die Verzögerung für setTimeout auf 0 ms einstellen, wird es nicht sofort ausgelöst. Die aktuelle Update-Frequenz der Browser Chrome und IE9+ beträgt 4 ms (wenn Sie einen Laptop verwenden und den Batterie- statt den Stromversorgungsmodus verwenden, stellt der Browser die Update-Frequenz zur Ressourcenschonung auf die gleiche Systemzeit um, d. h. kürzer). häufige Updates).

Wenn die Timer-Auflösung 16,7 ms erreichen kann, tritt auch ein asynchrones Warteschlangenproblem auf. Aufgrund der asynchronen Beziehung wird die Rückruffunktion in setTimeout nicht sofort ausgeführt, sondern muss zur Warteschlange hinzugefügt werden. Das Problem besteht jedoch darin, dass, wenn ein neues Synchronisationsskript ausgeführt werden muss, während auf den verzögerten Auslöser gewartet wird, das Synchronisationsskript nach dem Rückruf des Timers nicht in die Warteschlange gestellt wird, sondern sofort ausgeführt wird, z. B. der folgende Code:

function runForSeconds(s) {
    var start = +new Date();
    while (start + s * 1000 > (+new Date())) {}
}

document.body.addEventListener("click", function () {
    runForSeconds(10);
}, false);

setTimeout(function () {
    console.log("Done!");
}, 1000 * 3);

Wenn jemand auf den Text klickt, während er auf die 3 Sekunden wartet, um die Verzögerung auszulösen, wird der Rückruf dann immer noch rechtzeitig ausgelöst, wenn die 3 Sekunden abgelaufen sind? Natürlich nicht, es wird 10 Sekunden lang gewartet. Synchrone Funktionen haben immer Vorrang vor asynchronen Funktionen:

等待3秒延迟 |    1s    |    2s    |    3s    |--->console.log("Done!");

经过2秒     |----1s----|----2s----|          |--->console.log("Done!");

点击body后

以为是这样:|----1s----|----2s----|----3s----|--->console.log("Done!")--->|------------------10s----------------|

其实是这样:|----1s----|----2s----|------------------10s----------------|--->console.log("Done!");

John Resign hat drei Artikel über Timer-Leistung und -Genauigkeit: 1. Genauigkeit der JavaScript-Zeit, 2. Analysieren des Timers Leistung, 3. Funktionsweise von JavaScript-Timern. Dem Artikel können Sie einige Probleme mit Timer unter verschiedenen Plattformbrowsern und Betriebssystemen entnehmen.

Wenn man einen Schritt zurückgeht und davon ausgeht, dass die Timer-Auflösung 16,7 ms erreichen kann und die asynchrone Funktion nicht verzögert wird, ist die vom Timer gesteuerte Animation immer noch unbefriedigend. Darüber wird im nächsten Abschnitt gesprochen.

Problem mit der vertikalen Synchronisierung

Bitte erlauben Sie mir, eine weitere Konstante 60 einzuführen – die Bildschirmaktualisierungsrate 60 Hz.

Welche Beziehung besteht zwischen 60 Hz und 60 fps? Es gibt keine Beziehung. fps stellt die Frequenz dar, mit der die GPU das Bild rendert, und Hz stellt die Frequenz dar, mit der der Monitor den Bildschirm aktualisiert. Für ein statisches Bild können Sie sagen, dass die fps dieses Bilds 0 Bilder/Sekunde beträgt, Sie können jedoch niemals sagen, dass die Bildwiederholfrequenz des Bildschirms zu diesem Zeitpunkt 0 Hz beträgt, was bedeutet, dass sich die Bildwiederholfrequenz bei der Änderung nicht ändert des Bildinhaltes. Ob es sich um ein Spiel oder einen Browser handelt: Wenn wir von Frame-Drops sprechen, bedeutet das, dass die GPU das Bild seltener rendert. Sie sinkt beispielsweise auf 30 oder sogar 20 Bilder pro Sekunde, aber aufgrund des Prinzips der Persistenz des Sehens ist das Bild, das wir sehen, immer noch bewegt und kohärent.

In Fortsetzung des vorherigen Abschnitts gehen wir davon aus, dass jeder Timer weder verzögert noch durch Synchronisierungsfunktionen gestört wird und die Zeit sogar auf 16 ms verkürzen kann. Was dann passieren wird:

(Zum Vergrößern auf das Bild klicken)

Bildverlust trat nach 22 Sekunden auf

Wenn die Verzögerungszeit verkürzt wird, ist die Anzahl der verlorenen Bilder höher :

Die tatsächliche Situation wird viel komplizierter sein, als oben dargestellt. Selbst wenn Sie eine feste Verzögerung angeben können, um das Problem des Bildverlusts auf einem 60-Hz-Bildschirm zu lösen, was sollten Sie mit Monitoren mit anderen Bildwiederholfrequenzen tun? Sie müssen wissen, dass verschiedene Geräte oder sogar dasselbe Gerät unterschiedliche Bildwiederholfrequenzen haben unterschiedliche Batteriezustände.

以上同时还忽略了屏幕刷新画面的时间成本。问题产生于GPU渲染画面的频率和屏幕刷新频率的不一致:如果GPU渲染出一帧画面的时间比显示器刷新一张画面的时间要短(更快),那么当显示器还没有刷新完一张图片时,GPU渲染出的另一张图片已经送达并覆盖了前一张,导致屏幕上画面的撕裂,也就是是上半部分是前一张图片,下半部分是后一张图片:

PC游戏中解决这个问题的方法是开启垂直同步(v-sync),也就是让GPU妥协,GPU渲染图片必须在屏幕两次刷新之间,且必须等待屏幕发出的垂直同步信号。但这样同样也是要付出代价的:降低了GPU的输出频率,也就降低了画面的帧数。以至于你在玩需要高帧数运行的游戏时(比如竞速、第一人称射击)感觉到“顿卡”,因为掉帧。

requestAnimationFrame

在这里不谈requestAnimationFrame(以下简称rAF)用法,具体请参考MDN:Window.requestAnimationFrame()。我们来具体谈谈rAF所解决的问题。

从上一节我们可以总结出实现平滑动画的两个因素

  1. 时机(Frame Timing): 新的一帧准备好的时机

  2. 成本(Frame Budget): 渲染新的一帧需要多长的时间

这个Native API把我们从纠结于多久刷新的一次的困境中解救出来(其实rAF也不关心距离下次屏幕刷新页面还需要多久)。当我们调用这个函数的时候,我们告诉它需要做两件事: 1. 我们需要新的一帧;2.当你渲染新的一帧时需要执行我传给你的回调函数

那么它解决了我们上面描述的第一个问题,产生新的一帧的时机。

那么第二个问题呢。不,它无能为力。比如可以对比下面两个页面:

  1. DEMO

  2. DEMO-FIXED

对比两个页面的源码,你会发现只有一处不同:

// animation loop
function update(timestamp) {
    for(var m = 0; m < movers.length; m++) {
        // DEMO 版本
        //movers[m].style.left = ((Math.sin(movers[m].offsetTop + timestamp/1000)+1) * 500) + &#39;px&#39;;

        // FIXED 版本
        movers[m].style.left = ((Math.sin(m + timestamp/1000)+1) * 500) + &#39;px&#39;;
        }
    rAF(update);
};
rAF(update);

DEMO版本之所以慢的原因是,在修改每一个物体的left值时,会请求这个物体的offsetTop值。这是一个非常耗时的reflow操作(具体还有哪些耗时的reflow操作可以参考这篇: How (not) to trigger a layout in WebKit)。这一点从Chrome调试工具中可以看出来(截图中的某些功能需要在Chrome canary版本中才可启用)

未矫正的版本

可见大部分时间都花在了rendering上,而矫正之后的版本:

rendering时间大大减少了

但如果你的回调函数耗时真的很严重,rAF还是可以为你做一些什么的。比如当它发现无法维持60fps的频率时,它会把频率降低到30fps,至少能够保持帧数的稳定,保持动画的连贯。

使用rAF推迟代码

没有什么是万能的,面对上面的情况,我们需要对代码进行组织和优化。

看看下面这样一段代码:

function jank(second) {
    var start = +new Date();
    while (start + second * 1000 > (+new Date())) {}
}

p.style.backgroundColor = "red";

// some long run task
jank(5);

p.style.backgroundColor = "blue";

无论在任何的浏览器中运行上面的代码,你都不会看到p变为红色,页面通常会在假死5秒,然后容器变为蓝色。这是因为浏览器的始终只有一个线程在运行(可以这么理解,因为js引擎与UI引擎互斥)。虽然你告诉浏览器此时p背景颜色应该为红色,但是它此时还在执行脚本,无法调用UI线程。

有了这个前提,我们接下来看这段代码:

var p = document.getElementById("foo");

var currentWidth = p.innerWidth; 
p.style.backgroundColor = "blue";

// do some "long running" task, like sorting data

这个时候我们不仅仅需要更新背景颜色,还需要获取容器的宽度。可以想象它的执行顺序如下:

当我们请求innerWidth一类的属性时,浏览器会以为我们马上需要,于是它会立即更新容器的样式(通常浏览器会攒着一批,等待时机一次性的repaint,以便节省性能),并把计算的结果告诉我们。这通常是性能消耗量大的工作。

但如果我们并非立即需要得到结果呢?

上面的代码有两处不足,

  1. 更新背景颜色的代码过于提前,根据前一个例子,我们知道,即使在这里告知了浏览器我需要更新背景颜色,浏览器至少也要等到js运行完毕才能调用UI线程;

  2. 假设后面部分的long runing代码会启动一些异步代码,比如setTimeout或者Ajax请求又或者web-worker,那应该尽早为妙。

综上所述,如果我们不是那么迫切的需要知道innerWidth,我们可以使用rAF推迟这部分代码的发生:

requestAnimationFrame(function(){
    var el = document.getElementById("foo");

    var currentWidth = el.innerWidth;
    el.style.backgroundColor = "blue";

    // ...
});

// do some "long running" task, like sorting data

可见即使我们在这里没有使用到动画,但仍然可以使用rAF优化我们的代码。执行的顺序会变成:

在这里rAF的用法变成了:把代码推迟到下一帧执行。

有时候我们需要把代码推迟的更远,比如这个样子:

再比如我们想要一个效果分两步执行:1.p的display变为block;2. p的top值缩短移动到某处。如果这两项操作都放入同一帧中的话,浏览器会同时把这两项更改应用于容器,在同一帧内。于是我们需要两帧把这两项操作区分开来:

requestAnimationFrame(function(){
   el.style.display = "block";
   requestAnimationFrame(function(){
      // fire off a CSS transition on its `top` property
      el.style.top = "300px";
   });
});

这样的写法好像有些不太讲究,Kyle Simpson有一个开源项目h5ive,它把上面的用法封装了起来,并且提供了API。实现起来非常简单,摘一段代码瞧瞧:

function qID(){
    var id;
    do {
        id = Math.floor(Math.random() * 1E9);
    } while (id in q_ids);
    return id;
}

function queue(cb) {
    var qid = qID();

    q_ids[qid] = rAF(function(){
        delete q_ids[qid];
        cb.apply(publicAPI,arguments);
    });

    return qid;
}

function queueAfter(cb) {
    var qid;

    qid = queue(function(){
        // do our own rAF call here because we want to re-use the same `qid` for both frames
        q_ids[qid] = rAF(function(){
            delete q_ids[qid];
            cb.apply(publicAPI,arguments);
        });
    });

    return qid;
}

使用方法:

// 插入下一帧
id1 = aFrame.queue(function(){
    text = document.createTextNode("##");
    body.appendChild(text);
});

// 插入下下一帧
id2 = aFrame.queueAfter(function(){
    text = document.createTextNode("!!");
    body.appendChild(text);
});

使用rAF解耦代码

先从一个2011年twitter遇到的bug说起。

当时twitter加入了一个新功能:“无限滚动”。也就是当页面滚至底部的时候,去加载更多的twitter:

$(window).bind(&#39;scroll&#39;, function () {
    if (nearBottomOfPage()) {
        // load more tweets ...
    }
});

但是在这个功能上线之后,发现了一个严重的bug:经过几次滚动到最底部之后,滚动就会变得奇慢无比。

经过排查发现,原来是一条语句引起的:$details.find(“.details-pane-outer”);

这还不是真正的罪魁祸首,真正的原因是因为他们将使用的jQuery类库从1.4.2升级到了1.4.4版。而这jQuery其中一个重要的升级是把Sizzle的上下文选择器全部替换为了querySelectorAll。但是这个接口原实现使用的是getElementsByClassName。虽然querySelectorAll在大部分情况下性能还是不错的。但在通过Class名称选择元素这一项是占了下风。有两个对比测试可以看出来:1.querySelectorAll v getElementsByClassName 2.jQuery Simple Selector

通过这个bug,John Resig给出了一条(实际上是两条,但是今天只取与我们话题有关的)非常重要的建议

It’s a very, very, bad idea to attach handlers to the window scroll event.

他想表达的意思是,像scroll,resize这一类的事件会非常频繁的触发,如果把太多的代码放进这一类的回调函数中,会延迟页面的滚动,甚至造成无法响应。所以应该把这一类代码分离出来,放在一个timer中,有间隔的去检查是否滚动,再做适当的处理。比如如下代码:

var didScroll = false;

$(window).scroll(function() {
    didScroll = true;
});

setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        // Check your page position and then
        // Load in more results
    }
}, 250)

这样的作法类似于Nicholas将需要长时间运算的循环分解为“片”来进行运算:

// 具体可以参考他写的《javascript高级程序设计》
// 也可以参考他的这篇博客: http://www.php.cn/
function chunk(array, process, context){
    var items = array.concat();   //clone the array
    setTimeout(function(){
        var item = items.shift();
        process.call(context, item);

        if (items.length > 0){
            setTimeout(arguments.callee, 100);
        }
    }, 100);
}

原理其实是一样的,为了优化性能、为了防止浏览器假死,将需要长时间运行的代码分解为小段执行,能够使浏览器有时间响应其他的请求。

回到rAF上来,其实rAF也可以完成相同的功能。比如最初的滚动代码是这样:

function onScroll() {
    update();
}

function update() {

    // assume domElements has been declared
    for(var i = 0; i < domElements.length; i++) {

        // read offset of DOM elements
        // to determine visibility - a reflow

        // then apply some CSS classes
        // to the visible items - a repaint

    }
}

window.addEventListener(&#39;scroll&#39;, onScroll, false);

这是很典型的反例:每一次滚动都需要遍历所有元素,而且每一次遍历都会引起reflow和repaint。接下来我们要做的事情就是把这些费时的代码从update中解耦出来。

首先我们仍然需要给scroll事件添加回调函数,用于记录滚动的情况,以方便其他函数的查询:

var latestKnownScrollY = 0;

function onScroll() {
    latestKnownScrollY = window.scrollY;
}

接下来把分离出来的repaint或者reflow操作全部放入一个update函数中,并且使用rAF进行调用:

function update() {
    requestAnimationFrame(update);

    var currentScrollY = latestKnownScrollY;

    // read offset of DOM elements
    // and compare to the currentScrollY value
    // then apply some CSS classes
    // to the visible items
}

// kick off
requestAnimationFrame(update);

其实解耦的目的已经达到了,但还需要做一些优化,比如不能让update无限执行下去,需要设标志位来控制它的执行:

var latestKnownScrollY = 0,
    ticking = false;

function onScroll() {
    latestKnownScrollY = window.scrollY;
    requestTick();
} 

function requestTick() {
    if(!ticking) {
        requestAnimationFrame(update);
    }
    ticking = true;
}

并且我们始终只需要一个rAF实例的存在,也不允许无限次的update下去,于是我们还需要一个出口:

function update() {
    // reset the tick so we can
    // capture the next onScroll
    ticking = false;

    var currentScrollY = latestKnownScrollY;

    // read offset of DOM elements
    // and compare to the currentScrollY value
    // then apply some CSS classes
    // to the visible items
}

// kick off - no longer needed! Woo.
// update();

理解Layer

Kyle Simpson说:

Rule of thumb: don’t do in JS what you can do in CSS.

如以上所说,即使使用rAF,还是会有诸多的不便。我们还有一个选择是使用css动画:虽然浏览器中UI线程与js线程是互斥,但这一点对css动画不成立。

在这里不聊css动画的用法。css动画运用的是什么原理来提升浏览器性能的。

首先我们看看淘宝首页的焦点图:

我想提出一个问题,为什么明明可以使用translate 2d去实现的动画,它要用3d去实现呢?

Ich bin kein Taobao-Mitarbeiter, aber meine erste Vermutung ist, dass der Grund dafür die Verwendung des Translate3d-Hacks ist. Einfach ausgedrückt: Wenn Sie das Attribut -webkit-transform: TranslateZ(0); oder -webkit-transform: Translate3d(0,0,0); hinzufügen, weisen Sie den Browser an, die GPU zum Rendern zu verwenden Elementebenen, wodurch Geschwindigkeit und Leistung im Vergleich zum regulären CPU-Rendering verbessert werden. (Ich bin mir ziemlich sicher, dass dadurch die Hardwarebeschleunigung in Chrome aktiviert wird, aber auf anderen Plattformen gibt es keine Garantie. Soweit ich die Informationen erhalten habe, ist es auch in den meisten Browsern wie Firefox und Safari anwendbar.)

Aber diese Aussage ist tatsächlich nicht korrekt, zumindest in der aktuellen Version von Chrome handelt es sich hierbei nicht um einen Hack. Denn standardmäßig durchlaufen alle Webseiten beim Rendern die GPU. Ist das also noch notwendig? haben. Bevor Sie das Prinzip verstehen, müssen Sie zunächst das Konzept einer Ebene verstehen.

HTML wird im Browser in einen DOM-Baum konvertiert, und jeder Knoten des DOM-Baums wird in ein RenderObject umgewandelt. Mehrere RenderObjects können einem oder mehreren RenderLayer entsprechen. Der Prozess des Browser-Renderings ist wie folgt:

  1. Holen Sie sich das DOM und teilen Sie es in mehrere Ebenen auf (RenderLayer)

  2. Rastern Sie jede Ebene und Zeichnen Sie die Bitmaps unabhängig voneinander

  3. Laden Sie diese Bitmaps als Texturen auf die GPU hoch

  4. Fügen Sie mehrere Ebenen zusammen, um das endgültige Bildschirmbild (ultimative Ebene) zu generieren.

Dies ähnelt der 3D-Darstellung im Spiel. Obwohl wir einen dreidimensionalen Charakter sehen, ist die Haut dieses Charakters aus verschiedenen Bildern „zusammengefügt“ und „zusammengefügt“. von. Die Webseite hat einen Schritt mehr als diesen, und obwohl die endgültige Webseite aus mehreren Bitmap-Ebenen besteht, ist das, was wir sehen, nur eine Kopie, und letztendlich gibt es nur eine Ebene. Natürlich können einige Ebenen nicht kombiniert werden, z. B. Flash. Am Beispiel einer Wiedergabeseite von iQiyi (http://www.php.cn/) können wir das Ebenenbedienfeld von Chrome (standardmäßig nicht aktiviert und muss manuell aktiviert werden) verwenden, um alle Ebenen auf der Seite anzuzeigen:

Wir können sehen, dass die Seite aus den folgenden Ebenen besteht:

OK, dann kommt hier die Frage.

Angenommen, ich möchte jetzt den Stil eines Containers ändern (kann als Animationsschritt angesehen werden) und im schlimmsten Fall seine Länge und Breite ändern – warum ist es am besten, die Länge und Breite zu ändern? Was für eine schlimme Situation. Normalerweise erfordert das Ändern des Stils eines Objekts die folgenden vier Schritte:

Jede Attributänderung führt dazu, dass der Browser den Stil des Containers neu berechnet, beispielsweise wenn Sie die Größe ändern des Containers oder der Position (Reflow), dann ist das erste, was beeinflusst wird, die Größe und Position des Containers (dies wirkt sich auch auf die Position benachbarter Knoten des zugehörigen übergeordneten Knotens usw. aus), und dann muss der Browser den Container neu zeichnen ; aber wenn Sie nur die Hintergrundfarbe des Containers und andere Eigenschaften ändern, die nichts mit der Größe des Containers zu tun haben, dann sparen Sie sich die Zeit der Positionsberechnung im ersten Schritt. Mit anderen Worten: Wenn mit den Attributänderungen früher (je weiter oben) im Wasserfalldiagramm begonnen wird, sind die Auswirkungen größer und die Effizienz geringer. Reflow und Repaint führen dazu, dass die Bitmaps der Ebene, in der sich alle betroffenen Knoten befinden, neu gezeichnet werden und der obige Prozess wiederholt ausgeführt wird, was zu einer verringerten Effizienz führt.

Um die Kosten zu minimieren, ist es natürlich am besten, nur den Compositing-Layer-Schritt zu belassen. Angenommen, wenn wir den Stil eines Containers ändern, wirkt sich dieser nur auf sich selbst aus und es besteht keine Notwendigkeit, ihn neu zu zeichnen. Wäre es nicht besser, den Stil direkt durch Ändern der Texturattribute in der GPU zu ändern? Dies ist natürlich erreichbar, vorausgesetzt, Sie haben eine eigene Ebene

Dies ist auch das Prinzip des oben genannten Hardwarebeschleunigungs-Hacks und auch das Prinzip der CSS-Animation: Erstellen Sie Ihre eigene Ebene für das Element, anstatt für die meisten anderen der Elemente auf der Seite Elemente teilen sich eine Ebene.

Welche Elemente können eine eigene Ebene erstellen? In Chrome muss mindestens eine der folgenden Bedingungen erfüllt sein:

  • Ebene verfügt über 3D- oder Perspektivtransformations-CSS-Eigenschaften (Eigenschaften mit 3D-Elementen)

  • Die Ebene wird von einem 39000f942b2545a5315c57fa3276f220-Element mit beschleunigter Videodekodierung verwendet (Video-Tag und beschleunigte Videodekodierung verwenden).

  • Die Ebene wird von einem 5ba626b379994d53f7acf72a64f9b697-Element mit einem 3D-Kontext verwendet oder beschleunigter 2D-Kontext (Canvas-Element und 3D aktiviert)

  • Ebene wird für ein zusammengesetztes Plugin (Plug-In, z. B. Flash) verwendet

  • Layer verwendet eine CSS-Animation für seine Deckkraft oder verwendet eine animierte Webkit-Transformation (CSS-Animation)

  • Layer verwendet beschleunigte CSS-Filter (CSS-Filter)

  • Eine Ebene mit einem zusammengesetzten Nachkommen enthält Informationen, die im Baum der zusammengesetzten Ebene enthalten sein müssen, z. B. ein Clip oder eine Spiegelung (es gibt ein Nachkommenelement, das eine unabhängige Ebene ist)

  • Ebene hat ein Geschwisterelement mit einem niedrigeren Z-Index, das über eine zusammengesetzte Ebene verfügt (mit anderen Worten, die Ebene wird über einer zusammengesetzten Ebene gerendert) (benachbarte Elemente eines Elements sind unabhängige Ebenen)

Offensichtlich erfüllen das, was wir gerade gesehen haben: Der Blitz auf der Wiedergabeseite und das Fokusbild mit aktiviertem Translate3D-Stil die oben genannten Bedingungen.

同时你也可以勾选Chrome开发工具中的rendering选显卡下的Show composited layer borders 选项。页面上的layer便会加以边框区别开来。为了验证我们的想法,看下面这样一段代码:

<html>
<head>
  <style type="text/css">
  p {
      -webkit-animation-duration: 5s;
      -webkit-animation-name: slide;
      -webkit-animation-iteration-count: infinite;
      -webkit-animation-direction: alternate;
      width: 200px;
      height: 200px;
      margin: 100px;
      background-color: skyblue;
  }
  @-webkit-keyframes slide {
      from {
          -webkit-transform: rotate(0deg);
      }
      to {
          -webkit-transform: rotate(120deg);
      }
  }
  </style>
</head>
<body>
  <p id="foo">I am a strange root.</p>
</body>
</html>

运行时的timeline截图如下:

可见元素有自己的layer,并且在动画的过程中没有触发reflow和repaint。

最后再看看淘宝首页,不仅仅只有焦点图才拥有了独立的layer:

但太多的layer也未必是一件好事情,有兴趣的同学可以看一看这篇文章:Jank Busting Apple’s Home Page。看一看在苹果首页太多layer时出现的问题。

 以上就是Javascript高性能动画与页面渲染的内容,更多相关内容请关注PHP中文网(www.php.cn)!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn