Skip to content
AstroPaper
Go back

Layout / Paint / Composite / Layers — ULTIMATE Deep-Dive Guide

Updated:
Edit page

Layout / Paint / Composite / Layers — ULTIMATE Deep-Dive Guide

Complete engineering guide for browser rendering internals: layout calculation, paint operations, GPU compositing, rendering layers, frame production, and performance optimization — from beginner concepts to browser-engine-level mental models.


Table of Contents

Open Table of Contents

1. Big Picture

What Are Layout, Paint, and Compositing?

Layout (aka “reflow”) calculates the exact position and size of every element in the render tree. It answers: “Where does each box go, and how big is it?”

Paint fills in pixels — colors, text, images, borders, shadows. It produces draw commands (display lists) that describe what to render.

Compositing assembles painted layers into the final frame using the GPU. Layers are independently rasterized textures composited together.

Rendering layers are the browser’s mechanism for isolating parts of the page into independent GPU textures, enabling efficient updates and GPU-accelerated animations.

Why Browsers Split Rendering Into Stages

HTML/CSS/JS Input


┌─────────────────────────────────────────────────────────────┐
│  Parse HTML → Build DOM → Build CSSOM → Compute Styles      │
│       │                                                      │
│       ▼                                                      │
│  Build Render Tree → Layout → Paint → Rasterize → Composite │
│                                                              │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌──────────────┐   │
│  │  CPU    │  │  CPU    │  │CPU/GPU  │  │    GPU       │   │
│  │ Main    │  │ Main    │  │ Raster  │  │ Compositor   │   │
│  │ Thread  │  │ Thread  │  │ Threads │  │ Thread       │   │
│  └─────────┘  └─────────┘  └─────────┘  └──────────────┘   │
└─────────────────────────────────────────────────────────────┘


   Display (VSync)

Why stages? Separation enables:

Rendering Lifecycle

HTML Parsing
    → DOM Construction
        → CSSOM Construction
            → Style Calculation (computed styles)
                → Layout (geometry: position + size)
                    → Paint (display list generation)
                        → Rasterization (pixels from display lists)
                            → Compositing (layer assembly on GPU)
                                → Frame Presentation (vsync)

Key Comparisons

ConceptLayoutPaintComposite
WhatCalculate geometryFill pixelsAssemble layers
WhereMain thread (CPU)Main thread → raster threadsCompositor thread (GPU)
CostO(n) elements affectedProportional to paint areaCheap (GPU texture ops)
TriggerWidth, height, position changesColor, shadow, visibility changesTransform, opacity changes
InvalidationCascades to descendantsPer-layerPer-layer (cheapest)
ComparisonExplanation
Repaint vs ReflowReflow = layout recalculation (expensive, cascades). Repaint = redraw pixels without geometry change.
CPU vs GPU renderingCPU handles layout + paint (sequential). GPU handles compositing (massively parallel).
Software vs hardware accelerationSoftware = CPU rasterization. Hardware = GPU rasterization + compositing.
Main thread vs compositor threadMain thread runs JS + layout + paint. Compositor thread composites layers independently — can animate without waiting for JS.

How Invalidation Works

DOM Mutation (e.g., element.style.width = '200px')


Style Invalidation → recalculate computed styles


Layout Invalidation → recalculate positions/sizes of affected subtree


Paint Invalidation → regenerate display list for affected layers


Compositing Update → re-composite affected layers


Frame Commit → present to screen at next vsync

Critical insight: Each stage can short-circuit. If you only change opacity, the browser skips layout and paint entirely — only compositing runs.

How Frames Are Produced

┌──────────────────── 16.67ms Frame Budget (60fps) ────────────────────┐
│                                                                       │
│  [JS] → [Style] → [Layout] → [Paint] → [Raster] → [Composite]      │
│  ~5ms    ~1ms      ~3ms        ~2ms      ~3ms       ~1ms             │
│                                                                       │
│  If total > 16.67ms → DROPPED FRAME (jank)                           │
└───────────────────────────────────────────────────────────────────────┘

2. Browser Rendering Pipeline Deep Dive

Pipeline Stages in Detail

DOM Tree

CSSOM Tree

Render Tree

Style Calculation

Layout Calculation

Paint Phases

Blink paints in a specific order per stacking context:

  1. Background
  2. Float
  3. Foreground (content)
  4. Outline
  5. Overlay (selection, caret)

Display Lists

Rasterization

Compositing Pipeline

Paint → Display Lists → Rasterize Tiles → Upload Textures → Composite


                                                          GPU draws quads
                                                          for each layer


                                                           Frame buffer


                                                             Display

Tile Rendering

GPU Textures

Frame Scheduling

Rendering Invalidation & Partial Updates

Not every DOM change reruns the full pipeline:

ChangeStages Run
element.textContent = 'x'Style → Layout → Paint → Composite
element.style.color = 'red'Style → Paint → Composite
element.style.transform = '...'Composite only
element.style.opacity = '0.5'Composite only
element.style.width = '100px'Style → Layout → Paint → Composite
┌─────────────────────────────────────────────────────┐
│ Main Thread                                          │
│  ├─ DOM + Style                                     │
│  ├─ LayoutNG (layout tree → fragment tree)          │
│  ├─ Paint (display lists via PaintArtifact)         │
│  └─ Commit to compositor                            │
├─────────────────────────────────────────────────────┤
│ Compositor Thread (impl thread)                      │
│  ├─ Layer tree management                           │
│  ├─ Tile management                                 │
│  ├─ Animation ticking                              │
│  └─ Draw frame (produce compositor frame)           │
├─────────────────────────────────────────────────────┤
│ Raster Worker Threads (GPU process or CPU)           │
│  └─ Rasterize tiles from display lists              │
├─────────────────────────────────────────────────────┤
│ GPU Process / Viz (Display Compositor)               │
│  └─ Final compositing + present to screen           │
└─────────────────────────────────────────────────────┘

WebKit Differences

Gecko Differences

Mobile Rendering Differences


3. Layout Deep Dive

What Layout Calculates

Layout determines:

Layout Algorithms

Flow Layout (Block + Inline)

Flexbox Layout

Grid Layout

Layout Invalidation

element.style.width = '200px'


Mark element as needing layout (dirty flag)


