[{"data":1,"prerenderedAt":1379},["ShallowReactive",2],{"page-\u002Fcss\u002F19-performance":3},{"id":4,"title":5,"body":6,"description":1372,"extension":1373,"meta":1374,"navigation":384,"path":1375,"seo":1376,"stem":1377,"__hash__":1378},"content\u002Fcss\u002F19-performance.md","19 — CSS Performance: Render Path, Containment & GPU Layers",{"type":7,"value":8,"toc":1353},"minimark",[9,13,35,40,247,251,308,312,464,468,478,483,549,719,726,792,799,841,849,905,909,1000,1046,1050,1079,1083,1172,1176,1254,1258,1307,1350],[10,11,5],"h1",{"id":12},"_19-css-performance-render-path-containment-gpu-layers",[14,15,16,17,21,22,25,26,30,31,34],"p",{},"CSS performance has two domains: ",[18,19,20],"strong",{},"load"," (parse, render-blocking → FCP\u002FLCP) and ",[18,23,24],{},"runtime"," (layout, paint, composite → scroll\u002Finput smoothness). The render pipeline is: style → layout → paint → composite. Animating ",[27,28,29],"code",{},"transform","\u002F",[27,32,33],{},"opacity"," skips layout and paint (compositor-only). Everything else triggers layout or paint — the source of jank.",[36,37,39],"h2",{"id":38},"render-blocking-css-inline-critical-async-the-rest","Render-Blocking CSS — inline critical, async the rest",[41,42,44],"code-wrapper",{"language":43},"html",[45,46,50],"pre",{"className":47,"code":48,"language":43,"meta":49,"style":49},"language-html shiki shiki-themes github-light github-dark","\u003Chead>\n  \u003Cstyle>\n    \u002F* Critical above-the-fold CSS inlined → removes a render-blocking round trip. *\u002F\n    body { font-family: system-ui, sans-serif; }\n    .hero { \u002F* above-the-fold only *\u002F }\n  \u003C\u002Fstyle>\n  \u003C!-- Non-critical CSS: preload without blocking, swap to stylesheet on load. -->\n  \u003Clink rel=\"preload\" href=\"\u002Fassets\u002Fapp.[hash].css\" as=\"style\" onload=\"this.rel='stylesheet'\">\n  \u003Cnoscript>\u003Clink rel=\"stylesheet\" href=\"\u002Fassets\u002Fapp.[hash].css\">\u003C\u002Fnoscript>\n\u003C\u002Fhead>\n","",[27,51,52,68,79,86,114,129,139,145,204,237],{"__ignoreMap":49},[53,54,57,61,65],"span",{"class":55,"line":56},"line",1,[53,58,60],{"class":59},"ssxIu","\u003C",[53,62,64],{"class":63},"sk71V","head",[53,66,67],{"class":59},">\n",[53,69,71,74,77],{"class":55,"line":70},2,[53,72,73],{"class":59},"  \u003C",[53,75,76],{"class":63},"style",[53,78,67],{"class":59},[53,80,82],{"class":55,"line":81},3,[53,83,85],{"class":84},"sdCPZ","    \u002F* Critical above-the-fold CSS inlined → removes a render-blocking round trip. *\u002F\n",[53,87,89,92,95,99,102,105,108,111],{"class":55,"line":88},4,[53,90,91],{"class":63},"    body",[53,93,94],{"class":59}," { ",[53,96,98],{"class":97},"snvgF","font-family",[53,100,101],{"class":59},": ",[53,103,104],{"class":97},"system-ui",[53,106,107],{"class":59},", ",[53,109,110],{"class":97},"sans-serif",[53,112,113],{"class":59},"; }\n",[53,115,117,121,123,126],{"class":55,"line":116},5,[53,118,120],{"class":119},"sIsaT","    .hero",[53,122,94],{"class":59},[53,124,125],{"class":84},"\u002F* above-the-fold only *\u002F",[53,127,128],{"class":59}," }\n",[53,130,132,135,137],{"class":55,"line":131},6,[53,133,134],{"class":59},"  \u003C\u002F",[53,136,76],{"class":63},[53,138,67],{"class":59},[53,140,142],{"class":55,"line":141},7,[53,143,144],{"class":84},"  \u003C!-- Non-critical CSS: preload without blocking, swap to stylesheet on load. -->\n",[53,146,148,150,153,156,159,163,166,168,171,174,176,179,182,184,187,190,193,196,199,202],{"class":55,"line":147},8,[53,149,73],{"class":59},[53,151,152],{"class":63},"link",[53,154,155],{"class":119}," rel",[53,157,158],{"class":59},"=",[53,160,162],{"class":161},"sJ6F3","\"preload\"",[53,164,165],{"class":119}," href",[53,167,158],{"class":59},[53,169,170],{"class":161},"\"\u002Fassets\u002Fapp.[hash].css\"",[53,172,173],{"class":119}," as",[53,175,158],{"class":59},[53,177,178],{"class":161},"\"style\"",[53,180,181],{"class":119}," onload",[53,183,158],{"class":59},[53,185,186],{"class":161},"\"",[53,188,189],{"class":97},"this",[53,191,192],{"class":161},".",[53,194,195],{"class":59},"rel",[53,197,158],{"class":198},"svdQ7",[53,200,201],{"class":161},"'stylesheet'\"",[53,203,67],{"class":59},[53,205,207,209,212,215,217,219,221,224,226,228,230,233,235],{"class":55,"line":206},9,[53,208,73],{"class":59},[53,210,211],{"class":63},"noscript",[53,213,214],{"class":59},">\u003C",[53,216,152],{"class":63},[53,218,155],{"class":119},[53,220,158],{"class":59},[53,222,223],{"class":161},"\"stylesheet\"",[53,225,165],{"class":119},[53,227,158],{"class":59},[53,229,170],{"class":161},[53,231,232],{"class":59},">\u003C\u002F",[53,234,211],{"class":63},[53,236,67],{"class":59},[53,238,240,243,245],{"class":55,"line":239},10,[53,241,242],{"class":59},"\u003C\u002F",[53,244,64],{"class":63},[53,246,67],{"class":59},[36,248,250],{"id":249},"selector-matching-right-to-left","Selector Matching — right-to-left",[41,252,254],{"language":253},"css",[45,255,258],{"className":256,"code":257,"language":253,"meta":49,"style":49},"language-css shiki shiki-themes github-light github-dark","\u002F* The engine matches the RIGHTMOST selector (the \"key\") first, then walks UP the DOM.\n   Deep descendant chains are O(depth) per key match. BEM's flat single-class is O(1). *\u002F\n.nav a { }              \u002F* find every \u003Ca>, check if ancestor is .nav *\u002F\n.card:hover .title { } \u002F* find every .title, check if ancestor is .card AND .card:hover *\u002F\n\u002F* Tips: avoid universal keys (*, [class]); prefer classes over tags; keep chains shallow. *\u002F\n\u002F* In practice, selector perf rarely matters on modern browsers — focus on layout\u002Fpaint costs. *\u002F\n",[27,259,260,265,270,284,298,303],{"__ignoreMap":49},[53,261,262],{"class":55,"line":56},[53,263,264],{"class":84},"\u002F* The engine matches the RIGHTMOST selector (the \"key\") first, then walks UP the DOM.\n",[53,266,267],{"class":55,"line":70},[53,268,269],{"class":84},"   Deep descendant chains are O(depth) per key match. BEM's flat single-class is O(1). *\u002F\n",[53,271,272,275,278,281],{"class":55,"line":81},[53,273,274],{"class":119},".nav",[53,276,277],{"class":63}," a",[53,279,280],{"class":59}," { }              ",[53,282,283],{"class":84},"\u002F* find every \u003Ca>, check if ancestor is .nav *\u002F\n",[53,285,286,289,292,295],{"class":55,"line":88},[53,287,288],{"class":119},".card:hover",[53,290,291],{"class":119}," .title",[53,293,294],{"class":59}," { } ",[53,296,297],{"class":84},"\u002F* find every .title, check if ancestor is .card AND .card:hover *\u002F\n",[53,299,300],{"class":55,"line":116},[53,301,302],{"class":84},"\u002F* Tips: avoid universal keys (*, [class]); prefer classes over tags; keep chains shallow. *\u002F\n",[53,304,305],{"class":55,"line":131},[53,306,307],{"class":84},"\u002F* In practice, selector perf rarely matters on modern browsers — focus on layout\u002Fpaint costs. *\u002F\n",[36,309,311],{"id":310},"layout-thrashing-batch-reads-and-writes","Layout Thrashing — batch reads and writes",[41,313,315],{"language":314},"javascript",[45,316,319],{"className":317,"code":318,"language":314,"meta":49,"style":49},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F ❌ Alternating read (forces sync reflow) and write (schedules layout) in a loop → reflow EACH iteration.\nitems.forEach(item => {\n  item.style.width = item.offsetWidth + 10 + 'px';  \u002F\u002F read (reflow), write (schedule) — reflow per item\n});\n\n\u002F\u002F ✓ Batch all reads first, then all writes → ONE reflow.\nconst widths = items.map(item => item.offsetWidth);  \u002F\u002F all reads (one reflow)\nitems.forEach((item, i) => { item.style.width = widths[i] + 10 + 'px'; });  \u002F\u002F all writes (one layout)\n",[27,320,321,326,347,375,380,386,391,420],{"__ignoreMap":49},[53,322,323],{"class":55,"line":56},[53,324,325],{"class":84},"\u002F\u002F ❌ Alternating read (forces sync reflow) and write (schedules layout) in a loop → reflow EACH iteration.\n",[53,327,328,331,334,337,341,344],{"class":55,"line":70},[53,329,330],{"class":59},"items.",[53,332,333],{"class":119},"forEach",[53,335,336],{"class":59},"(",[53,338,340],{"class":339},"sCrzJ","item",[53,342,343],{"class":198}," =>",[53,345,346],{"class":59}," {\n",[53,348,349,352,354,357,360,363,366,369,372],{"class":55,"line":81},[53,350,351],{"class":59},"  item.style.width ",[53,353,158],{"class":198},[53,355,356],{"class":59}," item.offsetWidth ",[53,358,359],{"class":198},"+",[53,361,362],{"class":97}," 10",[53,364,365],{"class":198}," +",[53,367,368],{"class":161}," 'px'",[53,370,371],{"class":59},";  ",[53,373,374],{"class":84},"\u002F\u002F read (reflow), write (schedule) — reflow per item\n",[53,376,377],{"class":55,"line":88},[53,378,379],{"class":59},"});\n",[53,381,382],{"class":55,"line":116},[53,383,385],{"emptyLinePlaceholder":384},true,"\n",[53,387,388],{"class":55,"line":131},[53,389,390],{"class":84},"\u002F\u002F ✓ Batch all reads first, then all writes → ONE reflow.\n",[53,392,393,396,399,402,405,408,410,412,414,417],{"class":55,"line":141},[53,394,395],{"class":198},"const",[53,397,398],{"class":97}," widths",[53,400,401],{"class":198}," =",[53,403,404],{"class":59}," items.",[53,406,407],{"class":119},"map",[53,409,336],{"class":59},[53,411,340],{"class":339},[53,413,343],{"class":198},[53,415,416],{"class":59}," item.offsetWidth);  ",[53,418,419],{"class":84},"\u002F\u002F all reads (one reflow)\n",[53,421,422,424,426,429,431,433,436,439,442,445,447,450,452,454,456,458,461],{"class":55,"line":147},[53,423,330],{"class":59},[53,425,333],{"class":119},[53,427,428],{"class":59},"((",[53,430,340],{"class":339},[53,432,107],{"class":59},[53,434,435],{"class":339},"i",[53,437,438],{"class":59},") ",[53,440,441],{"class":198},"=>",[53,443,444],{"class":59}," { item.style.width ",[53,446,158],{"class":198},[53,448,449],{"class":59}," widths[i] ",[53,451,359],{"class":198},[53,453,362],{"class":97},[53,455,365],{"class":198},[53,457,368],{"class":161},[53,459,460],{"class":59},"; });  ",[53,462,463],{"class":84},"\u002F\u002F all writes (one layout)\n",[36,465,467],{"id":466},"the-performance-rule-what-triggers-what","The Performance Rule — what triggers what",[41,469,471],{"language":470},"text",[45,472,476],{"className":473,"code":475,"language":470,"meta":49},[474],"language-text","Change              Cost              Animate?\ntransform           Compositor (GPU)  ✅ 60fps\nopacity             Compositor (GPU)  ✅ 60fps\nfilter              Compositor (mostly) ✅\ncolor\u002Fbg-color      Paint             ⚠️ OK\nbox-shadow          Paint             ⚠️ (use pseudo-element opacity instead)\nwidth\u002Fheight        Layout            ❌ expensive (re-flows descendants)\ntop\u002Fleft\u002Fmargin     Layout            ❌ expensive\nfont-family\u002Fsize    Layout            ❌ re-renders text\ndisplay\u002Fposition    Layout            ❌ significant\n",[27,477,475],{"__ignoreMap":49},[479,480,482],"h3",{"id":481},"anti-pattern-animating-width-triggers-layout-every-frame","Anti-pattern: animating width triggers layout every frame",[41,484,485],{"language":253},[45,486,488],{"className":256,"code":487,"language":253,"meta":49,"style":49},"\u002F* ❌ width triggers layout every frame → janky, especially on mobile. *\u002F\n.sidebar { width: 250px; transition: width 0.3s ease; }\n.sidebar.collapsed { width: 0; }\n",[27,489,490,495,533],{"__ignoreMap":49},[53,491,492],{"class":55,"line":56},[53,493,494],{"class":84},"\u002F* ❌ width triggers layout every frame → janky, especially on mobile. *\u002F\n",[53,496,497,500,502,505,507,510,513,516,519,522,525,528,531],{"class":55,"line":70},[53,498,499],{"class":119},".sidebar",[53,501,94],{"class":59},[53,503,504],{"class":97},"width",[53,506,101],{"class":59},[53,508,509],{"class":97},"250",[53,511,512],{"class":198},"px",[53,514,515],{"class":59},"; ",[53,517,518],{"class":97},"transition",[53,520,521],{"class":59},": width ",[53,523,524],{"class":97},"0.3",[53,526,527],{"class":198},"s",[53,529,530],{"class":97}," ease",[53,532,113],{"class":59},[53,534,535,538,540,542,544,547],{"class":55,"line":81},[53,536,537],{"class":119},".sidebar.collapsed",[53,539,94],{"class":59},[53,541,504],{"class":97},[53,543,101],{"class":59},[53,545,546],{"class":97},"0",[53,548,113],{"class":59},[41,550,551],{"language":253},[45,552,554],{"className":256,"code":553,"language":253,"meta":49,"style":49},"\u002F* ✓ transform (compositor-only, GPU, no layout). *\u002F\n.sidebar { width: 250px; transition: transform 0.3s ease; transform: translateX(0); }\n.sidebar.collapsed { transform: translateX(-100%); }\n\n\u002F* ✓ grid-template-columns: Xfr → Yfr (interpolable, resizes both sidebar and content smoothly). *\u002F\n.layout { display: grid; grid-template-columns: 250px 1fr; transition: grid-template-columns 0.3s ease; }\n.layout.collapsed { grid-template-columns: 0fr 1fr; }\n.sidebar { overflow: hidden; }\n",[27,555,556,561,604,626,630,635,682,703],{"__ignoreMap":49},[53,557,558],{"class":55,"line":56},[53,559,560],{"class":84},"\u002F* ✓ transform (compositor-only, GPU, no layout). *\u002F\n",[53,562,563,565,567,569,571,573,575,577,579,582,584,586,588,590,592,594,597,599,601],{"class":55,"line":70},[53,564,499],{"class":119},[53,566,94],{"class":59},[53,568,504],{"class":97},[53,570,101],{"class":59},[53,572,509],{"class":97},[53,574,512],{"class":198},[53,576,515],{"class":59},[53,578,518],{"class":97},[53,580,581],{"class":59},": transform ",[53,583,524],{"class":97},[53,585,527],{"class":198},[53,587,530],{"class":97},[53,589,515],{"class":59},[53,591,29],{"class":97},[53,593,101],{"class":59},[53,595,596],{"class":97},"translateX",[53,598,336],{"class":59},[53,600,546],{"class":97},[53,602,603],{"class":59},"); }\n",[53,605,606,608,610,612,614,616,618,621,624],{"class":55,"line":81},[53,607,537],{"class":119},[53,609,94],{"class":59},[53,611,29],{"class":97},[53,613,101],{"class":59},[53,615,596],{"class":97},[53,617,336],{"class":59},[53,619,620],{"class":97},"-100",[53,622,623],{"class":198},"%",[53,625,603],{"class":59},[53,627,628],{"class":55,"line":88},[53,629,385],{"emptyLinePlaceholder":384},[53,631,632],{"class":55,"line":116},[53,633,634],{"class":84},"\u002F* ✓ grid-template-columns: Xfr → Yfr (interpolable, resizes both sidebar and content smoothly). *\u002F\n",[53,636,637,640,642,645,647,650,652,655,657,659,661,664,667,669,671,674,676,678,680],{"class":55,"line":131},[53,638,639],{"class":119},".layout",[53,641,94],{"class":59},[53,643,644],{"class":97},"display",[53,646,101],{"class":59},[53,648,649],{"class":97},"grid",[53,651,515],{"class":59},[53,653,654],{"class":97},"grid-template-columns",[53,656,101],{"class":59},[53,658,509],{"class":97},[53,660,512],{"class":198},[53,662,663],{"class":97}," 1",[53,665,666],{"class":198},"fr",[53,668,515],{"class":59},[53,670,518],{"class":97},[53,672,673],{"class":59},": grid-template-columns ",[53,675,524],{"class":97},[53,677,527],{"class":198},[53,679,530],{"class":97},[53,681,113],{"class":59},[53,683,684,687,689,691,693,695,697,699,701],{"class":55,"line":141},[53,685,686],{"class":119},".layout.collapsed",[53,688,94],{"class":59},[53,690,654],{"class":97},[53,692,101],{"class":59},[53,694,546],{"class":97},[53,696,666],{"class":198},[53,698,663],{"class":97},[53,700,666],{"class":198},[53,702,113],{"class":59},[53,704,705,707,709,712,714,717],{"class":55,"line":147},[53,706,499],{"class":119},[53,708,94],{"class":59},[53,710,711],{"class":97},"overflow",[53,713,101],{"class":59},[53,715,716],{"class":97},"hidden",[53,718,113],{"class":59},[36,720,722,725],{"id":721},"content-visibility-auto-skip-off-screen-rendering",[27,723,724],{},"content-visibility: auto"," — skip off-screen rendering",[41,727,728],{"language":253},[45,729,731],{"className":256,"code":730,"language":253,"meta":49,"style":49},"\u002F* Skips layout + paint for off-screen elements → massive win for long lists\u002Ffeeds.\n   contain-intrinsic-size gives a placeholder so the scrollbar is accurate (no jumps). *\u002F\n.long-list-item {\n  content-visibility: auto;\n  contain-intrinsic-size: 0 50px;  \u002F* (width height) placeholder estimate *\u002F\n}\n\u002F* Support: Chrome 85+, Safari 18+, Firefox 125+. *\u002F\n",[27,732,733,738,743,750,763,782,787],{"__ignoreMap":49},[53,734,735],{"class":55,"line":56},[53,736,737],{"class":84},"\u002F* Skips layout + paint for off-screen elements → massive win for long lists\u002Ffeeds.\n",[53,739,740],{"class":55,"line":70},[53,741,742],{"class":84},"   contain-intrinsic-size gives a placeholder so the scrollbar is accurate (no jumps). *\u002F\n",[53,744,745,748],{"class":55,"line":81},[53,746,747],{"class":119},".long-list-item",[53,749,346],{"class":59},[53,751,752,755,757,760],{"class":55,"line":88},[53,753,754],{"class":97},"  content-visibility",[53,756,101],{"class":59},[53,758,759],{"class":97},"auto",[53,761,762],{"class":59},";\n",[53,764,765,768,770,772,775,777,779],{"class":55,"line":116},[53,766,767],{"class":97},"  contain-intrinsic-size",[53,769,101],{"class":59},[53,771,546],{"class":97},[53,773,774],{"class":97}," 50",[53,776,512],{"class":198},[53,778,371],{"class":59},[53,780,781],{"class":84},"\u002F* (width height) placeholder estimate *\u002F\n",[53,783,784],{"class":55,"line":131},[53,785,786],{"class":59},"}\n",[53,788,789],{"class":55,"line":141},[53,790,791],{"class":84},"\u002F* Support: Chrome 85+, Safari 18+, Firefox 125+. *\u002F\n",[36,793,795,798],{"id":794},"contain-isolate-a-subtree",[27,796,797],{},"contain"," — isolate a subtree",[41,800,801],{"language":253},[45,802,804],{"className":256,"code":803,"language":253,"meta":49,"style":49},"\u002F* Tells the browser the element's internals don't affect the rest of the page → optimization. *\u002F\n.card { contain: layout paint style; }  \u002F* layout + paint + style isolation *\u002F\n\u002F* Values: layout, paint, style, size, strict (all), content (all except size).\n   Use on isolated components (cards, widgets, third-party embeds). *\u002F\n",[27,805,806,811,831,836],{"__ignoreMap":49},[53,807,808],{"class":55,"line":56},[53,809,810],{"class":84},"\u002F* Tells the browser the element's internals don't affect the rest of the page → optimization. *\u002F\n",[53,812,813,816,818,820,823,825,828],{"class":55,"line":70},[53,814,815],{"class":119},".card",[53,817,94],{"class":59},[53,819,797],{"class":97},[53,821,822],{"class":59},": layout paint ",[53,824,76],{"class":97},[53,826,827],{"class":59},"; }  ",[53,829,830],{"class":84},"\u002F* layout + paint + style isolation *\u002F\n",[53,832,833],{"class":55,"line":81},[53,834,835],{"class":84},"\u002F* Values: layout, paint, style, size, strict (all), content (all except size).\n",[53,837,838],{"class":55,"line":88},[53,839,840],{"class":84},"   Use on isolated components (cards, widgets, third-party embeds). *\u002F\n",[36,842,844,845,848],{"id":843},"gpu-layers-will-change-memory-cost","GPU Layers & ",[27,846,847],{},"will-change"," — memory cost",[41,850,851],{"language":253},[45,852,854],{"className":256,"code":853,"language":253,"meta":49,"style":49},"\u002F* transform\u002Fopacity\u002Ffilter\u002Fwill-change promote to GPU layers. Good for animation, costs RAM. *\u002F\n.modal { will-change: transform, opacity; }  \u002F* hint: promote before animating *\u002F\n.modal.closed { will-change: auto; }          \u002F* remove after → frees GPU memory *\u002F\n\u002F* ❌ * { will-change: transform; } → every element gets a GPU layer → OOM on mobile.\n   Add will-change just before the animation, remove after. Never leave it permanently. *\u002F\n",[27,855,856,861,876,895,900],{"__ignoreMap":49},[53,857,858],{"class":55,"line":56},[53,859,860],{"class":84},"\u002F* transform\u002Fopacity\u002Ffilter\u002Fwill-change promote to GPU layers. Good for animation, costs RAM. *\u002F\n",[53,862,863,866,868,870,873],{"class":55,"line":70},[53,864,865],{"class":119},".modal",[53,867,94],{"class":59},[53,869,847],{"class":97},[53,871,872],{"class":59},": transform, opacity; }  ",[53,874,875],{"class":84},"\u002F* hint: promote before animating *\u002F\n",[53,877,878,881,883,885,887,889,892],{"class":55,"line":81},[53,879,880],{"class":119},".modal.closed",[53,882,94],{"class":59},[53,884,847],{"class":97},[53,886,101],{"class":59},[53,888,759],{"class":97},[53,890,891],{"class":59},"; }          ",[53,893,894],{"class":84},"\u002F* remove after → frees GPU memory *\u002F\n",[53,896,897],{"class":55,"line":88},[53,898,899],{"class":84},"\u002F* ❌ * { will-change: transform; } → every element gets a GPU layer → OOM on mobile.\n",[53,901,902],{"class":55,"line":116},[53,903,904],{"class":84},"   Add will-change just before the animation, remove after. Never leave it permanently. *\u002F\n",[36,906,908],{"id":907},"font-performance","Font Performance",[41,910,911],{"language":253},[45,912,914],{"className":256,"code":913,"language":253,"meta":49,"style":49},"@font-face {\n  font-family: \"Inter\";\n  src: url(\"inter.woff2\") format(\"woff2\");  \u002F* woff2 = best compression *\u002F\n  font-display: swap;  \u002F* show fallback immediately, swap when loaded → no FOIT *\u002F\n  unicode-range: U+0000-00FF;  \u002F* subset to Latin only → smaller download *\u002F\n}\n",[27,915,916,923,935,966,981,996],{"__ignoreMap":49},[53,917,918,921],{"class":55,"line":56},[53,919,920],{"class":198},"@font-face",[53,922,346],{"class":59},[53,924,925,928,930,933],{"class":55,"line":70},[53,926,927],{"class":97},"  font-family",[53,929,101],{"class":59},[53,931,932],{"class":161},"\"Inter\"",[53,934,762],{"class":59},[53,936,937,940,942,945,947,950,952,955,957,960,963],{"class":55,"line":81},[53,938,939],{"class":97},"  src",[53,941,101],{"class":59},[53,943,944],{"class":97},"url",[53,946,336],{"class":59},[53,948,949],{"class":161},"\"inter.woff2\"",[53,951,438],{"class":59},[53,953,954],{"class":97},"format",[53,956,336],{"class":59},[53,958,959],{"class":161},"\"woff2\"",[53,961,962],{"class":59},");  ",[53,964,965],{"class":84},"\u002F* woff2 = best compression *\u002F\n",[53,967,968,971,973,976,978],{"class":55,"line":88},[53,969,970],{"class":97},"  font-display",[53,972,101],{"class":59},[53,974,975],{"class":97},"swap",[53,977,371],{"class":59},[53,979,980],{"class":84},"\u002F* show fallback immediately, swap when loaded → no FOIT *\u002F\n",[53,982,983,986,988,991,993],{"class":55,"line":116},[53,984,985],{"class":97},"  unicode-range",[53,987,101],{"class":59},[53,989,990],{"class":97},"U+0000-00FF",[53,992,371],{"class":59},[53,994,995],{"class":84},"\u002F* subset to Latin only → smaller download *\u002F\n",[53,997,998],{"class":55,"line":131},[53,999,786],{"class":59},[41,1001,1002],{"language":43},[45,1003,1005],{"className":47,"code":1004,"language":43,"meta":49,"style":49},"\u003Clink rel=\"preload\" href=\"\u002Ffonts\u002Finter.woff2\" as=\"font\" type=\"font\u002Fwoff2\" crossorigin>\n",[27,1006,1007],{"__ignoreMap":49},[53,1008,1009,1011,1013,1015,1017,1019,1021,1023,1026,1028,1030,1033,1036,1038,1041,1044],{"class":55,"line":56},[53,1010,60],{"class":59},[53,1012,152],{"class":63},[53,1014,155],{"class":119},[53,1016,158],{"class":59},[53,1018,162],{"class":161},[53,1020,165],{"class":119},[53,1022,158],{"class":59},[53,1024,1025],{"class":161},"\"\u002Ffonts\u002Finter.woff2\"",[53,1027,173],{"class":119},[53,1029,158],{"class":59},[53,1031,1032],{"class":161},"\"font\"",[53,1034,1035],{"class":119}," type",[53,1037,158],{"class":59},[53,1039,1040],{"class":161},"\"font\u002Fwoff2\"",[53,1042,1043],{"class":119}," crossorigin",[53,1045,67],{"class":59},[36,1047,1049],{"id":1048},"measuring","Measuring",[41,1051,1053],{"language":1052},"bash",[45,1054,1057],{"className":1055,"code":1056,"language":1052,"meta":49,"style":49},"language-bash shiki shiki-themes github-light github-dark","# Chrome DevTools → Performance: record → see layout\u002Fpaint\u002Fcomposite times per frame.\n# Rendering tab: Paint flashing (repaint regions), Layout Shift Regions (CLS), FPS meter.\n# Coverage tab: % unused CSS per file → purge targets.\n# Lighthouse: audits render-blocking CSS, unused CSS, FCP\u002FLCP.\n",[27,1058,1059,1064,1069,1074],{"__ignoreMap":49},[53,1060,1061],{"class":55,"line":56},[53,1062,1063],{"class":84},"# Chrome DevTools → Performance: record → see layout\u002Fpaint\u002Fcomposite times per frame.\n",[53,1065,1066],{"class":55,"line":70},[53,1067,1068],{"class":84},"# Rendering tab: Paint flashing (repaint regions), Layout Shift Regions (CLS), FPS meter.\n",[53,1070,1071],{"class":55,"line":81},[53,1072,1073],{"class":84},"# Coverage tab: % unused CSS per file → purge targets.\n",[53,1075,1076],{"class":55,"line":88},[53,1077,1078],{"class":84},"# Lighthouse: audits render-blocking CSS, unused CSS, FCP\u002FLCP.\n",[36,1080,1082],{"id":1081},"tips-tricks","💡 Tips & Tricks",[1084,1085,1086,1122,1133,1141,1154],"ul",{},[1087,1088,1089,1092,1093,30,1095,1097,1098,30,1100,30,1103,30,1106,1109,1110,1113,1114,1117,1118,1121],"li",{},[18,1090,1091],{},"Idiom",": animate ",[27,1094,29],{},[27,1096,33],{}," (compositor-only, GPU) — never ",[27,1099,504],{},[27,1101,1102],{},"height",[27,1104,1105],{},"top",[27,1107,1108],{},"left"," (layout). ",[27,1111,1112],{},"transform: translateX()"," for position, ",[27,1115,1116],{},"scale()"," for size, ",[27,1119,1120],{},"grid-template-columns: Xfr → Yfr"," for collapsible panels.",[1087,1123,1124,101,1126,1128,1129,1132],{},[18,1125,1091],{},[27,1127,724],{}," + ",[27,1130,1131],{},"contain-intrinsic-size"," for long lists — skips rendering off-screen items. Massive perf win for feeds\u002Ftables. The browser only layouts\u002Fpaints visible items.",[1087,1134,1135,101,1137,1140],{},[18,1136,1091],{},[27,1138,1139],{},"contain: layout paint"," on isolated components — tells the browser the subtree doesn't affect the page → optimization. Use on cards, widgets, third-party embeds.",[1087,1142,1143,1145,1146,1149,1150,1153],{},[18,1144,1091],{},": inline critical CSS, preload the rest — ",[27,1147,1148],{},"\u003Cstyle>"," for above-the-fold, ",[27,1151,1152],{},"\u003Clink rel=\"preload\" as=\"style\" onload=\"this.rel='stylesheet'\">"," for the rest. Removes the render-blocking CSS for first paint.",[1087,1155,1156,101,1158,1128,1161,1164,1165,1128,1168,1171],{},[18,1157,1091],{},[27,1159,1160],{},"font-display: swap",[27,1162,1163],{},"\u003Clink rel=\"preload\">"," for critical fonts — fallback text shows instantly, font swaps when loaded, preload minimizes the swap window. Use ",[27,1166,1167],{},"woff2",[27,1169,1170],{},"unicode-range"," subsetting.",[36,1173,1175],{"id":1174},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[1084,1177,1178,1192,1200,1210,1220,1236,1248],{},[1087,1179,1180,1183,1184,1187,1188,1191],{},[18,1181,1182],{},"Layout thrashing",": alternating ",[27,1185,1186],{},"offsetWidth"," reads and ",[27,1189,1190],{},"style.width"," writes in a loop forces reflow each iteration. Batch reads (all first), then writes.",[1087,1193,1194,1199],{},[18,1195,1196,1198],{},[27,1197,847],{}," on too many elements wastes memory",": each gets a GPU layer (RAM). Use only for elements about to animate, remove after.",[1087,1201,1202,1209],{},[18,1203,1204,1206,1207],{},[27,1205,724],{}," needs ",[27,1208,1131],{},": without it, off-screen elements have 0 height → scrollbar jumps as you scroll.",[1087,1211,1212,1215,1216,1219],{},[18,1213,1214],{},"Render-blocking CSS is intentional",": CSS in ",[27,1217,1218],{},"\u003Chead>"," blocks rendering to avoid FOUC. Don't make all CSS async — only non-critical. Inline the critical.",[1087,1221,1222,1228,1229,1232,1233,1235],{},[18,1223,1224,1227],{},[27,1225,1226],{},"@import"," is serial + render-blocking",": CSS importing CSS importing CSS. Use ",[27,1230,1231],{},"\u003Clink>"," (parallel) or bundle. Never ",[27,1234,1226],{}," in production.",[1087,1237,1238,1243,1244,1247],{},[18,1239,1240,1242],{},[27,1241,1160],{}," causes FOUT",": fallback shows, then swaps — a flash. Acceptable for body text; ",[27,1245,1246],{},"optional"," avoids the swap (may not load).",[1087,1249,1250,1253],{},[18,1251,1252],{},"Unused CSS",": large frameworks ship CSS you don't use. Purge (PurgeCSS, Tailwind purge) or check the Coverage tab.",[36,1255,1257],{"id":1256},"spot-the-bug","🧠 Spot the Bug",[41,1259,1260],{"language":253},[45,1261,1263],{"className":256,"code":1262,"language":253,"meta":49,"style":49},".sidebar { width: 250px; transition: width 0.3s ease; }\n.sidebar.collapsed { width: 0; }\n",[27,1264,1265,1293],{"__ignoreMap":49},[53,1266,1267,1269,1271,1273,1275,1277,1279,1281,1283,1285,1287,1289,1291],{"class":55,"line":56},[53,1268,499],{"class":119},[53,1270,94],{"class":59},[53,1272,504],{"class":97},[53,1274,101],{"class":59},[53,1276,509],{"class":97},[53,1278,512],{"class":198},[53,1280,515],{"class":59},[53,1282,518],{"class":97},[53,1284,521],{"class":59},[53,1286,524],{"class":97},[53,1288,527],{"class":198},[53,1290,530],{"class":97},[53,1292,113],{"class":59},[53,1294,1295,1297,1299,1301,1303,1305],{"class":55,"line":70},[53,1296,537],{"class":119},[53,1298,94],{"class":59},[53,1300,504],{"class":97},[53,1302,101],{"class":59},[53,1304,546],{"class":97},[53,1306,113],{"class":59},[1308,1309,1310,1314],"details",{},[1311,1312,1313],"summary",{},"Answer",[14,1315,1316,1317,1319,1320,1323,1324,1326,1327,1330,1331,1334,1335,30,1337,30,1339,30,1341,1343,1344,1346,1347,192],{},"Animating ",[27,1318,504],{}," triggers ",[18,1321,1322],{},"layout"," every frame — the browser recomputes the width of ",[27,1325,499],{}," and the layout of everything depending on it (the main content, descendants). On mobile (slower CPUs), this drops frames → janky. Fix: ",[27,1328,1329],{},"transform: translateX(-100%)"," (compositor-only, GPU, no layout) or ",[27,1332,1333],{},"grid-template-columns: 250px 1fr → 0fr 1fr"," (interpolable, resizes sidebar + content smoothly). The lesson: never animate ",[27,1336,504],{},[27,1338,1102],{},[27,1340,1105],{},[27,1342,1108],{}," — use ",[27,1345,29],{}," or ",[27,1348,1349],{},"grid-template",[76,1351,1352],{},"html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sk71V, html code.shiki .sk71V{--shiki-default:#22863A;--shiki-github-dark:#85E89D}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}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 .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sCrzJ, html code.shiki .sCrzJ{--shiki-default:#E36209;--shiki-github-dark:#FFAB70}",{"title":49,"searchDepth":70,"depth":70,"links":1354},[1355,1356,1357,1358,1361,1363,1365,1367,1368,1369,1370,1371],{"id":38,"depth":70,"text":39},{"id":249,"depth":70,"text":250},{"id":310,"depth":70,"text":311},{"id":466,"depth":70,"text":467,"children":1359},[1360],{"id":481,"depth":81,"text":482},{"id":721,"depth":70,"text":1362},"content-visibility: auto — skip off-screen rendering",{"id":794,"depth":70,"text":1364},"contain — isolate a subtree",{"id":843,"depth":70,"text":1366},"GPU Layers & will-change — memory cost",{"id":907,"depth":70,"text":908},{"id":1048,"depth":70,"text":1049},{"id":1081,"depth":70,"text":1082},{"id":1174,"depth":70,"text":1175},{"id":1256,"depth":70,"text":1257},"Render-blocking CSS and critical-CSS inlining, right-to-left selector matching, layout thrashing, content-visibility:auto, contain, will-change memory cost, and the width-animation trap. Code-first reference for 60fps and fast FCP.","md",{},"\u002Fcss\u002F19-performance",{"title":5,"description":1372},"css\u002F19-performance","AdP77iQdsFw3eYAbkO6874DZYRY4wYESbi2ubR5Ylok",1789924647536]