[{"data":1,"prerenderedAt":743},["ShallowReactive",2],{"guide-css-transform-performance":3},{"id":4,"title":5,"body":6,"date":734,"description":735,"extension":736,"meta":737,"navigation":355,"path":738,"readingTime":739,"seo":740,"stem":741,"__hash__":742},"guides\u002Fguides\u002Fcss-transform-performance.md","CSS Transform Performance Tips",{"type":7,"value":8,"toc":720},"minimark",[9,14,27,30,40,46,52,55,59,62,203,222,226,232,268,271,276,302,306,327,375,379,386,410,479,486,490,496,567,570,574,577,597,600,604,661,665,704,708,716],[10,11,13],"h2",{"id":12},"why-transforms-are-fast","Why Transforms Are Fast",[15,16,17,18,22,23,26],"p",{},"CSS transforms are the gold standard for performant animations because they skip the browser's main rendering pipeline. When you animate ",[19,20,21],"code",{},"transform"," or ",[19,24,25],{},"opacity",", the browser promotes the element to its own compositor layer and handles changes entirely on the GPU.",[15,28,29],{},"The normal rendering pipeline for a style change looks like this:",[31,32,37],"pre",{"className":33,"code":35,"language":36},[34],"language-text","Style → Layout → Paint → Composite\n","text",[19,38,35],{"__ignoreMap":39},"",[15,41,42,43,45],{},"Animating ",[19,44,21],{}," short-circuits this to:",[31,47,50],{"className":48,"code":49,"language":36},[34],"Style → Composite\n",[19,51,49],{"__ignoreMap":39},[15,53,54],{},"The browser never recalculates layout or repaints pixels — it just tells the compositor to adjust the layer's position, scale, or rotation. This happens at 60fps even on mid-range hardware.",[10,56,58],{"id":57},"properties-that-trigger-full-repaint","Properties That Trigger Full Repaint",[15,60,61],{},"Not all CSS properties are compositor-friendly. Here is how common properties affect the pipeline:",[63,64,65,84],"table",{},[66,67,68],"thead",{},[69,70,71,75,78,81],"tr",{},[72,73,74],"th",{},"Property",[72,76,77],{},"Layout",[72,79,80],{},"Paint",[72,82,83],{},"Composite",[85,86,87,102,114,128,141,154,171,187],"tbody",{},[69,88,89,94,97,99],{},[90,91,92],"td",{},[19,93,21],{},[90,95,96],{},"No",[90,98,96],{},[90,100,101],{},"Yes",[69,103,104,108,110,112],{},[90,105,106],{},[19,107,25],{},[90,109,96],{},[90,111,96],{},[90,113,101],{},[69,115,116,121,123,126],{},[90,117,118],{},[19,119,120],{},"filter",[90,122,96],{},[90,124,125],{},"Sometimes",[90,127,101],{},[69,129,130,135,137,139],{},[90,131,132],{},[19,133,134],{},"box-shadow",[90,136,96],{},[90,138,101],{},[90,140,101],{},[69,142,143,148,150,152],{},[90,144,145],{},[19,146,147],{},"background-color",[90,149,96],{},[90,151,101],{},[90,153,101],{},[69,155,156,165,167,169],{},[90,157,158,161,162],{},[19,159,160],{},"width"," \u002F ",[19,163,164],{},"height",[90,166,101],{},[90,168,101],{},[90,170,101],{},[69,172,173,181,183,185],{},[90,174,175,161,178],{},[19,176,177],{},"margin",[19,179,180],{},"padding",[90,182,101],{},[90,184,101],{},[90,186,101],{},[69,188,189,197,199,201],{},[90,190,191,161,194],{},[19,192,193],{},"top",[19,195,196],{},"left",[90,198,101],{},[90,200,101],{},[90,202,101],{},[15,204,205,206,209,210,212,213,209,216,218,219,221],{},"Always prefer ",[19,207,208],{},"transform: translateX()"," over ",[19,211,196],{},", and ",[19,214,215],{},"transform: scale()",[19,217,160],{},"\u002F",[19,220,164],{}," changes. The difference between composite-only and layout-triggering animations is often 10x in frame time.",[10,223,225],{"id":224},"the-will-change-property","The will-change Property",[15,227,228,231],{},[19,229,230],{},"will-change"," tells the browser to optimize for an upcoming change, typically by promoting the element to its own layer ahead of time:",[31,233,237],{"className":234,"code":235,"language":236,"meta":39,"style":39},"language-css shiki shiki-themes github-light github-dark",".animated-element {\n  will-change: transform;\n}\n","css",[19,238,239,252,262],{"__ignoreMap":39},[240,241,244,248],"span",{"class":242,"line":243},"line",1,[240,245,247],{"class":246},"sScJk",".animated-element",[240,249,251],{"class":250},"sVt8B"," {\n",[240,253,255,259],{"class":242,"line":254},2,[240,256,258],{"class":257},"sj4cs","  will-change",[240,260,261],{"class":250},": transform;\n",[240,263,265],{"class":242,"line":264},3,[240,266,267],{"class":250},"}\n",[15,269,270],{},"This eliminates the first-frame jank that can occur when the browser promotes an element mid-animation.",[272,273,275],"h3",{"id":274},"when-to-use-will-change","When to Use will-change",[277,278,279,287,296],"ul",{},[280,281,282,286],"li",{},[283,284,285],"strong",{},"Use it"," when an element is about to be animated (e.g., on hover enter, or via a class added before animation starts).",[280,288,289,292,293,295],{},[283,290,291],{},"Use it sparingly"," — each ",[19,294,230],{}," declaration consumes GPU memory for a separate compositor layer.",[280,297,298,301],{},[283,299,300],{},"Remove it"," after the animation ends to free resources.",[272,303,305],{"id":304},"when-not-to-use-will-change","When Not to Use will-change",[277,307,308,318],{},[280,309,310,313,314,317],{},[283,311,312],{},"Never apply it broadly"," — setting ",[19,315,316],{},"will-change: transform"," on hundreds of list items causes GPU memory exhaustion and may actually hurt performance.",[280,319,320,323,324,326],{},[283,321,322],{},"Don't use it as a premature optimization"," — if your animation is already smooth, ",[19,325,230],{}," adds no benefit and consumes resources.",[31,328,330],{"className":234,"code":329,"language":236,"meta":39,"style":39},"\u002F* Bad: applied permanently to many elements *\u002F\n.list-item { will-change: transform; }\n\n\u002F* Good: applied just before animation, removed after *\u002F\n.list-item.is-dragging { will-change: transform; }\n",[19,331,332,338,351,357,363],{"__ignoreMap":39},[240,333,334],{"class":242,"line":243},[240,335,337],{"class":336},"sJ8bj","\u002F* Bad: applied permanently to many elements *\u002F\n",[240,339,340,343,346,348],{"class":242,"line":254},[240,341,342],{"class":246},".list-item",[240,344,345],{"class":250}," { ",[240,347,230],{"class":257},[240,349,350],{"class":250},": transform; }\n",[240,352,353],{"class":242,"line":264},[240,354,356],{"emptyLinePlaceholder":355},true,"\n",[240,358,360],{"class":242,"line":359},4,[240,361,362],{"class":336},"\u002F* Good: applied just before animation, removed after *\u002F\n",[240,364,366,369,371,373],{"class":242,"line":365},5,[240,367,368],{"class":246},".list-item.is-dragging",[240,370,345],{"class":250},[240,372,230],{"class":257},[240,374,350],{"class":250},[10,376,378],{"id":377},"containment-for-performance","Containment for Performance",[15,380,381,382,385],{},"The ",[19,383,384],{},"contain"," property tells the browser that an element's contents are independent of the rest of the document, allowing the browser to skip layout and paint for outside elements:",[31,387,389],{"className":234,"code":388,"language":236,"meta":39,"style":39},".card {\n  contain: layout paint;\n}\n",[19,390,391,398,406],{"__ignoreMap":39},[240,392,393,396],{"class":242,"line":243},[240,394,395],{"class":246},".card",[240,397,251],{"class":250},[240,399,400,403],{"class":242,"line":254},[240,401,402],{"class":257},"  contain",[240,404,405],{"class":250},": layout paint;\n",[240,407,408],{"class":242,"line":264},[240,409,267],{"class":250},[63,411,412,422],{},[66,413,414],{},[69,415,416,419],{},[72,417,418],{},"Value",[72,420,421],{},"What it isolates",[85,423,424,434,444,454,467],{},[69,425,426,431],{},[90,427,428],{},[19,429,430],{},"layout",[90,432,433],{},"Layout changes inside don't affect outside",[69,435,436,441],{},[90,437,438],{},[19,439,440],{},"paint",[90,442,443],{},"Element won't paint outside its bounds",[69,445,446,451],{},[90,447,448],{},[19,449,450],{},"size",[90,452,453],{},"Element's size is independent of content",[69,455,456,461],{},[90,457,458],{},[19,459,460],{},"content",[90,462,463,464],{},"Shorthand for ",[19,465,466],{},"layout paint",[69,468,469,474],{},[90,470,471],{},[19,472,473],{},"strict",[90,475,463,476],{},[19,477,478],{},"layout paint size",[15,480,481,482,485],{},"Use ",[19,483,484],{},"contain: content"," on repeated components like cards, list items, and sidebar sections to limit the scope of reflows.",[10,487,489],{"id":488},"promoting-elements-to-layers","Promoting Elements to Layers",[15,491,492,493,495],{},"Besides ",[19,494,230],{},", there are other ways to trigger layer promotion:",[31,497,499],{"className":234,"code":498,"language":236,"meta":39,"style":39},".promoted {\n  transform: translateZ(0);   \u002F* Classic hack *\u002F\n  backface-visibility: hidden; \u002F* Side effect: promotes *\u002F\n  isolation: isolate;          \u002F* Creates stacking context *\u002F\n}\n",[19,500,501,508,531,547,563],{"__ignoreMap":39},[240,502,503,506],{"class":242,"line":243},[240,504,505],{"class":246},".promoted",[240,507,251],{"class":250},[240,509,510,513,516,519,522,525,528],{"class":242,"line":254},[240,511,512],{"class":257},"  transform",[240,514,515],{"class":250},": ",[240,517,518],{"class":257},"translateZ",[240,520,521],{"class":250},"(",[240,523,524],{"class":257},"0",[240,526,527],{"class":250},");   ",[240,529,530],{"class":336},"\u002F* Classic hack *\u002F\n",[240,532,533,536,538,541,544],{"class":242,"line":264},[240,534,535],{"class":257},"  backface-visibility",[240,537,515],{"class":250},[240,539,540],{"class":257},"hidden",[240,542,543],{"class":250},"; ",[240,545,546],{"class":336},"\u002F* Side effect: promotes *\u002F\n",[240,548,549,552,554,557,560],{"class":242,"line":359},[240,550,551],{"class":257},"  isolation",[240,553,515],{"class":250},[240,555,556],{"class":257},"isolate",[240,558,559],{"class":250},";          ",[240,561,562],{"class":336},"\u002F* Creates stacking context *\u002F\n",[240,564,565],{"class":242,"line":365},[240,566,267],{"class":250},[15,568,569],{},"Use these only when benchmarking shows a measurable improvement. Promoting everything to layers increases memory usage and can cause the compositor to spend more time managing layers than actually compositing.",[10,571,573],{"id":572},"debugging-performance-issues","Debugging Performance Issues",[15,575,576],{},"Chrome DevTools provides several tools for diagnosing animation performance:",[277,578,579,585,591],{},[280,580,581,584],{},[283,582,583],{},"Performance tab",": Record an animation and check for long frames. Look at the \"Layers\" and \"Paints\" sections.",[280,586,587,590],{},[283,588,589],{},"Rendering panel",": Enable \"Paint flashing\" to see which elements repaint. Green overlays indicate paint areas.",[280,592,593,596],{},[283,594,595],{},"Layers panel",": See how many compositor layers exist and their memory cost.",[15,598,599],{},"A healthy animation profile shows frames under 16ms with zero layout or paint operations — only composite.",[10,601,603],{"id":602},"common-performance-mistakes","Common Performance Mistakes",[277,605,606,618,634,643,652],{},[280,607,608,617],{},[283,609,42,610,218,612,614,615],{},[19,611,193],{},[19,613,196],{}," instead of ",[19,616,21],{}," — triggers layout on every frame.",[280,619,620,626,627,630,631,633],{},[283,621,622,623,625],{},"Changing ",[19,624,134],{}," on hover"," — triggers paint. Use ",[19,628,629],{},"filter: drop-shadow()"," or animate ",[19,632,25],{}," of an overlaid shadow element.",[280,635,636,642],{},[283,637,638,639,641],{},"Too many ",[19,640,230],{}," declarations"," — exhausts GPU memory, causing layer thrashing.",[280,644,645,651],{},[283,646,647,648],{},"Nested transforms without ",[19,649,650],{},"transform-style: preserve-3d"," — forces the browser to flatten and repaint.",[280,653,654,660],{},[283,655,656,657,659],{},"Ignoring ",[19,658,384],{}," on large lists"," — every item change causes a full-container reflow.",[10,662,664],{"id":663},"key-takeaways","Key Takeaways",[277,666,667,675,680,683,688,701],{},[280,668,669,671,672,674],{},[19,670,21],{}," and ",[19,673,25],{}," are the only properties that animate on the compositor alone — always prefer them.",[280,676,677,679],{},[19,678,230],{}," pre-promotes elements to GPU layers; use it before animation, remove it after.",[280,681,682],{},"Each compositor layer consumes GPU memory — more layers are not always better.",[280,684,685,687],{},[19,686,484],{}," limits reflow scope for isolated components.",[280,689,690,691,693,694,693,696,212,698,700],{},"Avoid animating layout-triggering properties like ",[19,692,160],{},", ",[19,695,164],{},[19,697,193],{},[19,699,196],{},".",[280,702,703],{},"Use Chrome DevTools to verify that animations stay in the composite stage only.",[10,705,707],{"id":706},"try-it-yourself","Try It Yourself",[15,709,710,711,700],{},"Build and preview CSS transforms with live performance feedback using the ",[712,713,715],"a",{"href":714},"\u002Ftools\u002Fcss-transform","CSS Transform Tool",[717,718,719],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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 pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":39,"searchDepth":254,"depth":254,"links":721},[722,723,724,728,729,730,731,732,733],{"id":12,"depth":254,"text":13},{"id":57,"depth":254,"text":58},{"id":224,"depth":254,"text":225,"children":725},[726,727],{"id":274,"depth":264,"text":275},{"id":304,"depth":264,"text":305},{"id":377,"depth":254,"text":378},{"id":488,"depth":254,"text":489},{"id":572,"depth":254,"text":573},{"id":602,"depth":254,"text":603},{"id":663,"depth":254,"text":664},{"id":706,"depth":254,"text":707},"2026-05-28","Why transforms trigger GPU acceleration and how to use will-change for smooth animations.","md",{"immutable":355},"\u002Fguides\u002Fcss-transform-performance",6,{"title":5,"description":735},"guides\u002Fcss-transform-performance","3A8ODpqbDLQq6E-ypXbTSlbPFZuzTKAtxaWbHqPkFio",1780401333110]