Walk up to find layout root (nearest element that doesn't
affect its parent's size)


Layout from root downward through dirty subtree

Layout boundaries — elements that contain their layout effects:

Layout Thrashing

Layout thrashing occurs when you interleave reads and writes:

// BAD — forces synchronous layout on every iteration
for (const el of elements) {
  const height = el.offsetHeight; // READ → forces layout
  el.style.height = height * 2 + 'px'; // WRITE → invalidates layout
}

// GOOD — batch reads, then batch writes
const heights = elements.map((el) => el.offsetHeight); // All reads
elements.forEach((el, i) => {
  el.style.height = heights[i] * 2 + 'px'; // All writes
});

Properties that force synchronous layout (forced reflow):

Why Layout Is Expensive

  1. Cascading — parent size change can invalidate all children
  2. Multiple passes — flex/grid may need 2-3 passes
  3. Intrinsic sizing — requires measuring content to determine container size
  4. Large DOM — more nodes = more work (O(n) dirty nodes)
  5. Deep nesting — layout propagates up and down

Containing Blocks & Stacking Contexts

Layout Optimization Techniques

TechniqueEffect
contain: layoutCreates layout boundary — changes don’t propagate out
contain: sizeElement’s size is independent of children
content-visibility: autoSkips layout for off-screen content
Fixed dimensionsPrevents intrinsic sizing
Avoid table layoutTables require multiple layout passes
Batch DOM reads/writesPrevents layout thrashing
requestAnimationFrameCoalesce writes before next frame
CSS containmentcontain: strict for maximum isolation

DevTools Layout Debugging

  1. Performance panel → look for purple “Layout” bars
  2. Forced reflow warnings → console shows synchronous layout
  3. Layout Shift regions → rendering panel highlights shifts
  4. Performance insights → identifies layout thrashing patterns

4. Paint Deep Dive

What Paint Produces

Paint generates display lists — ordered sequences of draw commands:

Paint Invalidation

When a visual property changes, the browser marks the affected paint chunk as dirty:

Paint Order

Within each stacking context, paint happens in this order:

  1. Background + borders of the element
  2. Negative z-index children
  3. Block-level non-positioned children
  4. Float children
  5. Inline-level non-positioned children
  6. z-index: 0 / positioned children
  7. Positive z-index children

Expensive Paint Operations

OperationWhy Expensive
box-shadow (large/spread)Gaussian blur computation, large paint area
filter: blur()Multi-pass convolution
backdrop-filterReads back framebuffer, applies filter, composites
Large border-radiusAnti-aliased curve rasterization
Complex gradientsPer-pixel interpolation
Text renderingGlyph lookup, hinting, subpixel AA
clip-path (complex)Path rasterization + clipping
Large paint areasMore pixels to fill

CSS Properties That Trigger Paint (but not layout)

Text Rendering Complexity

Text is one of the most expensive paint operations:

Paint Optimization

StrategyEffect
Promote animated elements to own layerIsolate repaints
Reduce paint areaSmaller invalidation region
Simplify visual effectsFewer draw commands
Avoid large box-shadowReduce blur computation
Use will-change sparinglyPromote to compositor layer
contain: paintPaint doesn’t overflow element bounds
Prefer transform over visual property changesSkip paint entirely

DevTools Paint Debugging

  1. Rendering panel → Paint flashing — green highlights on repainted areas
  2. Performance panel → green “Paint” bars show paint duration
  3. Layers panel → shows paint counts per layer
  4. “Record paint” in Performance → captures display lists
  5. Profiler “Paint” event → shows paint reason and area

5. Composite & GPU Deep Dive

Why Compositing Exists

Without compositing: any visual change repaints the entire page. With compositing: the page is split into cached GPU textures (layers). Changes to one layer don’t affect others. The GPU assembles layers cheaply.

Compositor Thread Architecture

┌─────────────────────────────────────┐
│  Main Thread                         │
│  (JS, Style, Layout, Paint)         │
│         │                            │
│         │ commit (copy layer tree)   │
│         ▼                            │
├─────────────────────────────────────┤
│  Compositor Thread (impl)            │
│  • Owns copy of layer tree          │
│  • Ticks compositor animations      │
│  • Manages tiles                    │
│  • Produces compositor frames       │
│  • Handles scroll (no main thread!) │
│         │                            │
│         ▼                            │
├─────────────────────────────────────┤
│  GPU Process (Viz)                   │
│  • Receives compositor frames       │
│  • Executes GL/Vulkan/Metal calls   │
│  • Presents to display              │
└─────────────────────────────────────┘

Key insight: The compositor thread can animate transform and opacity WITHOUT involving the main thread. This is why these animations remain smooth even when JS is busy.

Layer Promotion Reasons

Browsers create compositing layers for elements that:

How GPU Compositing Works

Layer 1 (background)     → GPU Texture A
Layer 2 (main content)   → GPU Texture B
Layer 3 (animated element)→ GPU Texture C
Layer 4 (fixed header)   → GPU Texture D

Compositor combines:
  Draw Texture A at (0,0)
  Draw Texture B at (0, headerHeight)
  Draw Texture C at (x,y) with transform matrix
  Draw Texture D at (0,0) with opacity

→ Final framebuffer → Display

This is essentially texture mapping — the GPU’s specialty.

Why Transform Animations Are GPU-Friendly

/* GPU-composited animation — compositor thread only */
.animated {
  will-change: transform;
  transition: transform 0.3s;
}
.animated:hover {
  transform: translateX(100px);
}

Why Opacity Animations Are Performant

Tiled Compositing

Large layers are split into tiles:

GPU Memory Considerations

Each compositing layer costs:

VSync and Frame Scheduling

VSync Signal (every 16.67ms at 60Hz)

    ├─ Compositor checks: any pending animations?
    │   └─ Yes → tick animations, produce frame

    ├─ Main thread committed new content?
    │   └─ Yes → rasterize new tiles, composite

    └─ Nothing changed?
        └─ No frame produced (power saving)

Compositor-Thread vs Main-Thread Animations

PropertyThreadPerformance
transformCompositor✅ Excellent (GPU only)
opacityCompositor✅ Excellent (GPU only)
filter (on composited layer)Compositor⚠️ Good (GPU filter)
background-colorMain❌ Triggers paint
width / heightMain❌ Triggers layout + paint
top / leftMain❌ Triggers layout + paint

will-change Behavior

/* Tells browser to prepare a compositing layer */
.will-animate {
  will-change: transform;
}

/* Remove after animation completes */
.done-animating {
  will-change: auto;
}

Rules:

Chrome Layers Panel Workflow

  1. Open DevTools → More Tools → Layers
  2. See 3D visualization of all compositing layers
  3. Click layer to see:
    • Size (memory cost)
    • Compositing reason
    • Paint count
    • Whether it’s a scrolling layer
  4. Look for unexpected layers (implicit promotion from overlap)
  5. Check total layer count and memory

6. Rendering Layers & Layer Architecture

Layer TypePurpose
PaintLayerLogical grouping for paint order (stacking context)
CompositedLayerActual GPU texture for compositing
ScrollingLayerHandles scrollable content
ClipLayerManages overflow clipping

Stacking Context vs Compositing Layer

Stacking context = logical paint ordering concept:

Compositing layer = physical GPU texture:

One stacking context may contain multiple compositing layers, or multiple stacking contexts may share one compositing layer.

How Browsers Decide Layer Creation

Decision factors:

  1. Explicit promotion: will-change, transform: translateZ(0)
  2. Animation: actively animating compositor-friendly properties
  3. Overlap: element overlaps an existing compositing layer with higher z-index
  4. Special elements: <video>, <canvas>, <iframe>
  5. Scrolling: overflow scroll containers
  6. Fixed/sticky: position fixed or sticky elements

Layer Explosion Problem

/* Dangerous — each item becomes a layer because it overlaps
   the composited animated element */
.animated {
  will-change: transform;
  position: relative;
  z-index: 1;
}
.list-item {
  /* If list items overlap .animated, browser promotes them too */
  position: relative;
}

Symptoms:

Fix:

Scrolling & Layers

Scrollable containers get special treatment:

Fixed & Sticky Compositing

Layer Debugging Strategies

  1. Chrome Layers panel → count layers, check memory
  2. Rendering panel → Layer borders → orange = compositing layer
  3. Performance panel → look for “Update Layer Tree” events
  4. contain: strict → isolate rendering
  5. Rule of thumb: keep layers under 20-30 on mobile

7. Rendering Performance & 60FPS

Frame Budget

60 FPS = 1000ms / 60 = 16.67ms per frame
120 FPS = 1000ms / 120 = 8.33ms per frame

Actual budget (after browser overhead): ~10-12ms at 60fps

What Happens in a Frame

┌─ Frame Start (VSync) ─────────────────────────────────────┐
│                                                            │
│  1. Input events (touch, click, keyboard)    ~1-2ms       │
│  2. JavaScript (rAF callbacks, event handlers) ~3-5ms     │
│  3. Style calculation                         ~1-2ms      │
│  4. Layout                                    ~2-4ms      │
│  5. Paint                                     ~1-3ms      │
│  6. Composite                                 ~1-2ms      │
│                                                            │
│  Total must be < 16.67ms                                   │
│                                                            │
└─ Frame End ── Present to display ─────────────────────────┘

Jank Causes

CauseSymptomFix
Long JS executionBlocks entire pipelineBreak up work, use requestIdleCallback
Layout thrashingMultiple forced reflowsBatch reads/writes
Large paint areaSlow paint phasePromote to layer, reduce paint area
Too many layersSlow compositeReduce layer count
Large DOMSlow layout + styleVirtualize, content-visibility
Garbage collectionIntermittent spikesReduce allocations
Font loadingLayout shift + repaintfont-display: optional, preload

Input Responsiveness (INP)

120Hz Devices

Mobile Rendering Constraints

Performance Profiling Workflow

  1. Open Performance panel → Record
  2. Interact with page (scroll, click, animate)
  3. Stop recording
  4. Look for:
    • Long yellow bars (JS)
    • Purple bars (Layout) — especially “Forced reflow”
    • Green bars (Paint) — check paint area
    • Frame drops (gaps in frame timeline)
  5. Click events to see call stacks
  6. Use “Bottom-Up” to find expensive functions

8. CSS Properties & Rendering Cost

Composite-Only Properties (Cheapest)

These ONLY trigger compositing — no layout, no paint:

PropertyNotes
transformTranslate, rotate, scale, skew
opacityAlpha blending
filter (on composited layer)GPU-accelerated filters
backdrop-filterReads framebuffer (somewhat expensive)

Paint-Only Properties (Medium)

These trigger paint but NOT layout:

PropertyNotes
colorText color
background-colorBackground
background-imageBackground paint
border-colorBorder paint
border-styleBorder paint
box-shadowExpensive if large
outlinePaint only
visibilityPaint (hidden still takes space)
text-decorationText underline etc

Layout-Triggering Properties (Most Expensive)

These trigger layout → paint → composite:

PropertyNotes
width / heightBox size
paddingBox model
marginBox model
border-widthBox model
displayBox type
positionPositioning scheme
top/right/bottom/leftPositioned layout
floatFloat layout
font-sizeText layout
font-familyText layout
line-heightText layout
text-alignInline layout
overflowScroll containers
flex-*Flexbox layout
grid-*Grid layout

Animation Best Practices

/* ✅ GOOD — compositor only */
.animate-good {
  transition:
    transform 0.3s,
    opacity 0.3s;
}

/* ❌ BAD — triggers layout every frame */
.animate-bad {
  transition:
    width 0.3s,
    top 0.3s,
    left 0.3s;
}

/* ✅ GOOD — use transform instead of top/left */
.move-element {
  transform: translate(100px, 50px);
  /* NOT: top: 50px; left: 100px; */
}

/* ✅ GOOD — use scale instead of width/height */
.resize-element {
  transform: scale(1.5);
  /* NOT: width: 150%; height: 150%; */
}

Comparison Table

Use CaseBad (Layout)Good (Composite)
Move elementtop/lefttransform: translate()
Resizewidth/heighttransform: scale()
Show/hidedisplay/height: 0opacity + pointer-events
Fadevisibilityopacity
Shadow emphasisbox-shadow (changing)opacity on pseudo-element shadow

9. React / Next.js / Astro Rendering Implications

React Reconciliation & Rendering Pipeline

setState()
    → Reconciliation (virtual DOM diff)
        → Commit phase (DOM mutations)
            → Browser rendering pipeline
                → Style → Layout → Paint → Composite

Key insight: React batches state updates but when it commits DOM changes, those changes hit the browser pipeline synchronously. Large DOM updates = large layout/paint cost.

Why React Can Cause Layout Thrashing

// BAD — reading layout during render/effect causes thrashing
useEffect(() => {
  const rect = ref.current.getBoundingClientRect(); // Forces layout
  ref.current.style.width = rect.width * 2 + 'px'; // Invalidates layout
}, []);

// GOOD — use ResizeObserver or CSS
useEffect(() => {
  const observer = new ResizeObserver((entries) => {
    // Reads are batched by the observer
  });
  observer.observe(ref.current);
  return () => observer.disconnect();
}, []);

Hydration Rendering Cost

During hydration:

  1. React attaches event listeners (cheap)
  2. React may re-render mismatched content (expensive)
  3. Full page becomes interactive → large layout/paint spike
  4. Layout shifts if server HTML differs from hydrated output

Mitigation:

React Animation Architecture

// ✅ GOOD — compositor-only animation
function AnimatedCard({ isVisible }) {
  return (
    <div
      style={{
        transform: isVisible ? 'translateY(0)' : 'translateY(20px)',
        opacity: isVisible ? 1 : 0,
        transition: 'transform 0.3s, opacity 0.3s',
        willChange: 'transform, opacity',
      }}
    >
      <CardContent />
    </div>
  );
}

// ❌ BAD — layout-triggering animation
function BadAnimatedCard({ isVisible }) {
  return (
    <div
      style={{
        marginTop: isVisible ? 0 : 20, // Layout every frame!
        height: isVisible ? 'auto' : 0, // Layout every frame!
        transition: 'margin-top 0.3s, height 0.3s',
      }}
    >
      <CardContent />
    </div>
  );
}

Virtualization

For large lists/grids:

Rendering Strategy Comparison

StrategyLayout CostPaint CostHydration CostLCP
CSRDeferredDeferredNoneSlow
SSRFull on loadFull on loadFull pageFast
SSGFull on loadFull on loadFull pageFastest
Streaming SSRIncrementalIncrementalProgressiveFast
Islands (Astro)MinimalMinimalPer-islandFastest
Partial HydrationReducedReducedSelectiveFast

Next.js Specific Considerations

Astro Specific Considerations


10. Setup Guide

Chrome DevTools Rendering Tools

1. Enable Paint Flashing

DevTools → More tools → Rendering → Paint flashing ✓

Green rectangles flash on repainted areas. Use to identify unnecessary repaints.

2. Enable Layer Borders

DevTools → More tools → Rendering → Layer borders ✓

Orange borders = compositing layers. Blue = tiles. Use to see layer boundaries.

3. Enable FPS Meter

DevTools → More tools → Rendering → Frame Rendering Stats ✓

Shows real-time FPS, GPU memory usage, frame timing.

4. Performance Panel

DevTools → Performance → Record → interact → Stop

Full frame-by-frame timeline. Look for Layout (purple), Paint (green), Composite.

5. Layers Panel

DevTools → More tools → Layers

3D view of all compositing layers. Click for compositing reasons, memory.

Chrome Tracing (Advanced)

chrome://tracing → Record → Categories: cc, gpu, viz

Low-level compositor and GPU events. Shows tile lifecycle, texture uploads, frame scheduling.

Perfetto Setup

https://ui.perfetto.dev/

Open Chrome trace files or connect to device. More powerful visualization than DevTools.

React Profiling Setup

DevTools → React DevTools → Profiler → Record
  1. Identify the problem: Janky scroll? Slow animation? Layout shift?
  2. Performance panel recording: Capture the interaction
  3. Check frame timing: Any frames > 16.67ms?
  4. Identify bottleneck stage: Layout (purple)? Paint (green)? JS (yellow)?
  5. If layout: Check for forced reflows, layout thrashing
  6. If paint: Enable paint flashing, check paint area
  7. If composite: Check Layers panel for layer explosion
  8. If JS: Profile with React DevTools + Performance panel
  9. Fix and verify: Re-record to confirm improvement
  10. Automate: Add Web Vitals monitoring in production

11. Performance Tooling Comparison

ToolPurposeBest ForLimitations
Chrome DevTools PerformanceFrame-level profilingIdentifying bottleneck stageManual, not CI-friendly
PerfettoSystem-level trace analysisAdvanced compositor/GPU debuggingSteep learning curve
Chrome TracingLow-level browser eventsRendering pipeline internalsVery verbose
LighthouseAutomated auditingQuick overview, CI integrationSynthetic only, no real interactions
WebPageTestReal-world loading analysisLoad performance, filmstripNot for runtime animation
FPS MeterReal-time frame rateQuick jank detectionNo detailed breakdown
Layers PanelLayer visualizationLayer explosion, memoryStatic snapshot only
Rendering PanelPaint/layout visualizationPaint flashing, layout shiftsVisual only
React ProfilerComponent render timingUnnecessary re-rendersReact-specific, no browser details

Detailed Comparison

CriteriaDevToolsPerfettoLighthouseWebPageTest
Learning curveMediumHighLowLow
CI/CD integration
Runtime animation
Load performance
GPU debugging⚠️
Mobile testing⚠️
Production monitoring

For production monitoring, use:


12. Cheatsheet

Rendering Pipeline Quick Reference

DOM → Style → Layout → Paint → Raster → Composite → Display
                 │         │                  │
                 │         │                  └─ GPU (compositor thread)
                 │         └─ CPU (raster workers)
                 └─ CPU (main thread)

Properties by Rendering Cost

CHEAPEST (composite only):
  transform, opacity, filter (composited)

MEDIUM (paint, no layout):
  color, background, box-shadow, outline, visibility

EXPENSIVE (layout + paint + composite):
  width, height, margin, padding, border-width,
  top, left, font-size, display, position, float

Animation Optimization Patterns

/* Compositor-safe animations */
.safe {
  transition:
    transform 0.3s,
    opacity 0.3s;
}

/* Prepare layer before animating */
.will-animate {
  will-change: transform;
}

/* Remove after animation */
.done {
  will-change: auto;
}

GPU-Friendly CSS Patterns

/* Layer promotion */
.layer {
  will-change: transform;
}
.layer-alt {
  transform: translateZ(0);
}

/* Isolate from overlap promotion */
.container {
  isolation: isolate;
}

/* Contain rendering */
.contained {
  contain: layout paint;
}

/* Skip off-screen rendering */
.lazy {
  content-visibility: auto;
}

Rendering Anti-Patterns

// ❌ Layout thrashing
el.style.width = el.offsetWidth + 10 + 'px';

// ❌ Animating layout properties
element.animate({ width: ['100px', '200px'] }, { duration: 300 });

// ❌ will-change on everything
* { will-change: transform; }

// ❌ Unbounded layers
// (hundreds of positioned elements near composited layer)

Layout Thrashing Fixes

// ✅ Use requestAnimationFrame
requestAnimationFrame(() => {
  // All writes here — browser batches layout
  elements.forEach((el) => (el.style.transform = `translateX(${x}px)`));
});

// ✅ Use ResizeObserver instead of reading dimensions
const ro = new ResizeObserver((entries) => {
  /* ... */
});

// ✅ Use IntersectionObserver instead of scroll + getBoundingClientRect
const io = new IntersectionObserver((entries) => {
  /* ... */
});

React Rendering Optimization Patterns

// ✅ Memo expensive components
const HeavyList = React.memo(({ items }) => (
  <VirtualList items={items} rowHeight={50} />
));

// ✅ Compositor-only animations
const style = { transform: `translateY(${offset}px)` };

// ✅ content-visibility for off-screen sections
<section style={{ contentVisibility: 'auto', containIntrinsicSize: '0 500px' }}>
  <HeavyContent />
</section>;

// ✅ Defer non-critical rendering
const [show, setShow] = useState(false);
useEffect(() => {
  startTransition(() => setShow(true));
}, []);

Frame Budget Checklist

Mobile Rendering Checklist


13. Real-World Engineering Mindset

Sticky Headers

Problem: Fixed position during scroll. Must composite efficiently.

StrategyProsConsBest For
position: stickyNative compositor handling, simpleLimited customizationMost cases
position: fixedFull controlAlways composited layer, overlap issuesComplex headers
IntersectionObserver + class toggleFine-grained controlMore JS, potential layoutConditional sticky
Transform-basedGPU-friendlyRequires manual scroll trackingCustom animations

Senior choice: position: sticky with contain: layout on parent. Simplest, browser-optimized. Use fixed only when sticky semantics don’t work.

Pitfalls: Sticky inside overflow container doesn’t work. Sticky creates stacking context → can trigger layer promotion of siblings.

Infinite Scrolling

Problem: Growing DOM = growing layout cost. Eventually jank.

StrategyProsCons
Virtualization (react-window)Constant DOM sizeComplex implementation, scroll position management
content-visibility: autoSimple, progressiveBrowser support, less control
PaginationSimple, predictableWorse UX
Remove off-screen DOM nodesReduces layout costScroll position jumps, complexity

Senior choice: Virtualization for 1000+ items. content-visibility: auto for moderate lists. Pagination for data-heavy applications.

GPU implications: Large scroll containers create scroll layers. Virtualization keeps paint area constant.

Modals

Problem: Overlay creates stacking context, may trigger layer promotion of everything behind.

Best practices:

Complex Dashboards

Problem: Many widgets, charts, data updates. Heavy layout/paint.

Strategy:

Large Data Grids

Problem: Thousands of cells = massive DOM = slow layout.

Senior approach:

Animations

Problem: Must maintain 60fps. Easy to accidentally trigger layout.

Rules:

  1. Only animate transform and opacity
  2. will-change before animation starts, remove after
  3. Use Web Animations API or CSS transitions (not JS per-frame)
  4. If must animate layout properties: use FLIP technique
  5. Test on mobile with CPU throttling

FLIP Technique:

// First: get initial position
const first = el.getBoundingClientRect();

// Last: apply final state
el.classList.add('final-position');
const last = el.getBoundingClientRect();

// Invert: transform from last back to first
const dx = first.left - last.left;
const dy = first.top - last.top;
el.style.transform = `translate(${dx}px, ${dy}px)`;

// Play: animate transform to zero (compositor only!)
requestAnimationFrame(() => {
  el.style.transition = 'transform 0.3s';
  el.style.transform = '';
});

Drag and Drop

Problem: Continuous movement. Must not trigger layout per frame.

Strategy:

Virtualized Lists

Rendering implications:

Canvas/WebGL Overlays

Video-Heavy Applications

Design Systems

Rendering considerations:


14. Brainstorm / Open Questions

Layout (15 questions)

  1. Why does reading offsetHeight after writing style.height force synchronous layout?
  2. How does CSS containment (contain: layout) create layout boundaries?
  3. Why are nested flexbox layouts more expensive than single-level?
  4. How does content-visibility: auto skip layout for off-screen content?
  5. Why does display: grid with auto sizing require multiple layout passes?
  6. How do subpixel calculations affect layout precision?
  7. Why does adding position: relative to many elements affect performance?
  8. How does fragment tree caching work in Blink’s LayoutNG?
  9. Why do table layouts require multiple passes?
  10. How does the browser determine layout boundaries for partial relayout?
  11. Why does overflow: hidden sometimes create a layout boundary?
  12. What determines whether a layout change propagates to parent vs stays contained?
  13. How does intrinsic sizing interact with flex/grid layout performance?
  14. Why do CSS custom properties not trigger layout by themselves?
  15. How does the browser handle layout for elements with percentage-based dimensions in complex hierarchies?

Paint (15 questions)

  1. Why is box-shadow with large spread expensive to paint?
  2. How does the browser determine paint invalidation regions?
  3. Why does filter: blur() require reading from surrounding pixels?
  4. How do paint chunks enable partial repaint?
  5. Why is text rendering one of the most expensive paint operations?
  6. How does contain: paint prevent paint overflow?
  7. What determines whether an element gets its own paint layer?
  8. Why does mix-blend-mode force a compositing layer?
  9. How does the browser cache display lists for unchanged content?
  10. Why are large background images more expensive to paint than solid colors?
  11. How does hardware rasterization differ from software rasterization?
  12. Why does clip-path with complex paths increase paint cost?
  13. How does the paint order within stacking contexts affect rendering?
  14. Why does border-radius increase paint complexity?
  15. How does the browser handle paint for partially visible elements?

Compositing (15 questions)

  1. Why does transform: translateZ(0) create a compositing layer?
  2. How does the compositor thread animate without main thread involvement?
  3. What causes implicit layer promotion (layer explosion)?
  4. Why does GPU memory usage increase with layer count?
  5. How does tiled compositing handle large layers?
  6. Why might a compositor-thread animation still be janky?
  7. How does isolation: isolate prevent unwanted layer creation?
  8. What determines tile priority during scrolling?
  9. Why do position: fixed elements always get own layers?
  10. How does the browser decide between CPU and GPU rasterization?
  11. What triggers texture re-upload to GPU?
  12. Why can’t all CSS properties be composited?
  13. How does the display compositor (Viz) handle cross-process compositing?
  14. What happens when GPU memory is exhausted?
  15. How does layer squashing work to reduce layer count?

GPU Rendering (15 questions)

  1. How do GPU textures relate to compositing layers?
  2. Why is GPU memory more constrained on mobile?
  3. How does VSync timing affect frame scheduling?
  4. What happens when the GPU can’t composite within frame budget?
  5. How does the GPU handle transparent layers vs opaque layers?
  6. Why is overdraw (painting same pixel multiple times) expensive on GPU?
  7. How does the GPU rasterizer handle anti-aliasing?
  8. What is the cost of texture upload from CPU to GPU?
  9. How do GPU shader programs participate in CSS filter rendering?
  10. Why does video playback interact with compositing differently?
  11. How does WebGL rendering coordinate with DOM compositing?
  12. What determines GPU vs CPU rasterization threshold?
  13. How does tile-based GPU rendering differ from desktop GPU rendering?
  14. Why do some mobile GPUs struggle with many small textures?
  15. How does the GPU handle layer blending modes?

Mobile Rendering (15 questions)

  1. Why do mobile devices have stricter layer budgets?
  2. How does thermal throttling affect rendering performance?
  3. Why is touch scroll handled by the compositor thread?
  4. How does mobile GPU memory architecture differ from desktop?
  5. Why do passive event listeners matter for scroll performance?
  6. How does DPI scaling affect rendering on mobile?
  7. Why is backdrop-filter particularly expensive on mobile?
  8. How does iOS Safari’s rendering differ from Chrome on Android?
  9. What causes “black rectangle” rendering on mobile (layer failure)?
  10. How does meta viewport affect rendering pipeline?
  11. Why do mobile browsers limit concurrent raster threads?
  12. How does safe area inset handling affect layout performance?
  13. Why does overscroll behavior impact compositor performance?
  14. How do mobile browsers handle off-screen tile management?
  15. What mobile-specific heuristics do browsers use for layer promotion?

React Rendering (15 questions)

  1. Why does setState in a scroll handler cause jank?
  2. How does React batching interact with browser rendering?
  3. Why does hydration cause rendering spikes?
  4. How does useLayoutEffect vs useEffect affect layout?
  5. Why can large component trees cause layout thrashing?
  6. How does React concurrent rendering interact with frame budget?
  7. Why does virtualization improve rendering performance for large lists?
  8. How does React.memo reduce rendering pipeline work?
  9. Why do CSS-in-JS libraries sometimes cause rendering issues?
  10. How does React Suspense interact with layout stability?
  11. Why does key-based reconciliation cause more rendering work than index?
  12. How does server component architecture reduce client rendering cost?
  13. Why do inline styles in React cause paint invalidation?
  14. How does startTransition help maintain frame budget?
  15. Why does portal rendering affect stacking context and layers?

Browser Architecture (15 questions)

  1. How do browser rendering pipelines differ between Blink, WebKit, and Gecko?
  2. Why does Gecko’s WebRender use a different compositing model?
  3. How does the browser scheduler prioritize rendering work?
  4. Why are raster worker threads separate from the main thread?
  5. How does cross-origin iframe rendering affect compositing?
  6. What determines the rendering pipeline for <canvas> vs DOM?
  7. How does the browser handle rendering during JavaScript execution?
  8. Why do service workers not affect rendering pipeline?
  9. How does the browser rendering pipeline handle requestAnimationFrame?
  10. What determines frame production in background tabs?
  11. How does renderer process isolation affect compositing?
  12. Why does the browser have separate GPU and renderer processes?
  13. How does incremental rendering work during HTML streaming?
  14. What causes render-blocking behavior in the pipeline?
  15. How do intersection/mutation observers interact with rendering?

Animation Systems (15 questions)

  1. Why is the Web Animations API more efficient than JS per-frame animation?
  2. How does FLIP technique convert layout animations to composite animations?
  3. Why can’t all CSS transitions run on the compositor thread?
  4. How does animation-composition affect rendering cost?
  5. Why does @keyframes animation on compositor properties avoid main thread?
  6. How does the browser interpolate transform matrices during animation?
  7. Why is spring-based animation potentially expensive in React?
  8. How does the compositor handle animation timeline synchronization?
  9. Why do transition and animation have different performance profiles?
  10. How does scroll-linked animation affect rendering performance?
  11. Why is requestAnimationFrame preferred over setInterval for animation?
  12. How does the browser decide to promote an animating element to its own layer?
  13. What rendering overhead does Framer Motion add compared to CSS transitions?
  14. How do CSS scroll-driven animations avoid main thread?
  15. Why does animating height: auto require layout every frame?

Rendering Scalability (15 questions)

  1. How does DOM size affect each rendering pipeline stage differently?
  2. Why does content-visibility improve rendering for long documents?
  3. How do micro-frontends affect rendering layer isolation?
  4. What rendering governance should design systems enforce?
  5. How do you detect rendering regressions in CI/CD?
  6. Why does component lazy loading help rendering performance?
  7. How does code splitting interact with rendering pipeline?
  8. What rendering budgets should teams define?
  9. How do you monitor rendering performance in production?
  10. Why does third-party script loading affect rendering pipeline?
  11. How does resource prioritization (fetchpriority) affect rendering?
  12. What determines optimal content-visibility boundaries?
  13. How do rendering performance budgets scale with team size?
  14. Why does rendering isolation matter for widget-based architectures?
  15. How do you benchmark rendering performance reliably?

15. Practice Questions

Beginner (35 questions)

Q1. What are the three main stages of the rendering pipeline after style calculation?

Q2. True or False: Changing background-color triggers layout.

Q3. Which CSS property triggers ONLY compositing (no layout, no paint)?

Q4. What is the frame budget at 60 FPS?

Q5. What is “reflow”?

Q6. Which thread handles compositing in Chrome?

Q7. True or False: display: none elements are included in the render tree.

Q8. Which is more expensive: changing opacity or changing width?

Q9. What does “paint” produce?

Q10. Which CSS property change can cause layout shift (CLS)?

Q11. What is compositing?

Q12. True or False: CSS transform animations always run on the compositor thread.

Q13. What happens when you read element.offsetHeight after modifying styles?

Q14. Which is cheaper to animate: left or transform: translateX()?

Q15. What does “rasterization” mean?

Q16. True or False: Every element on the page gets its own compositing layer.

Q17. What CSS property should you use to hint that an element will be animated?

Q18. What is “jank”?

Q19. Which renders faster — solid background-color or complex box-shadow?

Q20. What thread runs JavaScript?

Q21. True or False: The compositor thread can produce frames without the main thread.

Q22. Which DevTools panel shows compositing layers?

Q23. What does contain: paint do?

Q24. True or False: visibility: hidden removes the element from layout.

Q25. What is the render tree?

Q26. Which triggers more rendering work: adding a CSS class or changing inline style?

Q27. What is a stacking context?

Q28. True or False: GPU compositing uses more memory than CPU rendering.

Q29. What happens in the browser pipeline BEFORE layout?

Q30. Which is a “compositor-only” property?

Q31. What does “layout thrashing” mean?

Q32. True or False: position: fixed elements always get their own compositing layer.

Q33. What does “paint flashing” in DevTools show?

Q34. Which CSS value causes an element to be removed from the render tree?

Q35. What is VSync?


Junior (35 questions)

Q36. Your scroll handler reads getBoundingClientRect() for every element. Why is this slow?

Q37. How do you identify forced reflows in Chrome DevTools?

Q38. True or False: will-change: transform should be applied to all elements for better performance.

Q39. What is the difference between requestAnimationFrame and setTimeout for animation?

Q40. You see frequent green bars in the Performance panel. What does this indicate?

Q41. An element with will-change: opacity is not currently animating. What’s the issue?

Q42. How does contain: strict help rendering performance?

Q43. You notice orange borders around 200+ elements in the Rendering panel. What’s happening?

Q44. True or False: opacity: 0.99 creates a stacking context.

Q45. How would you animate an element from height: 0 to height: auto efficiently?

Q46. What does the Layers panel show as “compositing reason” for an element?

Q47. Why should scroll event listeners use { passive: true }?

Q48. Match the DevTools tool with its purpose:

Q49. You add transform: translateZ(0) to an element and memory usage increases. Why?

Q50. True or False: content-visibility: auto can reduce layout cost for off-screen elements.

Q51. How do you batch DOM mutations to avoid layout thrashing?

Q52. An animation uses top to move an element. DevTools shows purple Layout bars each frame. How to fix?

Q53. What’s the difference between a paint layer and a compositing layer?

Q54. You see “Update Layer Tree” taking 5ms in Performance. What should you investigate?

Q55. True or False: position: absolute always creates a compositing layer.

Q56. Why does overflow: hidden sometimes improve performance?

Q57. Your React component re-renders and you see a layout shift. What might cause this?

Q58. How does isolation: isolate help prevent layer explosion?

Q59. True or False: Reading scrollTop always forces synchronous layout.

Q60. What rendering cost does a large box-shadow incur?

Q61. You have a list of 10,000 items. What rendering optimization should you apply?

Q62. Why is animating box-shadow expensive?

Q63. True or False: The compositor thread can handle scroll without the main thread.

Q64. What happens when will-change is applied during an animation (not before)?

Q65. How do you measure GPU memory usage in Chrome?

Q66. What is “layer squashing” in Chrome?

Q67. You animate filter: blur() on a non-composited element. Why is it slow?

Q68. True or False: contain: layout paint helps with scroll performance.

Q69. What is the rendering difference between visibility: hidden and opacity: 0?

Q70. Your page scrolls at 30fps on mobile. What’s your debugging workflow?


Senior (35 questions)

Q71. Your React app has 500 components re-rendering. How does this affect the rendering pipeline?

Q72. Explain the trade-off between many small compositing layers vs few large layers.

Q73. How would you architect rendering for a dashboard with 50 real-time updating widgets?

Q74. True or False: React Server Components reduce rendering pipeline cost on the client.

Q75. Your streaming SSR page shows layout shifts as content loads. How to fix?

Q76. How does IntersectionObserver avoid the rendering cost of scroll-based visibility detection?

Q77. You notice that a position: sticky header causes 30 extra compositing layers below it. Why?

Q78. How would you implement a performant drag-and-drop with 1000 sortable items?

Q79. Explain why React’s useLayoutEffect can cause rendering issues.

Q80. True or False: backdrop-filter: blur() is as cheap as regular filter: blur().

Q81. How do you detect rendering regressions in CI/CD?

Q82. Your virtualized list jitters during fast scroll. What’s the rendering issue?

Q83. How does content-visibility: auto interact with layout containment?

Q84. Explain GPU memory implications of a page with 20 fixed-position elements on mobile.

Q85. How would you architect animation for a design system used across 50 apps?

Q86. True or False: contain: size means the browser doesn’t need to layout children to determine parent size.

Q87. Your Next.js app hydrates and CLS spikes to 0.3. Debugging approach?

Q88. How does OffscreenCanvas improve rendering performance?

Q89. Explain the rendering cost difference between box-shadow on hover vs a pseudo-element approach.

.card::after {
  content: '';
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  opacity: 0;
  transition: opacity 0.3s;
}
.card:hover::after {
  opacity: 1;
}

Q90. How does React concurrent mode help rendering performance at the pipeline level?

Q91. True or False: CSS contain: strict on every component is always beneficial.

Q92. How would you handle rendering for a video call app with 25 video streams?

Q93. What rendering optimization does Astro’s islands architecture provide?

Q94. How do you profile rendering performance on low-end Android devices?

Q95. Explain the rendering implications of CSS-in-JS at scale (1000+ components).

Q96. True or False: transform: translate3d(0,0,0) and will-change: transform have identical rendering effects.

Q97. How do you prevent layer explosion when using tooltips across a data grid?

Q98. Your page has backdrop-filter: blur(10px) on mobile and stutters. Why?

Q99. How does font-display: optional help rendering performance?

Q100. Explain the rendering architecture you’d use for an infinite canvas app (like Figma).

Q101. True or False: Scroll-driven CSS animations avoid the main thread.

Q102. How does contain-intrinsic-size work with content-visibility?

Q103. Your Astro site uses client:visible but users see layout shifts when islands hydrate. Fix?

Q104. How do you measure rendering performance of a specific component in isolation?

Q105. Explain why z-index changes can affect rendering performance.


Expert / Browser Rendering Engineer (35 questions)

Q106. How does Blink’s LayoutNG differ from the legacy layout system?

Q107. Explain how the Blink compositor decides tile priority during scroll.

Q108. True or False: Blink’s raster worker threads can run in the GPU process.

Q109. How does the rendering scheduler handle a 100ms JavaScript task that spans multiple frames?

Q110. Explain Gecko’s WebRender architecture vs Blink’s compositing model.

Q111. How does Chrome handle rendering for <iframe> from a different origin?

Q112. What happens at the GPU level when an element’s transform changes during compositing?

Q113. True or False: The display compositor (Viz) in Chrome runs in the browser process, not the renderer process.

Q114. How does Chrome’s rendering pipeline handle requestAnimationFrame scheduling?

Q115. Explain the tile lifecycle in Blink’s compositing: creation → rasterization → upload → display → eviction.

Q116. How does the compositor thread decide when to commit a new frame from the main thread?

Q117. True or False: In Chrome, compositing happens in the renderer process’s compositor thread, not the GPU process.

Q118. How does style invalidation propagate in Blink when a CSS class is added?

Q119. Explain the rendering difference between transform: rotate(45deg) and transform: matrix(...).

Q120. How does the browser handle “jank” during layer promotion (when an element first becomes composited)?

Q121. True or False: The compositor thread can animate CSS filter without main thread involvement.

Q122. How does Chrome’s rendering pipeline handle a ResizeObserver that modifies layout?

Q123. Explain the memory overhead of text layers in GPU compositing.

Q124. How does the Blink rendering pipeline handle will-change: contents?

Q125. True or False: Blink can skip paint for compositing layers that haven’t changed.

Q126. How does scroll-linked compositing work in Chrome (scroll offset applied by compositor)?

Q127. Explain how Chrome handles rendering of position: fixed inside a transform parent.

Q128. How does the rendering pipeline handle backdrop-filter across frame boundaries?

Q129. True or False: Chrome’s GPU process uses Vulkan/Metal/DirectX (not only OpenGL).

Q130. How does the main thread → compositor thread commit work in terms of thread synchronization?

Q131. Explain how rendering pipeline handles content-visibility: hidden vs content-visibility: auto.

Q132. How do CSS layers (@layer) affect rendering performance?

Q133. True or False: The compositor can produce partial frames (show some layers updated and others stale).

Q134. How does the rendering pipeline handle will-change removal — what’s the GPU memory lifecycle?

Q135. Explain the rendering trade-off of using Canvas 2D vs SVG for a chart with 10,000 data points.

Q136. How does Chrome handle rendering of elements with mix-blend-mode in the compositing pipeline?

Q137. True or False: Blink can reuse rasterized tiles when scrolling back to previously viewed content.

Q138. How does the rendering pipeline handle CSS animation with composite: accumulate?

Q139. Explain the Chrome scheduling model for rendering: BeginFrame → commit → activate → draw.

Q140. How would you detect and fix a rendering regression that only appears on 120Hz displays?


16. Personalized Recommendations

For Your Stack (React, Next.js, Astro, Vite, TypeScript)

Most Important Rendering Concepts

  1. Layout containment — React re-renders can trigger cascading layout. Use contain: layout on component boundaries.
  2. Compositor-only animations — Never animate layout properties. Always use transform/opacity.
  3. Hydration rendering cost — Understand that hydration is a rendering spike. Use Suspense boundaries.
  4. Virtualization — Large lists in React = large DOM = expensive layout. Always virtualize.
  5. Layer management — Understand when React components create compositing layers and the memory cost.

Priority Topics to Learn Next

  1. CSS Containment API (contain, content-visibility) — highest ROI for React apps
  2. FLIP animation technique — enables smooth layout-like animations using compositor
  3. Chrome DevTools Performance panel — primary debugging tool
  4. Layer explosion diagnosis — common in complex React apps
  5. Mobile rendering profiling — real-world user impact

Common Frontend Engineer Rendering Mistakes

  1. Animating height/width/margin instead of transform
  2. Layout thrashing in effects (useLayoutEffect + DOM reads/writes)
  3. will-change on everything (layer explosion)
  4. Not virtualizing lists over 100 items
  5. Images without explicit dimensions (CLS)
  6. Runtime CSS-in-JS at scale (style recalculation)
  7. Non-passive scroll handlers (blocks compositor)
  8. Backdrop-filter on mobile without testing performance
  9. Not using contain on isolated components
  10. Ignoring hydration rendering cost in SSR apps

60-Day Learning Plan

Week 1-2: Fundamentals & Tooling

Week 3-4: Layout Mastery

Week 5-6: Paint & Compositing

Week 7-8: Advanced Performance

Milestone checks:


Beginner

Intermediate

Advanced

Expert / Browser Internals

React / Framework Performance


18. Advanced Engineering Topics

The Blink rendering pipeline runs in the renderer process:

Main Thread:
  DOM → StyleEngine → LayoutNG → PaintArtifact → CommitToCompositor

Compositor Thread (cc):
  LayerTreeHost → TileManager → Scheduler → ProduceFrame

GPU Process (Viz):
  DisplayCompositor → SkiaRenderer → Present

Key internal classes:

Rendering Scheduler

Chrome’s rendering scheduler prioritizes:

  1. Input events (highest priority)
  2. Compositor animations
  3. rAF callbacks
  4. Style/Layout/Paint
  5. Idle callbacks (lowest priority)

The scheduler uses deadline-based scheduling: work items must complete before the vsync deadline. If a task won’t fit, it’s deferred to next frame.

Rasterization Pipeline

DisplayItemList → SkCanvas commands → Skia → GPU backend

                                    ┌─────────┼──────────┐
                                    │         │          │
                                  Vulkan    Metal    OpenGL

GPU rasterization (OOP-R) rasterizes directly in GPU process, avoiding CPU→GPU texture upload latency.

Future Directions


Summary

Key Takeaways

  1. The rendering pipeline is a cascade: DOM → Style → Layout → Paint → Composite. Each stage can be skipped if not invalidated.
  2. Compositor-only animations are essential: transform and opacity bypass layout and paint entirely.
  3. Layout is the most expensive stage: Avoid forced reflows, use containment, virtualize large DOM.
  4. GPU memory is finite: Every compositing layer costs memory. Fewer layers = better mobile performance.
  5. The compositor thread is your ally: Offload work to it via compositor-friendly properties.
  6. Measure before optimizing: Use DevTools Performance panel to identify actual bottlenecks.

Next Steps

  1. Profile your current apps with Chrome DevTools Performance panel
  2. Fix any layout thrashing patterns
  3. Audit and convert animations to compositor-only
  4. Implement CSS containment on component boundaries
  5. Test on real mobile devices with CPU throttling
  6. Set up rendering performance monitoring in production

Advanced Topics to Continue


Edit page
Share this post:

Previous Post
GitLab Container Registry — Complete Deep-Dive Engineering Guide
Next Post
Monorepo Architecture — Complete Engineering Guide