[{"data":1,"prerenderedAt":668},["ShallowReactive",2],{"guide-3d-css-transform-performance":3},{"id":4,"title":5,"body":6,"date":660,"description":661,"extension":662,"meta":663,"navigation":275,"path":664,"readingTime":292,"seo":665,"stem":666,"__hash__":667},"guides\u002Fguides\u002F3d-css-transform-performance.md","3D CSS Transform Performance Guide",{"type":7,"value":8,"toc":636},"minimark",[9,14,34,37,41,47,50,55,65,68,72,75,118,122,158,167,171,177,199,203,208,212,325,332,336,415,418,422,426,429,443,447,464,475,479,485,493,497,500,509,513,556,560,595,599,608,612,632],[10,11,13],"h2",{"id":12},"why-3d-transforms-need-performance-attention","Why 3D Transforms Need Performance Attention",[15,16,17,18,22,23,22,26,29,30,33],"p",{},"2D CSS transforms run smoothly on nearly every device. 3D transforms — ",[19,20,21],"code",{},"rotateX",", ",[19,24,25],{},"rotateY",[19,27,28],{},"translateZ",", and ",[19,31,32],{},"perspective"," — engage the GPU compositing pipeline differently. A card flip or parallax scroll that looks great on your laptop can drop to 15fps on a mid-range phone.",[15,35,36],{},"Performance is not optional. Jank destroys user trust. This guide covers what happens under the hood when you use 3D transforms and how to keep animations smooth.",[10,38,40],{"id":39},"gpu-compositing-how-3d-transforms-get-rendered","GPU Compositing: How 3D Transforms Get Rendered",[15,42,43,44,46],{},"When you apply a 3D transform or ",[19,45,32],{},", the browser promotes the element to its own compositor layer. Instead of repainting the element inside the main page paint, the GPU handles it independently.",[15,48,49],{},"This is why 3D transforms feel fast — the GPU can move, rotate, and scale layers without touching the CPU-bound layout and paint steps.",[51,52,54],"h3",{"id":53},"the-compositing-pipeline","The compositing pipeline",[56,57,62],"pre",{"className":58,"code":60,"language":61},[59],"language-text","JavaScript → Style → Layout → Paint → Composite\n                                        ↑\n                               GPU handles this\n","text",[19,63,60],{"__ignoreMap":64},"",[15,66,67],{},"3D transforms skip the expensive Layout and Paint steps on subsequent frames — the GPU recomposites the layer at its new position. But each layer consumes GPU memory. Too many layers exhaust video memory and cause the opposite problem: dropped frames.",[10,69,71],{"id":70},"when-the-browser-creates-a-layer","When the Browser Creates a Layer",[15,73,74],{},"The browser promotes an element to a compositor layer when:",[76,77,78,96,105,111],"ul",{},[79,80,81,82,85,86,22,89,22,91,22,93,95],"li",{},"You set ",[19,83,84],{},"transform"," with a 3D function (",[19,87,88],{},"translate3d",[19,90,21],{},[19,92,25],{},[19,94,32],{},")",[79,97,81,98,101,102],{},[19,99,100],{},"will-change: transform"," or ",[19,103,104],{},"will-change: opacity",[79,106,107,108],{},"You apply ",[19,109,110],{},"backface-visibility: hidden",[79,112,113,114,117],{},"You use ",[19,115,116],{},"position: fixed"," or video\u002Fcanvas elements",[51,119,121],{"id":120},"explicit-promotion-with-will-change","Explicit promotion with will-change",[56,123,127],{"className":124,"code":125,"language":126,"meta":64,"style":64},"language-css shiki shiki-themes github-light github-dark",".card {\n  will-change: transform;\n}\n","css",[19,128,129,142,152],{"__ignoreMap":64},[130,131,134,138],"span",{"class":132,"line":133},"line",1,[130,135,137],{"class":136},"sScJk",".card",[130,139,141],{"class":140},"sVt8B"," {\n",[130,143,145,149],{"class":132,"line":144},2,[130,146,148],{"class":147},"sj4cs","  will-change",[130,150,151],{"class":140},": transform;\n",[130,153,155],{"class":132,"line":154},3,[130,156,157],{"class":140},"}\n",[15,159,160,163,164,166],{},[19,161,162],{},"will-change"," tells the browser to expect changes and promotes the element ahead of time. This avoids the cost of creating a layer on the first animation frame. But do not apply it indiscriminately — every ",[19,165,162],{}," element reserves GPU memory.",[51,168,170],{"id":169},"removing-layers","Removing layers",[15,172,173,174,176],{},"Remove ",[19,175,162],{}," after the animation completes to free GPU memory:",[56,178,182],{"className":179,"code":180,"language":181,"meta":64,"style":64},"language-javascript shiki shiki-themes github-light github-dark","element.addEventListener('transitionend', () => {\n  element.style.willChange = 'auto'\n})\n","javascript",[19,183,184,189,194],{"__ignoreMap":64},[130,185,186],{"class":132,"line":133},[130,187,188],{},"element.addEventListener('transitionend', () => {\n",[130,190,191],{"class":132,"line":144},[130,192,193],{},"  element.style.willChange = 'auto'\n",[130,195,196],{"class":132,"line":154},[130,197,198],{},"})\n",[10,200,202],{"id":201},"perspective-and-performance","Perspective and Performance",[15,204,205,207],{},[19,206,32],{}," adds depth to 3D transforms. It determines how dramatically elements shrink as they move away from the viewer.",[51,209,211],{"id":210},"parent-vs-individual-perspective","Parent vs. individual perspective",[56,213,215],{"className":124,"code":214,"language":126,"meta":64,"style":64},"\u002F* Perspective on parent: children share the same vanishing point *\u002F\n.container { perspective: 800px; }\n.card { transform: rotateY(45deg); }\n\n\u002F* Perspective per element: each has its own vanishing point *\u002F\n.card {\n  transform: perspective(800px) rotateY(45deg);\n}\n",[19,216,217,223,246,270,277,283,290,320],{"__ignoreMap":64},[130,218,219],{"class":132,"line":133},[130,220,222],{"class":221},"sJ8bj","\u002F* Perspective on parent: children share the same vanishing point *\u002F\n",[130,224,225,228,231,233,236,239,243],{"class":132,"line":144},[130,226,227],{"class":136},".container",[130,229,230],{"class":140}," { ",[130,232,32],{"class":147},[130,234,235],{"class":140},": ",[130,237,238],{"class":147},"800",[130,240,242],{"class":241},"szBVR","px",[130,244,245],{"class":140},"; }\n",[130,247,248,250,252,254,256,258,261,264,267],{"class":132,"line":154},[130,249,137],{"class":136},[130,251,230],{"class":140},[130,253,84],{"class":147},[130,255,235],{"class":140},[130,257,25],{"class":147},[130,259,260],{"class":140},"(",[130,262,263],{"class":147},"45",[130,265,266],{"class":241},"deg",[130,268,269],{"class":140},"); }\n",[130,271,273],{"class":132,"line":272},4,[130,274,276],{"emptyLinePlaceholder":275},true,"\n",[130,278,280],{"class":132,"line":279},5,[130,281,282],{"class":221},"\u002F* Perspective per element: each has its own vanishing point *\u002F\n",[130,284,286,288],{"class":132,"line":285},6,[130,287,137],{"class":136},[130,289,141],{"class":140},[130,291,293,296,298,300,302,304,306,309,311,313,315,317],{"class":132,"line":292},7,[130,294,295],{"class":147},"  transform",[130,297,235],{"class":140},[130,299,32],{"class":147},[130,301,260],{"class":140},[130,303,238],{"class":147},[130,305,242],{"class":241},[130,307,308],{"class":140},") ",[130,310,25],{"class":147},[130,312,260],{"class":140},[130,314,263],{"class":147},[130,316,266],{"class":241},[130,318,319],{"class":140},");\n",[130,321,323],{"class":132,"line":322},8,[130,324,157],{"class":140},[15,326,327,328,331],{},"Parent perspective is more efficient because the browser handles one perspective calculation for multiple children. Individual ",[19,329,330],{},"perspective()"," in transform functions triggers separate calculations per element — use it only when you need different vanishing points.",[51,333,335],{"id":334},"choosing-a-perspective-value","Choosing a perspective value",[337,338,339,355],"table",{},[340,341,342],"thead",{},[343,344,345,349,352],"tr",{},[346,347,348],"th",{},"Value",[346,350,351],{},"Visual Effect",[346,353,354],{},"Use Case",[356,357,358,372,385,402],"tbody",{},[343,359,360,366,369],{},[361,362,363],"td",{},[19,364,365],{},"200px",[361,367,368],{},"Extreme distortion",[361,370,371],{},"Gimmick effects",[343,373,374,379,382],{},[361,375,376],{},[19,377,378],{},"500px",[361,380,381],{},"Noticeable depth",[361,383,384],{},"Card flips",[343,386,387,396,399],{},[361,388,389,392,393],{},[19,390,391],{},"800px","–",[19,394,395],{},"1200px",[361,397,398],{},"Subtle realism",[361,400,401],{},"Product showcases, UI",[343,403,404,409,412],{},[361,405,406],{},[19,407,408],{},"2000px+",[361,410,411],{},"Near-flat",[361,413,414],{},"Gentle parallax",[15,416,417],{},"Low perspective values cause heavy pixel interpolation near the edges — more GPU work. Stick to 800px or above for production UIs.",[10,419,421],{"id":420},"performance-pitfalls","Performance Pitfalls",[51,423,425],{"id":424},"too-many-layers","Too many layers",[15,427,428],{},"Each compositor layer costs ~4 bytes per pixel (RGBA). A 500×300 element uses 600KB of GPU memory. One hundred such elements = 60MB. On a device with 200MB of VRAM, you are in trouble.",[15,430,431,435,436,438,439,442],{},[432,433,434],"strong",{},"Fix:"," Only promote elements that actually animate. Remove ",[19,437,162],{}," and ",[19,440,441],{},"transform: translateZ(0)"," from static elements.",[51,444,446],{"id":445},"painting-during-animation","Painting during animation",[15,448,449,450,22,453,22,456,459,460,463],{},"If your animated element triggers layout or paint — for example, changing ",[19,451,452],{},"width",[19,454,455],{},"height",[19,457,458],{},"margin",", or ",[19,461,462],{},"box-shadow"," during the animation — the GPU optimization breaks. The browser must repaint each frame.",[15,465,466,468,469,438,471,474],{},[432,467,434],{}," Animate only ",[19,470,84],{},[19,472,473],{},"opacity",". These are the two properties the compositor handles without repaints.",[51,476,478],{"id":477},"large-element-surfaces","Large element surfaces",[15,480,481,482,484],{},"A full-width hero image with ",[19,483,25],{}," forces the GPU to interpolate millions of pixels per frame. Even with compositor acceleration, large surfaces slow down.",[15,486,487,489,490,492],{},[432,488,434],{}," Keep animated elements small. If you must animate a large area, reduce the perspective value and use ",[19,491,110],{}," to skip rendering the back face.",[51,494,496],{"id":495},"transform3d-on-text","Transform3d on text",[15,498,499],{},"Subpixel rendering breaks on 3D-transformed text. Characters appear blurry because the GPU cannot apply subpixel antialiasing on compositor layers.",[15,501,502,504,505,508],{},[432,503,434],{}," Scale down slightly then back up, or apply ",[19,506,507],{},"translateZ(0)"," only during the animation and remove it in the rest state.",[10,510,512],{"id":511},"performance-checklist","Performance Checklist",[76,514,515,523,529,538,541,544,547,553],{},[79,516,517,518,438,520,522],{},"Animate only ",[19,519,84],{},[19,521,473],{}," — avoid layout-triggering properties",[79,524,525,526,528],{},"Set ",[19,527,100],{}," on elements before they animate, then remove it",[79,530,531,532,534,535,537],{},"Use parent ",[19,533,32],{}," over individual ",[19,536,330],{}," when multiple children share depth",[79,539,540],{},"Keep animated elements small in surface area",[79,542,543],{},"Count your compositor layers in DevTools (Layers panel in Chrome) — aim for under 30",[79,545,546],{},"Test on a mid-range mobile device, not just your dev machine",[79,548,549,550,552],{},"Use ",[19,551,110],{}," to skip rendering hidden faces of flipped elements",[79,554,555],{},"Avoid 3D transforms on text — use them on containers and let the text live at rest",[10,557,559],{"id":558},"key-takeaways","Key Takeaways",[76,561,562,565,568,575,580,589,592],{},[79,563,564],{},"3D transforms promote elements to GPU compositor layers, bypassing repaint costs",[79,566,567],{},"Each layer consumes GPU memory — too many layers cause performance drops",[79,569,517,570,438,572,574],{},[19,571,84],{},[19,573,473],{}," to stay on the compositor path",[79,576,549,577,579],{},[19,578,162],{}," sparingly and clean it up after animations finish",[79,581,582,583,585,586,588],{},"Prefer parent ",[19,584,32],{}," over per-element ",[19,587,330],{}," for multiple siblings",[79,590,591],{},"Test compositor layer count and memory usage in browser DevTools",[79,593,594],{},"Avoid applying 3D transforms to text directly to prevent blurriness",[10,596,598],{"id":597},"try-it-yourself","Try It Yourself",[15,600,601,602,607],{},"Build and preview 3D CSS transforms interactively with our ",[603,604,606],"a",{"href":605},"\u002Ftools\u002Fcss-transform","CSS Transform Tool",". Adjust perspective, rotate on any axis, and see performance-critical properties in action.",[10,609,611],{"id":610},"related-guides","Related Guides",[76,613,614,620,626],{},[79,615,616],{},[603,617,619],{"href":618},"\u002Fguides\u002Fcss-transform-guide","CSS Transform Guide",[79,621,622],{},[603,623,625],{"href":624},"\u002Fguides\u002Fcss-animation-tools","CSS Animation Tools",[79,627,628],{},[603,629,631],{"href":630},"\u002Fguides\u002Fcss-visual-effects","CSS Visual Effects",[633,634,635],"style",{},"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 .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 pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":64,"searchDepth":144,"depth":144,"links":637},[638,639,642,646,650,656,657,658,659],{"id":12,"depth":144,"text":13},{"id":39,"depth":144,"text":40,"children":640},[641],{"id":53,"depth":154,"text":54},{"id":70,"depth":144,"text":71,"children":643},[644,645],{"id":120,"depth":154,"text":121},{"id":169,"depth":154,"text":170},{"id":201,"depth":144,"text":202,"children":647},[648,649],{"id":210,"depth":154,"text":211},{"id":334,"depth":154,"text":335},{"id":420,"depth":144,"text":421,"children":651},[652,653,654,655],{"id":424,"depth":154,"text":425},{"id":445,"depth":154,"text":446},{"id":477,"depth":154,"text":478},{"id":495,"depth":154,"text":496},{"id":511,"depth":144,"text":512},{"id":558,"depth":144,"text":559},{"id":597,"depth":144,"text":598},{"id":610,"depth":144,"text":611},"2026-05-28","Optimize 3D CSS transforms for smooth 60fps animations — GPU compositing, will-change, perspective, and common performance pitfalls.","md",{"immutable":275},"\u002Fguides\u002F3d-css-transform-performance",{"title":5,"description":661},"guides\u002F3d-css-transform-performance","TzdQeNDkg-iT2Bzrt-brnlE5bkDM6C8YHKd-FPC9y4k",1780401331135]