[{"data":1,"prerenderedAt":2042},["ShallowReactive",2],{"content:\u002Frendering-css-performance":3,"surroundings:\u002Frendering-css-performance":2033},{"id":4,"title":5,"body":6,"description":2014,"extension":2015,"meta":2016,"navigation":2026,"path":2027,"seo":2028,"stem":2031,"__hash__":2032},"content\u002Frendering-css-performance\u002Findex.md","Rendering & CSS Performance",{"type":7,"value":8,"toc":1994},"minimark",[9,14,18,21,50,198,203,219,222,271,274,412,415,419,426,453,459,463,478,491,501,571,575,591,605,611,651,655,687,701,718,722,725,748,754,834,838,844,851,854,858,863,1052,1056,1121,1125,1309,1313,1418,1422,1773,1777,1839,1843,1855,1864,1873,1882,1899,1908,1917,1921,1953,1957,1980,1985,1988,1991],[10,11,13],"h1",{"id":12},"rendering-css-performance-making-style-layout-and-paint-cheap","Rendering & CSS Performance: Making Style, Layout and Paint Cheap",[15,16,17],"p",{},"Most performance work focuses on the network: fewer bytes, earlier requests, better caching. But once the bytes arrive, the browser still has to turn HTML and CSS into pixels, and it has to do that again every time something changes. That work — recalculating styles, computing layout, painting and compositing layers — runs largely on the main thread, the same thread that handles user input. When it is expensive, pages render late (hurting Largest Contentful Paint), respond slowly to clicks and typing (hurting Interaction to Next Paint), and shift around as late layout happens (hurting Cumulative Layout Shift).",[15,19,20],{},"Rendering costs are driven by decisions developers make every day: how CSS is delivered and how much of it there is, how large and deep the DOM grows, whether JavaScript reads layout in the middle of writing styles, which properties are animated, and whether independent parts of the page are isolated from each other. None of these show up as large network requests, so they are easy to miss in a waterfall. They show up instead as purple and green blocks in the Performance panel — Recalculate Style, Layout, Paint — and as long tasks that delay input.",[15,22,23,24,29,30,34,35,39,40,44,45,49],{},"This section is organised around the rendering pipeline. ",[25,26,28],"a",{"href":27},"\u002Frendering-css-performance\u002Fcritical-css-and-render-blocking-resources\u002F","Critical CSS and render-blocking resources"," covers getting the first frame painted. ",[25,31,33],{"href":32},"\u002Frendering-css-performance\u002Fcss-containment-and-content-visibility\u002F","CSS containment and content-visibility"," covers limiting how much work each change triggers. ",[25,36,38],{"href":37},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F","Layout thrashing and forced reflow"," covers the JavaScript patterns that make layout run repeatedly. ",[25,41,43],{"href":42},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002F","Animation and compositing performance"," covers moving motion off the main thread. ",[25,46,48],{"href":47},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002F","DOM size and list virtualization"," covers the cost multiplier behind all of the above: the number of elements.",[15,51,52],{},[53,54,60,61,60,68,60,72,60,75,60,93,60,101,60,107,60,115,60,122,60,127,60,131,60,134,60,138,60,141,60,147,60,151,60,154,60,157,60,160,60,164,60,167,60,170,60,174,60,177,60,186,60,190,60,194,60],"svg",{"viewBox":55,"width":56,"role":57,"ariaLabel":58,"style":59},"0 0 760 149","100%","img","The stages a browser runs to turn DOM and CSS into pixels, from style calculation through layout, paint and composite.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[62,63],"rect",{"className":64,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],"svg-canvas","0","#ffffff",[69,70,71],"title",{},"The browser rendering pipeline",[73,74,58],"desc",{},[76,77,78],"defs",{},[79,80,87],"marker",{"id":81,"viewBox":82,"refX":83,"refY":84,"markerWidth":85,"markerHeight":85,"orient":86},"fabcd399e1","0 0 10 10","9","5","7","auto-start-reverse",[88,89],"path",{"d":90,"fill":91,"style":92},"M0 0 L10 5 L0 10 z","currentColor","fill-opacity:0.7",[62,94],{"x":95,"y":95,"width":96,"height":97,"rx":98,"fill":99,"stroke":91,"style":100},"1","758","147","10","none","stroke-opacity:0.18",[102,103,71],"text",{"x":104,"y":105,"fill":91,"style":106},"28.0","34.0","font-size:16px;font-weight:700",[62,108],{"x":104,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":113,"style":114},"56.0","108.8","71.0","6","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[102,116,121],{"x":117,"y":118,"fill":91,"style":119,"textAnchor":120},"82.4","78.0","font-size:13px;font-weight:700","middle","DOM + CSSOM",[102,123,126],{"x":117,"y":124,"fill":91,"style":125,"textAnchor":120},"96.0","font-size:12px","parse HTML and",[102,128,130],{"x":117,"y":129,"fill":91,"style":125,"textAnchor":120},"112.0","CSS",[62,132],{"x":133,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":113,"style":114},"176.8",[102,135,137],{"x":136,"y":118,"fill":91,"style":119,"textAnchor":120},"231.2","Style",[102,139,140],{"x":136,"y":124,"fill":91,"style":125,"textAnchor":120},"match selectors",[62,142],{"x":143,"y":109,"width":110,"height":111,"rx":112,"fill":144,"stroke":145,"style":146},"325.6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[102,148,150],{"x":149,"y":118,"fill":91,"style":119,"textAnchor":120},"380.0","Layout",[102,152,153],{"x":149,"y":124,"fill":91,"style":125,"textAnchor":120},"geometry of",[102,155,156],{"x":149,"y":129,"fill":91,"style":125,"textAnchor":120},"boxes",[62,158],{"x":159,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":113,"style":114},"474.4",[102,161,163],{"x":162,"y":118,"fill":91,"style":119,"textAnchor":120},"528.8","Paint",[102,165,166],{"x":162,"y":124,"fill":91,"style":125,"textAnchor":120},"draw records",[62,168],{"x":169,"y":109,"width":110,"height":111,"rx":112,"fill":113,"stroke":113,"style":114},"623.2",[102,171,173],{"x":172,"y":118,"fill":91,"style":119,"textAnchor":120},"677.6","Composite",[102,175,176],{"x":172,"y":124,"fill":91,"style":125,"textAnchor":120},"layers to GPU",[178,179],"line",{"x1":180,"y1":181,"x2":182,"y2":181,"stroke":91,"strokeWidth":183,"style":184,"markerEnd":185},"136.8","91.5","174.8","1.5","stroke-opacity:0.6","url(#fabcd399e1)",[178,187],{"x1":188,"y1":181,"x2":189,"y2":181,"stroke":91,"strokeWidth":183,"style":184,"markerEnd":185},"285.6","323.6",[178,191],{"x1":192,"y1":181,"x2":193,"y2":181,"stroke":91,"strokeWidth":183,"style":184,"markerEnd":185},"434.4","472.4",[178,195],{"x1":196,"y1":181,"x2":197,"y2":181,"stroke":91,"strokeWidth":183,"style":184,"markerEnd":185},"583.2","621.2",[199,200,202],"h2",{"id":201},"diagnostic-overview-where-rendering-time-goes","Diagnostic Overview: Where Rendering Time Goes",[15,204,205,206,210,211,214,215,218],{},"Start from a Performance panel recording on a realistic device (or with 4x CPU throttling) covering page load and a few typical interactions. In the summary, compare time spent in ",[207,208,209],"strong",{},"Rendering"," (style and layout) and ",[207,212,213],{},"Painting"," against ",[207,216,217],{},"Scripting",". On many content sites, rendering is a modest share; on complex applications, dashboards and long feeds, it can rival or exceed JavaScript execution.",[15,220,221],{},"Then look for the four signatures that map to the topics in this section:",[223,224,225,232,243,257],"ul",{},[226,227,228,231],"li",{},[207,229,230],{},"Late first paint with an idle main thread."," The browser is waiting for render-blocking CSS (or synchronous scripts) to download. The fix is in delivery: inline critical CSS, defer the rest, remove unused rules. This is a network-shaped rendering problem and directly delays FCP and LCP.",[226,233,234,237,238,242],{},[207,235,236],{},"Large single Layout or Recalculate Style blocks."," A change touched many elements, or the DOM is very large. The fix is to reduce the scope of each change (containment, ",[239,240,241],"code",{},"content-visibility",") or the number of elements (virtualization, simpler markup).",[226,244,245,248,249,252,253,256],{},[207,246,247],{},"Many small Layout blocks interleaved with script",", often with a warning triangle labelled \"Forced reflow\". JavaScript is reading geometry (",[239,250,251],{},"offsetHeight",", ",[239,254,255],{},"getBoundingClientRect",") after writing styles, forcing synchronous layout again and again. The fix is batching reads and writes or using observers.",[226,258,259,262,263,266,267,270],{},[207,260,261],{},"Long Paint or frequent Composite work during animation",", with dropped frames. Animated properties trigger layout or paint on every frame. The fix is animating only ",[239,264,265],{},"transform"," and ",[239,268,269],{},"opacity",", and keeping layer counts sensible.",[15,272,273],{},"Each signature has a characteristic metric impact. Render-blocking CSS hurts FCP and LCP for every visitor. Expensive style and layout hurt INP, because the browser must render the next frame after an event handler runs, and that frame includes the style and layout work the handler triggered. Unbounded layout cost on large DOMs hurts both. Poorly implemented animations mostly hurt smoothness, but layout-triggering animations also cause layout shifts that count towards CLS.",[15,275,276],{},[53,277,60,280,60,283,60,286,60,288,60,291,60,293,60,298,60,305,60,309,60,313,60,316,60,320,60,323,60,327,60,331,60,335,60,337,60,340,60,342,60,345,60,347,60,350,60,353,60,357,60,359,60,362,60,364,60,367,60,369,60,371,60,374,60,378,60,380,60,382,60,384,60,387,60,389,60,391,60,394,60,398,60,400,60,402,60,404,60,407,60,409,60],{"viewBox":278,"width":56,"role":57,"ariaLabel":279,"style":59},"0 0 760 228","Mapping of common rendering problems to the Core Web Vitals they affect most.",[62,281],{"className":282,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],[69,284,285],{},"Rendering symptoms and the metrics they move",[73,287,279],{},[62,289],{"x":95,"y":95,"width":96,"height":290,"rx":98,"fill":99,"stroke":91,"style":100},"226",[102,292,285],{"x":104,"y":105,"fill":91,"style":106},[62,294],{"x":104,"y":109,"width":295,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},"178.6","30.0","fill-opacity:0.06;stroke-opacity:0.4",[102,299,304],{"x":300,"y":301,"fill":91,"style":302,"textAnchor":303},"38.0","75.5","font-size:12.5px;font-weight:700","start","Problem",[62,306],{"x":307,"y":109,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},"206.6","175.1",[102,310,312],{"x":311,"y":301,"fill":91,"style":302,"textAnchor":120},"294.2","LCP",[62,314],{"x":315,"y":109,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},"381.7",[102,317,319],{"x":318,"y":301,"fill":91,"style":302,"textAnchor":120},"469.3","INP",[62,321],{"x":322,"y":109,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},"556.9",[102,324,326],{"x":325,"y":301,"fill":91,"style":302,"textAnchor":120},"644.4","CLS",[62,328],{"x":104,"y":329,"width":295,"height":296,"rx":66,"fill":99,"stroke":91,"style":330},"86.0","stroke-opacity:0.35",[102,332,334],{"x":300,"y":333,"fill":91,"style":302,"textAnchor":303},"105.5","Render-blocking CSS",[62,336],{"x":307,"y":329,"width":308,"height":296,"rx":66,"fill":144,"stroke":145,"style":146},[102,338,339],{"x":311,"y":333,"fill":91,"style":125,"textAnchor":120},"delays first paint",[62,341],{"x":315,"y":329,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,343,344],{"x":318,"y":333,"fill":91,"style":125,"textAnchor":120},"minor",[62,346],{"x":322,"y":329,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,348,349],{"x":325,"y":333,"fill":91,"style":125,"textAnchor":120},"FOUC shifts if misused",[62,351],{"x":104,"y":352,"width":295,"height":296,"rx":66,"fill":99,"stroke":91,"style":330},"116.0",[102,354,356],{"x":300,"y":355,"fill":91,"style":302,"textAnchor":303},"135.5","Large DOM and style cost",[62,358],{"x":307,"y":352,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,360,361],{"x":311,"y":355,"fill":91,"style":125,"textAnchor":120},"slower first layout",[62,363],{"x":315,"y":352,"width":308,"height":296,"rx":66,"fill":144,"stroke":145,"style":146},[102,365,366],{"x":318,"y":355,"fill":91,"style":125,"textAnchor":120},"slow next paint",[62,368],{"x":322,"y":352,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,370,344],{"x":325,"y":355,"fill":91,"style":125,"textAnchor":120},[62,372],{"x":104,"y":373,"width":295,"height":296,"rx":66,"fill":99,"stroke":91,"style":330},"146.0",[102,375,377],{"x":300,"y":376,"fill":91,"style":302,"textAnchor":303},"165.5","Forced reflow in handlers",[62,379],{"x":307,"y":373,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,381,344],{"x":311,"y":376,"fill":91,"style":125,"textAnchor":120},[62,383],{"x":315,"y":373,"width":308,"height":296,"rx":66,"fill":144,"stroke":145,"style":146},[102,385,386],{"x":318,"y":376,"fill":91,"style":125,"textAnchor":120},"long tasks on input",[62,388],{"x":322,"y":373,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,390,344],{"x":325,"y":376,"fill":91,"style":125,"textAnchor":120},[62,392],{"x":104,"y":393,"width":295,"height":296,"rx":66,"fill":99,"stroke":91,"style":330},"176.0",[102,395,397],{"x":300,"y":396,"fill":91,"style":302,"textAnchor":303},"195.5","Animating layout properties",[62,399],{"x":307,"y":393,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,401,344],{"x":311,"y":396,"fill":91,"style":125,"textAnchor":120},[62,403],{"x":315,"y":393,"width":308,"height":296,"rx":66,"fill":91,"stroke":91,"style":297},[102,405,406],{"x":318,"y":396,"fill":91,"style":125,"textAnchor":120},"busy main thread",[62,408],{"x":322,"y":393,"width":308,"height":296,"rx":66,"fill":144,"stroke":145,"style":146},[102,410,411],{"x":325,"y":396,"fill":91,"style":125,"textAnchor":120},"shifts counted",[15,413,414],{},"A useful habit when profiling is to record the same interaction twice: once as a typical user would perform it, and once with the CPU throttled four times. Rendering costs scale with device speed, so a 40ms layout on a developer laptop can become 160ms on a budget phone, which is exactly where INP and LCP fail in the field.",[199,416,418],{"id":417},"architecture-1-getting-the-first-frame-painted","Architecture 1: Getting the First Frame Painted",[15,420,421,422,425],{},"The browser will not paint anything until it has the CSS it considers render-blocking: every stylesheet linked in the head without a non-matching ",[239,423,424],{},"media"," attribute. On a fast connection, that is a few tens of milliseconds; on a slow mobile connection with a 200KB framework stylesheet served from another origin, it can be over a second of blank screen, during which the main thread is mostly idle.",[15,427,428,429,432,433,436,437,440,441,444,445,448,449,452],{},"Three techniques address this. ",[207,430,431],{},"Critical CSS"," inlines the small set of rules needed to render above-the-fold content directly in the HTML, so the first paint does not wait for any stylesheet download. ",[207,434,435],{},"Asynchronous loading"," of the remaining CSS (for example with ",[239,438,439],{},"media=\"print\""," swapped to ",[239,442,443],{},"all"," on load, or ",[239,446,447],{},"rel=\"preload\""," with an onload handler) lets it arrive without blocking. ",[207,450,451],{},"Removing unused CSS"," shrinks everything — many sites ship 80–90% unused rules from frameworks and old features. A fourth consideration is the choice of styling architecture: runtime CSS-in-JS libraries generate and inject styles during rendering, which adds scripting and style recalculation cost on the client; zero-runtime alternatives extract static CSS at build time.",[15,454,455,456,458],{},"The trade-offs are real. Inlined CSS is not cached separately, so it is re-sent on every page view; it works best when small (under roughly 14KB compressed so it fits early in the response). Asynchronously loaded CSS can cause a flash of unstyled content or layout shifts if the critical subset misses rules for visible elements. The guides in ",[25,457,28],{"href":27}," cover extraction, safe async loading and measuring the effect on LCP.",[199,460,462],{"id":461},"architecture-2-limiting-the-scope-of-rendering-work","Architecture 2: Limiting the Scope of Rendering Work",[15,464,465,466,469,470,473,474,477],{},"By default, a change anywhere in the document can, in principle, affect layout anywhere else, so browsers must be conservative about what they recompute. CSS containment tells the browser that a subtree is independent. ",[239,467,468],{},"contain: layout"," means the inside of an element does not affect layout outside it; ",[239,471,472],{},"contain: paint"," means its contents do not paint outside its bounds; ",[239,475,476],{},"contain: size"," means its size does not depend on its contents. With these guarantees, the browser can limit style, layout and paint work to the subtree that changed.",[15,479,480,483,484,486,487,490],{},[239,481,482],{},"content-visibility: auto"," builds on containment to skip rendering work entirely for offscreen content. A long article or feed with ",[239,485,482],{}," on each section lets the browser skip style, layout and paint for sections far from the viewport, then render them as the user scrolls. Initial rendering cost can drop dramatically on long pages. The companion property ",[239,488,489],{},"contain-intrinsic-size"," provides a placeholder size for skipped content, so the scrollbar and page height stay stable.",[15,492,493,494,497,498,500],{},"Containment is a hint about independence, not a magic speed switch. Applied to widgets that really are independent — a chat panel, an ad slot, a comment thread — it reduces the blast radius of updates. Applied blindly, it can clip content (paint containment), break ",[239,495,496],{},"position: sticky"," in some layouts, or cause size jumps when intrinsic sizes are wrong. The guides in ",[25,499,33],{"href":32}," explain where it pays off and how to verify the savings.",[15,502,503],{},[53,504,60,507,60,510,60,513,60,515,60,518,60,520,60,527,60,534,60,539,60,543,60,548,60,552,60,556,60,561,60,565,60],{"viewBox":505,"width":56,"role":57,"ariaLabel":506,"style":59},"0 0 760 189","Bar chart comparing rendering time on initial load for a long article with and without content-visibility auto.",[62,508],{"className":509,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],[69,511,512],{},"Initial rendering time for a 12,000-word article page",[73,514,506],{},[62,516],{"x":95,"y":95,"width":96,"height":517,"rx":98,"fill":99,"stroke":91,"style":100},"187",[102,519,512],{"x":104,"y":105,"fill":91,"style":106},[102,521,526],{"x":522,"y":523,"fill":91,"style":524,"textAnchor":525},"225.3","70.0","font-size:13px","end","No containment",[62,528],{"x":529,"y":109,"width":530,"height":531,"rx":532,"fill":99,"stroke":91,"style":533},"237.3","430.7","19","3","fill-opacity:0.7;stroke-opacity:0.35",[102,535,538],{"x":536,"y":523,"fill":91,"style":537},"674.0","font-size:12px;font-weight:600","410ms",[102,540,542],{"x":522,"y":541,"fill":91,"style":524,"textAnchor":525},"101.0","contain: layout paint on sections",[62,544],{"x":529,"y":545,"width":546,"height":531,"rx":532,"fill":113,"stroke":113,"style":547},"87.0","346.7","fill-opacity:0.55;stroke-opacity:0.9",[102,549,551],{"x":550,"y":541,"fill":91,"style":537},"590.0","330ms",[102,553,555],{"x":522,"y":554,"fill":91,"style":524,"textAnchor":525},"132.0","content-visibility: auto on sections",[62,557],{"x":529,"y":558,"width":559,"height":531,"rx":532,"fill":144,"stroke":145,"style":560},"118.0","99.8","fill-opacity:0.7;stroke-opacity:0.9",[102,562,564],{"x":563,"y":554,"fill":91,"style":537},"343.1","95ms",[102,566,570],{"x":104,"y":567,"fill":568,"style":569},"167.0","#51617a","font-size:12.5px","Measured as style + layout + paint on a mid-range phone profile.",[199,572,574],{"id":573},"architecture-3-avoiding-layout-thrashing","Architecture 3: Avoiding Layout Thrashing",[15,576,577,578,252,581,252,584,252,587,590],{},"Browsers batch style and layout work: when JavaScript changes styles, the browser normally waits until the next frame to recompute layout once. That batching breaks when script asks for layout information — ",[239,579,580],{},"offsetWidth",[239,582,583],{},"getBoundingClientRect()",[239,585,586],{},"scrollTop",[239,588,589],{},"getComputedStyle()"," — after making a change. To answer accurately, the browser must run style and layout synchronously, right now. Do this in a loop (write, read, write, read) and layout runs once per iteration: layout thrashing.",[15,592,593,594,597,598,266,601,604],{},"Thrashing is a classic cause of long tasks in event handlers and therefore poor INP. Common sources include measuring elements in a loop to position tooltips, animating with JavaScript that reads current positions every frame, third-party widgets that measure their container repeatedly, and resize or scroll handlers that both read and write geometry. The cure is structural: read all the geometry you need first, then write all the changes; defer writes to ",[239,595,596],{},"requestAnimationFrame","; and replace polling handlers with ",[239,599,600],{},"ResizeObserver",[239,602,603],{},"IntersectionObserver",", which deliver geometry at points where layout is already up to date.",[15,606,607,608,610],{},"The Performance panel flags forced reflows explicitly, with a stack trace pointing to the line of code that read layout. The guides in ",[25,609,38],{"href":37}," walk through finding them, batching reads and writes, and migrating handlers to observers.",[15,612,613],{},[53,614,60,617,60,620,60,623,60,625,60,628,60,630,60,634,60,638,60,641,60,644,60,647,60],{"viewBox":615,"width":56,"role":57,"ariaLabel":616,"style":59},"0 0 760 132","Bar chart comparing main-thread time for a loop that forces layout on every iteration with a batched approach that runs layout once.",[62,618],{"className":619,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],[69,621,622],{},"Write-read loop vs batched reads and writes (100 items)",[73,624,616],{},[62,626],{"x":95,"y":95,"width":96,"height":627,"rx":98,"fill":99,"stroke":91,"style":100},"130",[102,629,622],{"x":104,"y":105,"fill":91,"style":106},[102,631,633],{"x":632,"y":523,"fill":91,"style":524,"textAnchor":525},"235.8","Interleaved write\u002Fread (100 layouts)",[62,635],{"x":636,"y":109,"width":637,"height":531,"rx":532,"fill":99,"stroke":91,"style":533},"247.8","420.2",[102,639,640],{"x":536,"y":523,"fill":91,"style":537},"110ms",[102,642,643],{"x":632,"y":541,"fill":91,"style":524,"textAnchor":525},"Batched reads then writes (1 layout)",[62,645],{"x":636,"y":545,"width":646,"height":531,"rx":532,"fill":144,"stroke":145,"style":560},"68.8",[102,648,650],{"x":649,"y":541,"fill":91,"style":537},"322.6","18ms",[199,652,654],{"id":653},"architecture-4-animating-on-the-compositor","Architecture 4: Animating on the Compositor",[15,656,657,658,252,661,252,664,252,667,252,670,673,674,252,677,680,681,683,684,686],{},"Each frame at 60Hz has about 16.7ms; on 120Hz displays, about 8.3ms. If an animation changes a property that affects layout (",[239,659,660],{},"width",[239,662,663],{},"height",[239,665,666],{},"top",[239,668,669],{},"left",[239,671,672],{},"margin","), each frame needs style, layout, paint and composite — on the main thread, competing with scripts. If it changes a paint-only property (",[239,675,676],{},"background-color",[239,678,679],{},"box-shadow","), layout is skipped but paint still runs. If it changes only ",[239,682,265],{}," or ",[239,685,269],{}," on an element with its own compositing layer, the compositor thread can run the animation alone, even while the main thread is busy.",[15,688,689,690,693,694,697,698,700],{},"This is why \"animate only transform and opacity\" is the most important rule of animation performance. Movement becomes ",[239,691,692],{},"translate",", size changes become ",[239,695,696],{},"scale",", fades become ",[239,699,269],{},". Layout-affecting animations also cause layout shifts that may count towards CLS when they are not triggered by user input.",[15,702,703,704,707,708,266,711,714,715,717],{},"Layers have a cost too. ",[239,705,706],{},"will-change: transform"," promotes an element to its own layer ahead of time, which helps animations start smoothly, but each layer consumes GPU memory, and promoting hundreds of elements (\"layer explosion\") can make performance worse, especially on low-memory phones. Newer platform features shift more work to the compositor: scroll-driven animations (",[239,709,710],{},"animation-timeline: scroll()",[239,712,713],{},"view()",") replace scroll listeners with declarative CSS, and the View Transitions API animates between page states with snapshots. Both have their own performance characteristics. The guides in ",[25,716,43],{"href":42}," cover each.",[199,719,721],{"id":720},"architecture-5-controlling-dom-size","Architecture 5: Controlling DOM Size",[15,723,724],{},"Almost every rendering cost scales with the number of elements: style recalculation must match selectors against elements, layout must compute boxes, paint must record them, and memory grows with each node. Lighthouse warns at around 800 body elements and flags pages above roughly 1,400 elements; it also reports maximum depth and the largest number of children under one parent. Pages with 5,000–20,000 elements are common on e-commerce category pages, admin dashboards and long feeds, and they show consistently worse INP.",[15,726,727,728,731,732,735,736,739,740,743,744,747],{},"The main techniques are: ",[207,729,730],{},"virtualization"," (render only the rows of a long list that are near the viewport, recycling elements as the user scrolls), ",[207,733,734],{},"pagination or \"load more\""," instead of infinite DOM growth, ",[207,737,738],{},"deferring hidden UI"," (menus, modals and tabs rendered only when opened), and ",[207,741,742],{},"simplifying markup"," (removing wrapper divs that exist only for styling, which modern layout with grid and flex rarely needs). Selector complexity matters less than element count in modern engines, but style invalidation patterns — such as toggling a class on ",[239,745,746],{},"body"," that affects thousands of descendants — can still create very large recalculations.",[15,749,750,751,753],{},"The guides in ",[25,752,48],{"href":47}," cover virtualizing lists in React, fixing the excessive DOM size warning, and reducing style recalculation cost.",[15,755,756],{},[53,757,60,760,60,763,60,766,60,768,60,771,60,773,60,781,60,786,60,790,60,794,60,798,60,802,60,806,60,810,60,814,60,818,60,822,60,826,60,828,60],{"viewBox":758,"width":56,"role":57,"ariaLabel":759,"style":59},"0 0 760 216","Bar chart of field INP at the 75th percentile across pages grouped by number of DOM elements.",[62,761],{"className":762,"x":66,"y":66,"width":56,"height":56,"fill":67},[65],[69,764,765],{},"INP p75 by DOM size band (one retail site, field data)",[73,767,759],{},[62,769],{"x":95,"y":95,"width":96,"height":770,"rx":98,"fill":99,"stroke":91,"style":100},"214",[102,772,765],{"x":104,"y":105,"fill":91,"style":106},[178,774],{"x1":775,"y1":776,"x2":775,"y2":777,"stroke":145,"strokeWidth":183,"strokeDashArray":778,"style":780},"397.7","72.0","194.0",[779,779],"4","stroke-opacity:0.9",[102,782,785],{"x":783,"y":784,"fill":91,"style":524,"textAnchor":525},"128.3","92.0","\u003C 1,000 elements",[62,787],{"x":788,"y":118,"width":789,"height":531,"rx":532,"fill":144,"stroke":145,"style":560},"140.3","180.2",[102,791,793],{"x":792,"y":784,"fill":91,"style":537},"326.5","140ms",[102,795,797],{"x":783,"y":796,"fill":91,"style":524,"textAnchor":525},"123.0","1,000-3,000",[62,799],{"x":788,"y":800,"width":801,"height":531,"rx":532,"fill":113,"stroke":113,"style":547},"109.0","244.5",[102,803,805],{"x":804,"y":796,"fill":91,"style":537},"390.9","190ms",[102,807,809],{"x":783,"y":808,"fill":91,"style":524,"textAnchor":525},"154.0","3,000-8,000",[62,811],{"x":788,"y":812,"width":813,"height":531,"rx":532,"fill":144,"stroke":145,"style":560},"140.0","347.5",[102,815,817],{"x":816,"y":808,"fill":91,"style":537},"493.8","270ms",[102,819,821],{"x":783,"y":820,"fill":91,"style":524,"textAnchor":525},"185.0","> 8,000",[62,823],{"x":788,"y":824,"width":825,"height":531,"rx":532,"fill":99,"stroke":91,"style":533},"171.0","527.7",[102,827,538],{"x":536,"y":820,"fill":91,"style":537},[102,829,833],{"x":775,"y":830,"fill":831,"style":832,"textAnchor":120},"68.0","#8a6608","font-size:12px;font-weight:700","INP good threshold",[199,835,837],{"id":836},"monitoring-and-ci-keeping-rendering-costs-in-budget","Monitoring and CI: Keeping Rendering Costs in Budget",[15,839,840,841,843],{},"Rendering regressions arrive quietly: a new component library adds 40KB of CSS, a product grid doubles its DOM, a carousel starts animating ",[239,842,669],{},". Guard against them with a few automated checks.",[15,845,846,847,850],{},"In CI, track CSS bytes (total and render-blocking) with a bundle size budget, and run Lighthouse on key templates with assertions on DOM size, render-blocking resources and total blocking time. Lab rendering times are noisy, but DOM element counts and CSS sizes are deterministic and make excellent budget metrics. In the field, attribute INP to interaction targets with the Long Animation Frames API: its script attribution and ",[239,848,849],{},"styleAndLayoutStart"," timing reveal how much of a slow frame was style and layout rather than script. Segment INP by template and DOM size band to see whether a rendering change moved the metric.",[15,852,853],{},"For animations, record smoothness in the lab with the Performance panel's frames track, and watch for dropped frames during key interactions such as opening menus or scrolling feeds. For CLS, layout shift attribution identifies elements that moved; animations of layout properties often appear here.",[199,855,857],{"id":856},"reference-implementations","Reference Implementations",[859,860,862],"h3",{"id":861},"inlining-critical-css-and-loading-the-rest-asynchronously","Inlining critical CSS and loading the rest asynchronously",[864,865,870],"pre",{"className":866,"code":867,"language":868,"meta":869,"style":869},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Chead>\n  \u003Cstyle>\u002F* critical rules for header, hero and above-the-fold layout (~8KB) *\u002F\u003C\u002Fstyle>\n  \u003Clink rel=\"preload\" href=\"\u002Fcss\u002Fsite.4f2a1c.css\" as=\"style\">\n  \u003Clink rel=\"stylesheet\" href=\"\u002Fcss\u002Fsite.4f2a1c.css\" media=\"print\" onload=\"this.media='all'\">\n  \u003Cnoscript>\u003Clink rel=\"stylesheet\" href=\"\u002Fcss\u002Fsite.4f2a1c.css\">\u003C\u002Fnoscript>\n\u003C\u002Fhead>\n\u003C!-- trade-off: if the critical subset misses rules for visible content, users see\n     a flash of unstyled content and layout shifts when the full CSS applies. -->\n","html","",[239,871,872,887,910,947,999,1031,1040,1046],{"__ignoreMap":869},[873,874,876,880,884],"span",{"class":178,"line":875},1,[873,877,879],{"class":878},"saISM","\u003C",[873,881,883],{"class":882},"sZBmE","head",[873,885,886],{"class":878},">\n",[873,888,890,893,896,899,903,906,908],{"class":178,"line":889},2,[873,891,892],{"class":878},"  \u003C",[873,894,895],{"class":882},"style",[873,897,898],{"class":878},">",[873,900,902],{"class":901},"sjfSM","\u002F* critical rules for header, hero and above-the-fold layout (~8KB) *\u002F",[873,904,905],{"class":878},"\u003C\u002F",[873,907,895],{"class":882},[873,909,886],{"class":878},[873,911,913,915,918,922,925,929,932,934,937,940,942,945],{"class":178,"line":912},3,[873,914,892],{"class":878},[873,916,917],{"class":882},"link",[873,919,921],{"class":920},"sPXB4"," rel",[873,923,924],{"class":878},"=",[873,926,928],{"class":927},"sZ8jY","\"preload\"",[873,930,931],{"class":920}," href",[873,933,924],{"class":878},[873,935,936],{"class":927},"\"\u002Fcss\u002Fsite.4f2a1c.css\"",[873,938,939],{"class":920}," as",[873,941,924],{"class":878},[873,943,944],{"class":927},"\"style\"",[873,946,886],{"class":878},[873,948,950,952,954,956,958,961,963,965,967,970,972,975,978,980,983,986,989,991,994,997],{"class":178,"line":949},4,[873,951,892],{"class":878},[873,953,917],{"class":882},[873,955,921],{"class":920},[873,957,924],{"class":878},[873,959,960],{"class":927},"\"stylesheet\"",[873,962,931],{"class":920},[873,964,924],{"class":878},[873,966,936],{"class":927},[873,968,969],{"class":920}," media",[873,971,924],{"class":878},[873,973,974],{"class":927},"\"print\"",[873,976,977],{"class":920}," onload",[873,979,924],{"class":878},[873,981,982],{"class":927},"\"",[873,984,985],{"class":920},"this",[873,987,988],{"class":927},".",[873,990,424],{"class":878},[873,992,924],{"class":993},"sPARh",[873,995,996],{"class":927},"'all'\"",[873,998,886],{"class":878},[873,1000,1002,1004,1007,1010,1012,1014,1016,1018,1020,1022,1024,1027,1029],{"class":178,"line":1001},5,[873,1003,892],{"class":878},[873,1005,1006],{"class":882},"noscript",[873,1008,1009],{"class":878},">\u003C",[873,1011,917],{"class":882},[873,1013,921],{"class":920},[873,1015,924],{"class":878},[873,1017,960],{"class":927},[873,1019,931],{"class":920},[873,1021,924],{"class":878},[873,1023,936],{"class":927},[873,1025,1026],{"class":878},">\u003C\u002F",[873,1028,1006],{"class":882},[873,1030,886],{"class":878},[873,1032,1034,1036,1038],{"class":178,"line":1033},6,[873,1035,905],{"class":878},[873,1037,883],{"class":882},[873,1039,886],{"class":878},[873,1041,1043],{"class":178,"line":1042},7,[873,1044,1045],{"class":901},"\u003C!-- trade-off: if the critical subset misses rules for visible content, users see\n",[873,1047,1049],{"class":178,"line":1048},8,[873,1050,1051],{"class":901},"     a flash of unstyled content and layout shifts when the full CSS applies. -->\n",[859,1053,1055],{"id":1054},"skipping-offscreen-rendering-on-long-pages","Skipping offscreen rendering on long pages",[864,1057,1061],{"className":1058,"code":1059,"language":1060,"meta":869,"style":869},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast",".article-section {\n  content-visibility: auto;\n  contain-intrinsic-size: auto 900px;   \u002F* remembered after first render *\u002F\n}\n\u002F* trade-off: in-page find and anchor links still work, but rendering on scroll\n   costs a little time per section; avoid on sections just below the fold. *\u002F\n","css",[239,1062,1063,1071,1085,1106,1111,1116],{"__ignoreMap":869},[873,1064,1065,1068],{"class":178,"line":875},[873,1066,1067],{"class":920},".article-section",[873,1069,1070],{"class":878}," {\n",[873,1072,1073,1076,1079,1082],{"class":178,"line":889},[873,1074,1075],{"class":920},"  content-visibility",[873,1077,1078],{"class":878},": ",[873,1080,1081],{"class":920},"auto",[873,1083,1084],{"class":878},";\n",[873,1086,1087,1090,1092,1094,1097,1100,1103],{"class":178,"line":912},[873,1088,1089],{"class":920},"  contain-intrinsic-size",[873,1091,1078],{"class":878},[873,1093,1081],{"class":920},[873,1095,1096],{"class":920}," 900",[873,1098,1099],{"class":993},"px",[873,1101,1102],{"class":878},";   ",[873,1104,1105],{"class":901},"\u002F* remembered after first render *\u002F\n",[873,1107,1108],{"class":178,"line":949},[873,1109,1110],{"class":878},"}\n",[873,1112,1113],{"class":178,"line":1001},[873,1114,1115],{"class":901},"\u002F* trade-off: in-page find and anchor links still work, but rendering on scroll\n",[873,1117,1118],{"class":178,"line":1033},[873,1119,1120],{"class":901},"   costs a little time per section; avoid on sections just below the fold. *\u002F\n",[859,1122,1124],{"id":1123},"batching-dom-reads-and-writes","Batching DOM reads and writes",[864,1126,1130],{"className":1127,"code":1128,"language":1129,"meta":869,"style":869},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","function layoutTooltips(tooltips) {\n  \u002F\u002F Read phase: gather geometry once.\n  const rects = tooltips.map((t) => t.anchor.getBoundingClientRect());\n  \u002F\u002F Write phase: apply all positions together, in the next frame.\n  requestAnimationFrame(() => {\n    tooltips.forEach((t, i) => {\n      t.el.style.transform = `translate(${rects[i].left}px, ${rects[i].bottom + 8}px)`;\n    });\n  });\n}\n\u002F\u002F trade-off: deferring writes to the next frame adds up to one frame of latency;\n\u002F\u002F for tooltips this is invisible, for drag handles it may not be.\n","javascript",[239,1131,1132,1151,1156,1193,1198,1210,1233,1281,1286,1292,1297,1303],{"__ignoreMap":869},[873,1133,1134,1137,1141,1144,1148],{"class":178,"line":875},[873,1135,1136],{"class":993},"function",[873,1138,1140],{"class":1139},"smZ65"," layoutTooltips",[873,1142,1143],{"class":878},"(",[873,1145,1147],{"class":1146},"sQw3B","tooltips",[873,1149,1150],{"class":878},") {\n",[873,1152,1153],{"class":178,"line":889},[873,1154,1155],{"class":901},"  \u002F\u002F Read phase: gather geometry once.\n",[873,1157,1158,1161,1164,1167,1170,1173,1176,1179,1182,1185,1188,1190],{"class":178,"line":912},[873,1159,1160],{"class":993},"  const",[873,1162,1163],{"class":920}," rects",[873,1165,1166],{"class":993}," =",[873,1168,1169],{"class":878}," tooltips.",[873,1171,1172],{"class":1139},"map",[873,1174,1175],{"class":878},"((",[873,1177,1178],{"class":1146},"t",[873,1180,1181],{"class":878},") ",[873,1183,1184],{"class":993},"=>",[873,1186,1187],{"class":878}," t.anchor.",[873,1189,255],{"class":1139},[873,1191,1192],{"class":878},"());\n",[873,1194,1195],{"class":178,"line":949},[873,1196,1197],{"class":901},"  \u002F\u002F Write phase: apply all positions together, in the next frame.\n",[873,1199,1200,1203,1206,1208],{"class":178,"line":1001},[873,1201,1202],{"class":1139},"  requestAnimationFrame",[873,1204,1205],{"class":878},"(() ",[873,1207,1184],{"class":993},[873,1209,1070],{"class":878},[873,1211,1212,1215,1218,1220,1222,1224,1227,1229,1231],{"class":178,"line":1033},[873,1213,1214],{"class":878},"    tooltips.",[873,1216,1217],{"class":1139},"forEach",[873,1219,1175],{"class":878},[873,1221,1178],{"class":1146},[873,1223,252],{"class":878},[873,1225,1226],{"class":1146},"i",[873,1228,1181],{"class":878},[873,1230,1184],{"class":993},[873,1232,1070],{"class":878},[873,1234,1235,1238,1240,1243,1246,1249,1251,1254,1256,1259,1261,1263,1265,1267,1270,1273,1276,1279],{"class":178,"line":1042},[873,1236,1237],{"class":878},"      t.el.style.transform ",[873,1239,924],{"class":993},[873,1241,1242],{"class":927}," `translate(${",[873,1244,1245],{"class":878},"rects",[873,1247,1248],{"class":927},"[",[873,1250,1226],{"class":878},[873,1252,1253],{"class":927},"].",[873,1255,669],{"class":878},[873,1257,1258],{"class":927},"}px, ${",[873,1260,1245],{"class":878},[873,1262,1248],{"class":927},[873,1264,1226],{"class":878},[873,1266,1253],{"class":927},[873,1268,1269],{"class":878},"bottom",[873,1271,1272],{"class":993}," +",[873,1274,1275],{"class":920}," 8",[873,1277,1278],{"class":927},"}px)`",[873,1280,1084],{"class":878},[873,1282,1283],{"class":178,"line":1048},[873,1284,1285],{"class":878},"    });\n",[873,1287,1289],{"class":178,"line":1288},9,[873,1290,1291],{"class":878},"  });\n",[873,1293,1295],{"class":178,"line":1294},10,[873,1296,1110],{"class":878},[873,1298,1300],{"class":178,"line":1299},11,[873,1301,1302],{"class":901},"\u002F\u002F trade-off: deferring writes to the next frame adds up to one frame of latency;\n",[873,1304,1306],{"class":178,"line":1305},12,[873,1307,1308],{"class":901},"\u002F\u002F for tooltips this is invisible, for drag handles it may not be.\n",[859,1310,1312],{"id":1311},"compositor-friendly-animation","Compositor-friendly animation",[864,1314,1316],{"className":1058,"code":1315,"language":1060,"meta":869,"style":869},".drawer { transform: translateX(-100%); transition: transform 240ms ease-out; }\n.drawer.open { transform: translateX(0); }\n\u002F* Not: transition: left 240ms — that runs layout on every frame. *\u002F\n@media (prefers-reduced-motion: reduce) { .drawer { transition: none; } }\n\u002F* trade-off: transformed elements keep their original layout box, so content\n   behind the drawer does not reflow; design the layout with that in mind. *\u002F\n",[239,1317,1318,1362,1382,1387,1408,1413],{"__ignoreMap":869},[873,1319,1320,1323,1326,1328,1330,1333,1335,1338,1341,1344,1347,1350,1353,1356,1359],{"class":178,"line":875},[873,1321,1322],{"class":920},".drawer",[873,1324,1325],{"class":878}," { ",[873,1327,265],{"class":920},[873,1329,1078],{"class":878},[873,1331,1332],{"class":920},"translateX",[873,1334,1143],{"class":878},[873,1336,1337],{"class":920},"-100",[873,1339,1340],{"class":993},"%",[873,1342,1343],{"class":878},"); ",[873,1345,1346],{"class":920},"transition",[873,1348,1349],{"class":878},": transform ",[873,1351,1352],{"class":920},"240",[873,1354,1355],{"class":993},"ms",[873,1357,1358],{"class":920}," ease-out",[873,1360,1361],{"class":878},"; }\n",[873,1363,1364,1367,1369,1371,1373,1375,1377,1379],{"class":178,"line":889},[873,1365,1366],{"class":920},".drawer.open",[873,1368,1325],{"class":878},[873,1370,265],{"class":920},[873,1372,1078],{"class":878},[873,1374,1332],{"class":920},[873,1376,1143],{"class":878},[873,1378,66],{"class":920},[873,1380,1381],{"class":878},"); }\n",[873,1383,1384],{"class":178,"line":912},[873,1385,1386],{"class":901},"\u002F* Not: transition: left 240ms — that runs layout on every frame. *\u002F\n",[873,1388,1389,1392,1395,1397,1399,1401,1403,1405],{"class":178,"line":949},[873,1390,1391],{"class":993},"@media",[873,1393,1394],{"class":878}," (prefers-reduced-motion: reduce) { ",[873,1396,1322],{"class":920},[873,1398,1325],{"class":878},[873,1400,1346],{"class":920},[873,1402,1078],{"class":878},[873,1404,99],{"class":920},[873,1406,1407],{"class":878},"; } }\n",[873,1409,1410],{"class":178,"line":1001},[873,1411,1412],{"class":901},"\u002F* trade-off: transformed elements keep their original layout box, so content\n",[873,1414,1415],{"class":178,"line":1033},[873,1416,1417],{"class":901},"   behind the drawer does not reflow; design the layout with that in mind. *\u002F\n",[859,1419,1421],{"id":1420},"virtualizing-a-long-list","Virtualizing a long list",[864,1423,1425],{"className":1127,"code":1424,"language":1129,"meta":869,"style":869},"import { useVirtualizer } from '@tanstack\u002Freact-virtual';\nfunction OrderList({ orders }) {\n  const parentRef = React.useRef(null);\n  const v = useVirtualizer({ count: orders.length, getScrollElement: () => parentRef.current, estimateSize: () => 56, overscan: 8 });\n  return (\n    \u003Cdiv ref={parentRef} style={{ height: 600, overflow: 'auto' }}>\n      \u003Cdiv style={{ height: v.getTotalSize(), position: 'relative' }}>\n        {v.getVirtualItems().map((row) => (\n          \u003Cdiv key={row.key} style={{ position: 'absolute', top: 0, transform: `translateY(${row.start}px)`, height: 56, width: '100%' }}>\n            {orders[row.index].number}\n          \u003C\u002Fdiv>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  );\n}\n\u002F\u002F trade-off: virtualized rows are not in the DOM, so browser find-in-page and some\n\u002F\u002F assistive technologies cannot see them; provide search and pagination as well.\n",[239,1426,1427,1443,1459,1482,1532,1540,1584,1613,1640,1704,1714,1723,1730,1740,1750,1756,1761,1767],{"__ignoreMap":869},[873,1428,1429,1432,1435,1438,1441],{"class":178,"line":875},[873,1430,1431],{"class":993},"import",[873,1433,1434],{"class":878}," { useVirtualizer } ",[873,1436,1437],{"class":993},"from",[873,1439,1440],{"class":927}," '@tanstack\u002Freact-virtual'",[873,1442,1084],{"class":878},[873,1444,1445,1447,1450,1453,1456],{"class":178,"line":889},[873,1446,1136],{"class":993},[873,1448,1449],{"class":1139}," OrderList",[873,1451,1452],{"class":878},"({ ",[873,1454,1455],{"class":1146},"orders",[873,1457,1458],{"class":878}," }) {\n",[873,1460,1461,1463,1466,1468,1471,1474,1476,1479],{"class":178,"line":912},[873,1462,1160],{"class":993},[873,1464,1465],{"class":920}," parentRef",[873,1467,1166],{"class":993},[873,1469,1470],{"class":878}," React.",[873,1472,1473],{"class":1139},"useRef",[873,1475,1143],{"class":878},[873,1477,1478],{"class":920},"null",[873,1480,1481],{"class":878},");\n",[873,1483,1484,1486,1489,1491,1494,1497,1500,1502,1505,1508,1510,1513,1516,1518,1520,1523,1526,1529],{"class":178,"line":949},[873,1485,1160],{"class":993},[873,1487,1488],{"class":920}," v",[873,1490,1166],{"class":993},[873,1492,1493],{"class":1139}," useVirtualizer",[873,1495,1496],{"class":878},"({ count: orders.",[873,1498,1499],{"class":920},"length",[873,1501,252],{"class":878},[873,1503,1504],{"class":1139},"getScrollElement",[873,1506,1507],{"class":878},": () ",[873,1509,1184],{"class":993},[873,1511,1512],{"class":878}," parentRef.current, ",[873,1514,1515],{"class":1139},"estimateSize",[873,1517,1507],{"class":878},[873,1519,1184],{"class":993},[873,1521,1522],{"class":920}," 56",[873,1524,1525],{"class":878},", overscan: ",[873,1527,1528],{"class":920},"8",[873,1530,1531],{"class":878}," });\n",[873,1533,1534,1537],{"class":178,"line":1001},[873,1535,1536],{"class":993},"  return",[873,1538,1539],{"class":878}," (\n",[873,1541,1542,1545,1548,1551,1554,1557,1560,1563,1565,1568,1571,1574,1577,1580,1582],{"class":178,"line":1033},[873,1543,1544],{"class":878},"    \u003C",[873,1546,1547],{"class":882},"div",[873,1549,1550],{"class":920}," ref",[873,1552,1553],{"class":993},"={",[873,1555,1556],{"class":878},"parentRef",[873,1558,1559],{"class":993},"}",[873,1561,1562],{"class":920}," style",[873,1564,1553],{"class":993},[873,1566,1567],{"class":878},"{ height: ",[873,1569,1570],{"class":920},"600",[873,1572,1573],{"class":878},", overflow: ",[873,1575,1576],{"class":927},"'auto'",[873,1578,1579],{"class":878}," }",[873,1581,1559],{"class":993},[873,1583,886],{"class":878},[873,1585,1586,1589,1591,1593,1595,1598,1601,1604,1607,1609,1611],{"class":178,"line":1042},[873,1587,1588],{"class":878},"      \u003C",[873,1590,1547],{"class":882},[873,1592,1562],{"class":920},[873,1594,1553],{"class":993},[873,1596,1597],{"class":878},"{ height: v.",[873,1599,1600],{"class":1139},"getTotalSize",[873,1602,1603],{"class":878},"(), position: ",[873,1605,1606],{"class":927},"'relative'",[873,1608,1579],{"class":878},[873,1610,1559],{"class":993},[873,1612,886],{"class":878},[873,1614,1615,1618,1621,1624,1627,1629,1631,1634,1636,1638],{"class":178,"line":1048},[873,1616,1617],{"class":993},"        {",[873,1619,1620],{"class":878},"v.",[873,1622,1623],{"class":1139},"getVirtualItems",[873,1625,1626],{"class":878},"().",[873,1628,1172],{"class":1139},[873,1630,1175],{"class":878},[873,1632,1633],{"class":1146},"row",[873,1635,1181],{"class":878},[873,1637,1184],{"class":993},[873,1639,1539],{"class":878},[873,1641,1642,1645,1647,1650,1652,1655,1657,1659,1661,1664,1667,1670,1672,1675,1678,1680,1682,1684,1686,1689,1692,1695,1698,1700,1702],{"class":178,"line":1288},[873,1643,1644],{"class":878},"          \u003C",[873,1646,1547],{"class":882},[873,1648,1649],{"class":920}," key",[873,1651,1553],{"class":993},[873,1653,1654],{"class":878},"row.key",[873,1656,1559],{"class":993},[873,1658,1562],{"class":920},[873,1660,1553],{"class":993},[873,1662,1663],{"class":878},"{ position: ",[873,1665,1666],{"class":927},"'absolute'",[873,1668,1669],{"class":878},", top: ",[873,1671,66],{"class":920},[873,1673,1674],{"class":878},", transform: ",[873,1676,1677],{"class":927},"`translateY(${",[873,1679,1633],{"class":878},[873,1681,988],{"class":927},[873,1683,303],{"class":878},[873,1685,1278],{"class":927},[873,1687,1688],{"class":878},", height: ",[873,1690,1691],{"class":920},"56",[873,1693,1694],{"class":878},", width: ",[873,1696,1697],{"class":927},"'100%'",[873,1699,1579],{"class":878},[873,1701,1559],{"class":993},[873,1703,886],{"class":878},[873,1705,1706,1709,1712],{"class":178,"line":1294},[873,1707,1708],{"class":993},"            {",[873,1710,1711],{"class":878},"orders[row.index].number",[873,1713,1110],{"class":993},[873,1715,1716,1719,1721],{"class":178,"line":1299},[873,1717,1718],{"class":878},"          \u003C\u002F",[873,1720,1547],{"class":882},[873,1722,886],{"class":878},[873,1724,1725,1728],{"class":178,"line":1305},[873,1726,1727],{"class":878},"        ))",[873,1729,1110],{"class":993},[873,1731,1733,1736,1738],{"class":178,"line":1732},13,[873,1734,1735],{"class":878},"      \u003C\u002F",[873,1737,1547],{"class":882},[873,1739,886],{"class":878},[873,1741,1743,1746,1748],{"class":178,"line":1742},14,[873,1744,1745],{"class":878},"    \u003C\u002F",[873,1747,1547],{"class":882},[873,1749,886],{"class":878},[873,1751,1753],{"class":178,"line":1752},15,[873,1754,1755],{"class":878},"  );\n",[873,1757,1759],{"class":178,"line":1758},16,[873,1760,1110],{"class":878},[873,1762,1764],{"class":178,"line":1763},17,[873,1765,1766],{"class":901},"\u002F\u002F trade-off: virtualized rows are not in the DOM, so browser find-in-page and some\n",[873,1768,1770],{"class":178,"line":1769},18,[873,1771,1772],{"class":901},"\u002F\u002F assistive technologies cannot see them; provide search and pagination as well.\n",[199,1774,1776],{"id":1775},"common-pitfalls","Common Pitfalls",[223,1778,1779,1785,1794,1800,1809,1818,1824,1833],{},[226,1780,1781,1784],{},[207,1782,1783],{},"Inlining all CSS."," Critical CSS should be a small subset; inlining a 150KB stylesheet delays the HTML instead of the CSS.",[226,1786,1787,1793],{},[207,1788,1789,1790,1792],{},"Applying ",[239,1791,482],{}," to above-the-fold content."," It adds work at the moment it is first rendered without any skipped work to gain.",[226,1795,1796,1799],{},[207,1797,1798],{},"Measuring layout inside loops."," Every read after a write forces layout.",[226,1801,1802,1808],{},[207,1803,1804,1807],{},[239,1805,1806],{},"will-change"," on everything."," Layer explosion consumes memory and slows compositing.",[226,1810,1811,1817],{},[207,1812,1813,1814,1816],{},"Animating ",[239,1815,663],{}," for accordions."," Use transforms, or accept layout cost and keep the affected subtree small with containment.",[226,1819,1820,1823],{},[207,1821,1822],{},"Rendering every list item."," Thousands of rows multiply the cost of every style and layout operation.",[226,1825,1826,1832],{},[207,1827,1828,1829,1831],{},"Toggling theme or state classes on ",[239,1830,746],{}," without containment."," Very large style invalidations on every toggle.",[226,1834,1835,1838],{},[207,1836,1837],{},"Ignoring rendering in INP investigations."," Handlers may be fast while the rendering they trigger is slow.",[199,1840,1842],{"id":1841},"faq","FAQ",[1844,1845,1848,1852],"details",{"className":1846},[1847],"faq-item",[1849,1850,1851],"summary",{},"Is CSS really a performance problem if it is small?",[15,1853,1854],{},"Delivery is mostly about size and blocking; once CSS is small and non-blocking, its runtime cost depends on how styles are applied — how many elements match, how often classes change, and what properties change. A small stylesheet on a huge DOM can still be expensive to recalculate.",[1844,1856,1858,1861],{"className":1857},[1847],[1849,1859,1860],{},"Do CSS selectors still matter for performance?",[15,1862,1863],{},"Much less than they used to. Modern engines match selectors very efficiently. Element count and the scope of style invalidations matter far more than whether you use descendant or class selectors.",[1844,1865,1867,1870],{"className":1866},[1847],[1849,1868,1869],{},"How does rendering affect INP?",[15,1871,1872],{},"INP measures until the next frame is painted after an interaction. If the event handler changes styles that require recalculating style and layout for thousands of elements, that work is part of the interaction's presentation delay, even if the handler's JavaScript is fast.",[1844,1874,1876,1879],{"className":1875},[1847],[1849,1877,1878],{},"Should I use content-visibility everywhere?",[15,1880,1881],{},"No. It helps on long pages with substantial offscreen content. For short pages or above-the-fold sections it adds overhead without benefit, and wrong intrinsic sizes can cause scrollbar jumps.",[1844,1883,1885,1888],{"className":1884},[1847],[1849,1886,1887],{},"Is JavaScript animation always slower than CSS animation?",[15,1889,1890,1891,266,1893,1895,1896,1898],{},"Not inherently. The Web Animations API animating ",[239,1892,265],{},[239,1894,269],{}," can run on the compositor just like CSS. JavaScript animation that updates styles every frame with ",[239,1897,596],{}," runs on the main thread and can stutter when the thread is busy.",[1844,1900,1902,1905],{"className":1901},[1847],[1849,1903,1904],{},"What DOM size should I aim for?",[15,1906,1907],{},"Below about 1,500 elements on initial load is a reasonable target for most pages; Lighthouse warns above roughly 800 and flags pages above about 1,400. Applications can exceed that if they virtualize long lists and keep updates scoped.",[1844,1909,1911,1914],{"className":1910},[1847],[1849,1912,1913],{},"Does CSS-in-JS hurt performance?",[15,1915,1916],{},"Runtime CSS-in-JS adds scripting to generate styles and style recalculation when it injects them, especially during hydration and re-renders. Zero-runtime or compile-time approaches avoid most of the cost while keeping a component-oriented authoring style.",[199,1918,1920],{"id":1919},"topics-in-this-section","Topics in This Section",[223,1922,1923,1929,1935,1941,1947],{},[226,1924,1925,1928],{},[25,1926,1927],{"href":27},"Critical CSS & render-blocking resources"," — painting the first frame without waiting for stylesheets.",[226,1930,1931,1934],{},[25,1932,1933],{"href":32},"CSS containment & content-visibility"," — limiting and skipping rendering work.",[226,1936,1937,1940],{},[25,1938,1939],{"href":37},"Layout thrashing & forced reflow"," — JavaScript patterns that make layout run repeatedly.",[226,1942,1943,1946],{},[25,1944,1945],{"href":42},"Animation & compositing performance"," — smooth motion off the main thread.",[226,1948,1949,1952],{},[25,1950,1951],{"href":47},"DOM size & list virtualization"," — controlling the element count behind every rendering cost.",[199,1954,1956],{"id":1955},"related","Related",[223,1958,1959,1966,1973],{},[226,1960,1961,1965],{},[25,1962,1964],{"href":1963},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals measurement"," — the metrics rendering work moves.",[226,1967,1968,1972],{},[25,1969,1971],{"href":1970},"\u002Fjavascript-bundle-optimization-code-splitting\u002F","JavaScript bundle optimization"," — the scripting side of main-thread cost.",[226,1974,1975,1979],{},[25,1976,1978],{"href":1977},"\u002Fimage-media-optimization\u002F","Image & media optimization"," — the largest bytes on most pages.",[1981,1982,1984],"script",{"type":1983},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Rendering & CSS Performance: Making Style, Layout and Paint Cheap\",\n  \"description\": \"A production guide to the browser rendering pipeline — style, layout, paint and composite — and how CSS and DOM decisions show up in LCP, INP and CLS.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002F\"\n  }\n}\n",[1981,1986,1987],{"type":1983},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Rendering & CSS Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002F\"\n    }\n  ]\n}\n",[1981,1989,1990],{"type":1983},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is CSS really a performance problem if it is small?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Delivery is mostly about size and blocking; once CSS is small and non-blocking, its runtime cost depends on how styles are applied — how many elements match, how often classes change, and what properties change. A small stylesheet on a huge DOM can still be expensive to recalculate.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Do CSS selectors still matter for performance?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Much less than they used to. Modern engines match selectors very efficiently. Element count and the scope of style invalidations matter far more than whether you use descendant or class selectors.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How does rendering affect INP?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"INP measures until the next frame is painted after an interaction. If the event handler changes styles that require recalculating style and layout for thousands of elements, that work is part of the interaction's presentation delay, even if the handler's JavaScript is fast.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Should I use content-visibility everywhere?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"No. It helps on long pages with substantial offscreen content. For short pages or above-the-fold sections it adds overhead without benefit, and wrong intrinsic sizes can cause scrollbar jumps.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Is JavaScript animation always slower than CSS animation?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Not inherently. The Web Animations API animating transform and opacity can run on the compositor just like CSS. JavaScript animation that updates styles every frame with requestAnimationFrame runs on the main thread and can stutter when the thread is busy.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"What DOM size should I aim for?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Below about 1,500 elements on initial load is a reasonable target for most pages; Lighthouse warns above roughly 800 and flags pages above about 1,400. Applications can exceed that if they virtualize long lists and keep updates scoped.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Does CSS-in-JS hurt performance?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Runtime CSS-in-JS adds scripting to generate styles and style recalculation when it injects them, especially during hydration and re-renders. Zero-runtime or compile-time approaches avoid most of the cost while keeping a component-oriented authoring style.\"\n      }\n    }\n  ]\n}\n",[895,1992,1993],{},"html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":869,"searchDepth":889,"depth":889,"links":1995},[1996,1997,1998,1999,2000,2001,2002,2003,2010,2011,2012,2013],{"id":201,"depth":889,"text":202},{"id":417,"depth":889,"text":418},{"id":461,"depth":889,"text":462},{"id":573,"depth":889,"text":574},{"id":653,"depth":889,"text":654},{"id":720,"depth":889,"text":721},{"id":836,"depth":889,"text":837},{"id":856,"depth":889,"text":857,"children":2004},[2005,2006,2007,2008,2009],{"id":861,"depth":912,"text":862},{"id":1054,"depth":912,"text":1055},{"id":1123,"depth":912,"text":1124},{"id":1311,"depth":912,"text":1312},{"id":1420,"depth":912,"text":1421},{"id":1775,"depth":889,"text":1776},{"id":1841,"depth":889,"text":1842},{"id":1919,"depth":889,"text":1920},{"id":1955,"depth":889,"text":1956},"A production guide to the browser rendering pipeline — style, layout, paint and composite — and how CSS and DOM decisions show up in LCP, INP and CLS.","md",{"slug":2017,"type":2018,"breadcrumb":2019,"datePublished":2025,"dateModified":2025},"rendering-css-performance","guide",[2020,2023],{"name":2021,"url":2022},"Home","\u002F",{"name":5,"url":2024},"\u002Frendering-css-performance\u002F","2026-10-06",true,"\u002Frendering-css-performance",{"title":2029,"description":2030},"Rendering & CSS Performance: Style, Layout, Paint","Make the browser’s rendering pipeline fast: critical CSS, containment, layout thrashing, compositor-only animation and DOM size, measured against LCP, INP and CLS.","rendering-css-performance\u002Findex","xa3VLvOFrtYi4rdnEqAej7O-iaP7Knl9FkiYe7r9RoA",[2034,2038],{"title":2035,"path":2036,"stem":2037,"children":-1},"Fixing TTFB from Serverless Cold Starts","\u002Fnetwork-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Ffixing-ttfb-from-serverless-cold-starts","network-protocol-optimization\u002Ftime-to-first-byte-optimization\u002Ffixing-ttfb-from-serverless-cold-starts\u002Findex",{"title":2039,"path":2040,"stem":2041,"children":-1},"Animation & Compositing Performance","\u002Frendering-css-performance\u002Fanimation-and-compositing-performance","rendering-css-performance\u002Fanimation-and-compositing-performance\u002Findex",1791308069636]