Learning material for web and rendering performance optimization.
Goal 1: Optimize Test page: Janky Animation to cheaper animation
Goal 2: A measurable performance (before/after) optimization in https://github.com/jouni-kantola/grid-snake
- Get Started With Analyzing Runtime Performance (added: 2021-03-03)
- Rendering Performance (added: 2021-03-15)
- Test page: Janky Animation (added: 2021-03-22)
- How browser rendering works β behind the scenes (added: 2021-03-29)
- The Anatomy of a Frame
- Critical Rendering Path (added: 2021-03-29)
- What forces layout / reflow (added: 2021-03-03)
- CSS Triggers (added: 2021-03-03)
- Performance Analysis Reference (added: 2021-03-15)
Question | Answer |
---|---|
How should I defined performance? | Art of doing less to produce equal or better results |
What is a frame? | Naive explanation: Single picture, in a sequence of images |
What is rendering? | Drawing an image to the screen |
What is FPS? | Non-linear metric to show how many renders are made per second |
What is jank? | When frame rate drops noticeably, causing content to judder on screen, because screen updates aren't done fast enough |
What is good FPS? | Follow device refresh rate. Normally about 16 ms (1 second / 60 frames = 16.66ms). It's really stricter because of housekeeping work the browser does, so budget is rather 10 ms |
What is pixel-to-screen pipeline? | The steps taken in the process of rendering updates to screen |
What is rasterization? | TBA |
What is a render tree? | TBA |
What is debouncing? | TBA |
How do I schedule a requestAnimationFrame visual change batch (debounce)? |
TBA |
- Run runtime performance audits in incognito mode, to not be affected by browser extensions
- Undock DevTools to all charts in the Performance tab
- Simulate a slow CPU to clearly detect bottlenecks
- Open FPS meter and check % of frames successfully rendered
- Check CPU use in Performance Monitor (
ctrl
+shift
+P
:Show Performance monitor
) - Run audit and looks for red lines in the FPS chart. Compare below.
Better, CPU idle now and then:
- In Summary tab, check what process occupies main thread the most.
- In the Main panel, find activities with "red triangles", which signals events with issues. Zoom in on those.
- Dig deeper, find which specific events are causing problems. In the
Summary
, find which code blocks affect performance.
- Measure Style Recalculation Cost, by finding how many elements are affected by a style recalculation.
-
Compare with What forces layout / reflow and CSS Triggers to find better solution.
-
For animations or modifying DOM nodes, use tool
Show paint flashing rectangles
to verify only expected elements are painted. -
Long running tasks, where the UI thread is busy for >50 ms, can be observed with the Long Task API
const observer = new PerformanceObserver(list => {
const entries = list.getEntries();
console.warn("Long Task", entries);
});
observer.observe({ entryTypes: ["longtask"] });
The work required for visual changes. The less work, the cheaper the process. When optimizing it's preferable to do as few steps as possible in the Pixel-to-Screen pipeline.
π’ Change element's geometry (e.g. width): JavaScript / CSS > Style > Layout > Paint > Composite
π Change paint only property (e.g. background): JavaScript / CSS > Style > Paint > Composite
π Change composite only property (e.g. transform): JavaScript / CSS > Style > Composite
Adding, removing or modifying DOM elements trigger visual change. Triggered by JavaScript, CSS animations or CSS transitions.
Recalculating DOM element's styles are called computed style calculation. This is done in two steps:
- The browser figures out what selectors apply to a given element
- Take style rules for given element and assess what updated styles are
As changes to a specific element may affect others, the browser needs to consider parent and child nodes when an element's size and position is calculated. If an element's geometry is changed, the browser needs to reflow the page.
Painting, often the most expensive part of the pipeline, is the process of "filling in the pixels". Paint consists of two tasks:
- Create a list of draw calls
- Rasterization, the process of filling in/drawing visual parts of an element
Handle order of drawing layers, so that page renders correctly. Put together painted parts for displaying on screen.
Find the bottlenecks before optimizing; micro-optimizations do not pay off in the end!
- π Use
requestAnimationFrame
(start of the frame) instead ofsetTimeout
orsetInterval
(some point in the frame) for visual changes. - π Avoid forced synchronous layouts ("layout thrashing"). Batch style reads together, to reuse the previous frame's layout values. Once calculations are performed, then perform writes.
- π‘ For large operations that require DOM access, batch work in separate
requestAnimationFrame
tasks. This may require status indicators to clearly signal a long running process. Worth noting, poor implementation of tasks results in "laggy" visuals (without clearly showing in performance debugger). - π΄ Input handlers are scheduled before
requestAnimationFrame
(rAF
). If style writes are done in input handlers and then read inrAF
this causes layout trashing. To not block the compositor thread, batch changes together by debouncing to nextrAF
callback. - π· Move computational work to web workers to offload main thread and prevent blocking visual updates.
- Don't focus on CSS as a bottle neck when it comes to selectors, but prefer more specific selectors to affect as few elements as possible when style calculations are applied.
- In animations, prefer
transform
(and/oropacity
) to avoid both Layout and Paint. - GPU acceleration via layer compositioning can be utilized to improve performance.
will-change: transform
ortransform: translateZ(0)
promotes a layer, and thereby reduces paint areas (and affects fewer elements) in animations. Use with care and profile to check the overhead doesn't actually decrease performance.
- Microsoft Edge has a 3D View to show page layers. Activate with
Shift+Ctrl+P
and toggle Show 3D View
- 2021-03-03: Find study material
- 2021-03-04: Goal 1 defined
- 2021-03-04: Defined Q&A section
- 2021-03-15: Defined checklist section
- 2021-03-15: Cleared tutorial https://www.youtube.com/watch?v=7HSkc9TLF5U and https://developers.google.com/web/tools/chrome-devtools/evaluate-performance
- 2021-03-15: Defined next tutorial to go through
- 2021-03-22: Defined section to describe the pixel-to-screen pipeline
- 2021-03-22: Added section for what to think about when optimizing JavaScript
- 2021-03-22: Added section for CSS optimizations
- 2021-03-22: Updated goals to use the Test Page Demo before moving on to unkown territory
- 2021-03-22: Read https://developers.google.com/web/fundamentals/performance/rendering, https://developers.google.com/web/fundamentals/performance/rendering/optimize-javascript-execution and https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations
- 2021-03-29: Read https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing
- 2021-03-29: Included further study material to understand how browser rendering works
- 2021-04-07: Start looking for tooling in other browsers
- 2021-04-07: Finished Rendering Performance course
- 2021-04-13: Forked exercise devtools-samples/jank
- 2021-04-13: Put reading to practice
- 2021-04-19: Start looking for bottlenecks in Grid Snake
- 2021-04-26: Programmatically observe long running tasks