Coding materials for my workshop about "Reflow & Repaint events".
Javascript: Execute JS code to interact with DOM tree
Style: Calculate style in CSSOM tree to apply to DOM tree
Layout: Calculate position, size to arrange elements in the web page
Paint: Draw elements into layers (separated by 3D attributes or manually)
Composite: Apply transform to layers (without re-painting !!!)
Recomposite: Changing opacity & transform requests GPU to apply effects to rendered textures
Repaint: Changing color, border radius, shadow ... requests browser to re-paint element (then re-composite)
Reflow: Changing position, size ... requests browser to re-arrange / re-layout ALL RELATED elements, THEN re-paint & re-composite them
Restyle: Changing CSS atributes in general, leadaing to ONE of following events: reflow, repaint, recomposite
One element changes in layout forces browser to reflow before processing next requests
Many element changes ? -> Repeat forcing reflow (-> repaint & recomposite)
.
Add / remove a DOM node ? Reflow + Repaint
Hide element by 'display:none' ? Reflow + Repaint
Hide element by 'visibility:hidden' ? Repaint
Hide element by 'transform:scale(0)' ? None
Target: Draw balls moving horizontally, smoothly with 60FPS. More balls are better.
File: `index.html`
Screenshots: `screenshots/*.png`
Check commit messages for more details.
- v0: setInterval -> Unstable frame rate with 100 balls (Introduce Chrome Devtools, FPS meter, tab Performance)
- v1: requestAnimationFrame -> 100 balls but still force reflow
- v2: separate get & set style -> 600 balls
- v3: cache offsetTop -> 800 balls
- v4: use
transform
instead ofposition
-> 1K balls (replace Repaint by Restyle, Introducesessions
in tab Performance) - v5: apply culling -> 12K balls !!! (Tab
Layer
, use "Camera" for visual debugging) - v6: hide elements by moving to a hidden node -> 80K with hiccup (DOM GC)
- v6b: batch showing elements by DocumentFragment -> better but it seems GC appears more often
- v7: pre-calculate visible index range -> 140K balls, and split functions in profiler
- v7b: separate updating position and display ? nothing improved -> ignore
- v8: only update dirty elements by comparing index range -> 1M balls !!!
Lessons:
top/left
withposition:absolute
will not trigger many reflow events- Put restyle, reflow, repaint commands in
requestAnimationFrame
instead ofsetInterval/setTimeout
for a stable frame rate - Group restyle, reflow repaint commands to make them trigger all at once. Check FastDOM for explaination.
- Optimize more and more if you can (culling, dirty techniques).
- Profiler is your friend. Trust your tools, not someone's rule, because browser is changing quickly.
- Batch
appendChild
to DOM usingDocumentFragment
- Generate many elements by
innerHTML
&cloneNode
Some more improvements / experiments:
- Find & fix memory leaks
- Re-use
tops[m]
to reducetops.length
- Create default
movers
as hidden ones. Check branchfeature/animate-position/v8-advanced
for more details. - Use
ArrayBuffer
to store movers - Promote
#inputContainer
into a separate layer usingtransform
- Disable each CSS in
.move
r to measure which style is heavy (tryborder-radius
,radial-gradient
)
Since v8 will not be affected by number of balls anymore:
- Script: Number of balls change each frame <= 2 * max-visible-balls
- Restyle: Number of balls move = max-visible-balls
- Reflow: Number of new visible/hidden balls <= 2 * max-visible-balls
- Repaint: Number of visible balls is almost fixed
=> No need to improve more
Target: Improve balls drawing with opacity and transform to mimic 3D space
File: `index_3d.html`
Screenshots: `screenshots/animate-3d/*.png`
Check commit messages for more details.
- v8.1: Animate 'width' & 'height' -> 200 balls / 15 FPS (unstable)
- v8.1b: Use 'cssText' for batching changes -> not improve (+ Performance Monitor)
- v8.2: Use 'transform' ('translate' & 'scale') to animate -> 100K balls / 60FPS
- v8.2b: Add 'opacity: 0.8' -> 200 balls / 30FPS (unstable)
- v8.3: Add 'will-change' -> 1K balls / 60FPS
- v8.4: Animate 'opacity' -> 1K balls / 60FPS
Check 'layer borders' option and you will see that each ball is in a separate layer (orange border), this is why FPS is low.
More specific, open `Layers` tab and each .mover is in a layer with `Compositing reasons` = `willChange`.
Go back to v8 and check layer borders, they are cyan -> many elements are combined into one single layer
Lessons:
- Best CSS attributes for animation: opacity, transform
will-change
separates element in its own layer (best for opacity, transform animation)- Some more improvements / experiments:
- CSS custom properties (force separate paint each ball !!!)
Target: Replace JS animation by CSS animation
File: `index_3d_css.html`
Screenshots: `screenshots/animate-3d-css/*.png`
Check commit messages for more details.
- v9.0: Move 1 ball by CSS
- v9.1: Generate many balls
- v9.2: Add delay to each ball
- v9.3: Pause
- v9.4: Apply culling
- v9.5: Dynamic CSS animation
Lessons:
- CSS animations are useful and handy for simple animations
- CSS animations start immediately when assigned class, or becoming visible
- It is hard to control and optimize CSS animations (time, delay, dynamic parameter like position ... ), but we can try CSS custom var
JS: https://gist.github.com/paulirish/5d52fb081b3570c81e3a
Presentations:
Documents hub:
- http://jankfree.org/
- https://developers.google.com/web/
- https://developers.google.com/web/fundamentals/codelabs/web-perf/
- https://developers.google.com/web/fundamentals/performance/rendering/
- https://developers.google.com/web/tools/chrome-devtools/rendering-tools/
- http://wilsonpage.co.uk/preventing-layout-thrashing/
- https://www.igvita.com/slides/2012/web-performance-for-the-curious/#29
- https://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome
Advanced profiling tools: