/reflow-workshop

Coding materials for my workshop about `Reflow & Repaint events`

Primary LanguageHTML

reflow-workshop

Coding materials for my workshop about "Reflow & Repaint events".


1 - Rendering pipeline

Javascript > Style > Layout > Paint > Composite

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 !!!)

2 - What are restyle, reflow, repaint events

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

3 - Why are they slow

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

4 - Workshop - part 1

Target: Draw balls moving horizontally, smoothly with 60FPS. More balls are better. Workshop - Part 1 - Screenshot

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 of position -> 1K balls (replace Repaint by Restyle, Introduce sessions 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 with position:absolute will not trigger many reflow events
  • Put restyle, reflow, repaint commands in requestAnimationFrame instead of setInterval/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 using DocumentFragment
  • Generate many elements by innerHTML & cloneNode

Some more improvements / experiments:

  • Find & fix memory leaks
  • Re-use tops[m] to reduce tops.length
  • Create default movers as hidden ones. Check branch feature/animate-position/v8-advanced for more details.
  • Use ArrayBuffer to store movers
  • Promote #inputContainer into a separate layer using transform
  • Disable each CSS in .mover to measure which style is heavy (try border-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


5 - Workshop - part 2

Target: Improve balls drawing with opacity and transform to mimic 3D space Workshop - Part 2 - Screenshot

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 !!!)

6 - Workshop - part 3

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

7 - List of properties and functions forcing reflow

JS: https://gist.github.com/paulirish/5d52fb081b3570c81e3a

CSS: https://csstriggers.com/


8 - More references

Presentations:

Documents hub:

Advanced profiling tools: