[{"data":1,"prerenderedAt":1349},["ShallowReactive",2],{"content:\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity":3,"surroundings:\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity":1341},{"id":4,"title":5,"body":6,"description":1321,"extension":1322,"meta":1323,"navigation":1334,"path":1335,"seo":1336,"stem":1339,"__hash__":1340},"content\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002Findex.md","Animating Only transform and opacity",{"type":7,"value":8,"toc":1304},"minimark",[9,14,44,47,207,212,241,245,251,264,270,276,280,285,381,385,500,504,670,674,677,892,994,998,1001,1005,1011,1015,1038,1088,1092,1121,1125,1131,1137,1143,1157,1161,1173,1186,1195,1217,1226,1235,1244,1253,1262,1266,1289,1294,1297,1300],[10,11,13],"h1",{"id":12},"how-to-animate-only-transform-and-opacity","How to Animate Only transform and opacity",[15,16,17,18,23,24,28,29,33,34,33,37,33,40,43],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002F","Animation & Compositing Performance",", within ",[19,25,27],{"href":26},"\u002Frendering-css-performance\u002F","Rendering & CSS Performance",". The advice \"only animate transform and opacity\" is easy to repeat and harder to apply: designs ask for panels that slide, cards that grow, shadows that deepen on hover, accordions that expand and progress bars that fill. Each has a natural implementation with a layout or paint property — ",[30,31,32],"code",{},"left",", ",[30,35,36],{},"width",[30,38,39],{},"box-shadow",[30,41,42],{},"height"," — and a compositor-friendly alternative that needs a little more thought.",[15,45,46],{},"Compositor-only animations keep running smoothly even when the main thread is busy with JavaScript, they do not cause layout shifts, and they do not repaint every frame. This guide shows rewrites for the most common animation patterns and the trade-offs each one introduces.",[15,48,49],{},[50,51,57,58,57,65,57,69,57,72,57,81,57,87,57,93,57,100,57,104,57,109,57,112,57,116,57,120,57,124,57,129,57,133,57,137,57,140,57,143,57,147,57,149,57,152,57,154,57,157,57,160,57,164,57,166,57,168,57,170,57,173,57,176,57,180,57,182,57,185,57,187,57,190,57,193,57,197,57,199,57,202,57,204,57],"svg",{"viewBox":52,"width":53,"role":54,"ariaLabel":55,"style":56},"0 0 760 258","100%","img","Common UI animations implemented with layout or paint properties and their transform or opacity alternatives.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[59,60],"rect",{"className":61,"x":63,"y":63,"width":53,"height":53,"fill":64},[62],"svg-canvas","0","#ffffff",[66,67,68],"title",{},"Common animations and their compositor-friendly rewrites",[70,71,55],"desc",{},[59,73],{"x":74,"y":74,"width":75,"height":76,"rx":77,"fill":78,"stroke":79,"style":80},"1","758","256","10","none","currentColor","stroke-opacity:0.18",[82,83,68],"text",{"x":84,"y":85,"fill":79,"style":86},"28.0","34.0","font-size:16px;font-weight:700",[59,88],{"x":84,"y":89,"width":90,"height":91,"rx":63,"fill":79,"stroke":79,"style":92},"56.0","161.9","30.0","fill-opacity:0.06;stroke-opacity:0.4",[82,94,99],{"x":95,"y":96,"fill":79,"style":97,"textAnchor":98},"38.0","75.5","font-size:12.5px;font-weight:700","start","Effect",[59,101],{"x":102,"y":89,"width":103,"height":91,"rx":63,"fill":79,"stroke":79,"style":92},"189.9","271.0",[82,105,108],{"x":106,"y":96,"fill":79,"style":97,"textAnchor":107},"325.4","middle","Expensive version",[59,110],{"x":111,"y":89,"width":103,"height":91,"rx":63,"fill":79,"stroke":79,"style":92},"461.0",[82,113,115],{"x":114,"y":96,"fill":79,"style":97,"textAnchor":107},"596.5","Compositor version",[59,117],{"x":84,"y":118,"width":90,"height":91,"rx":63,"fill":78,"stroke":79,"style":119},"86.0","stroke-opacity:0.35",[82,121,123],{"x":95,"y":122,"fill":79,"style":97,"textAnchor":98},"105.5","Slide in panel",[59,125],{"x":102,"y":118,"width":103,"height":91,"rx":63,"fill":126,"stroke":127,"style":128},"#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[82,130,132],{"x":106,"y":122,"fill":79,"style":131,"textAnchor":107},"font-size:12px","left \u002F right",[59,134],{"x":111,"y":118,"width":103,"height":91,"rx":63,"fill":135,"stroke":135,"style":136},"#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[82,138,139],{"x":114,"y":122,"fill":79,"style":131,"textAnchor":107},"translateX",[59,141],{"x":84,"y":142,"width":90,"height":91,"rx":63,"fill":78,"stroke":79,"style":119},"116.0",[82,144,146],{"x":95,"y":145,"fill":79,"style":97,"textAnchor":98},"135.5","Grow on hover",[59,148],{"x":102,"y":142,"width":103,"height":91,"rx":63,"fill":126,"stroke":127,"style":128},[82,150,151],{"x":106,"y":145,"fill":79,"style":131,"textAnchor":107},"width \u002F height",[59,153],{"x":111,"y":142,"width":103,"height":91,"rx":63,"fill":135,"stroke":135,"style":136},[82,155,156],{"x":114,"y":145,"fill":79,"style":131,"textAnchor":107},"scale",[59,158],{"x":84,"y":159,"width":90,"height":91,"rx":63,"fill":78,"stroke":79,"style":119},"146.0",[82,161,163],{"x":95,"y":162,"fill":79,"style":97,"textAnchor":98},"165.5","Deeper shadow on hover",[59,165],{"x":102,"y":159,"width":103,"height":91,"rx":63,"fill":126,"stroke":127,"style":128},[82,167,39],{"x":106,"y":162,"fill":79,"style":131,"textAnchor":107},[59,169],{"x":111,"y":159,"width":103,"height":91,"rx":63,"fill":135,"stroke":135,"style":136},[82,171,172],{"x":114,"y":162,"fill":79,"style":131,"textAnchor":107},"opacity of shadow layer",[59,174],{"x":84,"y":175,"width":90,"height":91,"rx":63,"fill":78,"stroke":79,"style":119},"176.0",[82,177,179],{"x":95,"y":178,"fill":79,"style":97,"textAnchor":98},"195.5","Progress bar fill",[59,181],{"x":102,"y":175,"width":103,"height":91,"rx":63,"fill":126,"stroke":127,"style":128},[82,183,184],{"x":106,"y":178,"fill":79,"style":131,"textAnchor":107},"width %",[59,186],{"x":111,"y":175,"width":103,"height":91,"rx":63,"fill":135,"stroke":135,"style":136},[82,188,189],{"x":114,"y":178,"fill":79,"style":131,"textAnchor":107},"scaleX from left origin",[59,191],{"x":84,"y":192,"width":90,"height":91,"rx":63,"fill":78,"stroke":79,"style":119},"206.0",[82,194,196],{"x":95,"y":195,"fill":79,"style":97,"textAnchor":98},"225.5","Fade in",[59,198],{"x":102,"y":192,"width":103,"height":91,"rx":63,"fill":79,"stroke":79,"style":92},[82,200,201],{"x":106,"y":195,"fill":79,"style":131,"textAnchor":107},"visibility + color",[59,203],{"x":111,"y":192,"width":103,"height":91,"rx":63,"fill":135,"stroke":135,"style":136},[82,205,206],{"x":114,"y":195,"fill":79,"style":131,"textAnchor":107},"opacity",[208,209,211],"h2",{"id":210},"rapid-diagnosis","Rapid Diagnosis",[213,214,215,223,229,235],"ul",{},[216,217,218,222],"li",{},[219,220,221],"strong",{},"Enable Paint flashing"," and trigger the animation; green flashes on every frame mean paint is involved.",[216,224,225,228],{},[219,226,227],{},"Record a trace","; Layout events in each animation frame mean layout properties are animated.",[216,230,231,234],{},[219,232,233],{},"Open the Animations panel"," to see each animation's properties and timing.",[216,236,237,240],{},[219,238,239],{},"Check CLS attribution"," for elements that move during animations.",[208,242,244],{"id":243},"root-cause-analysis","Root Cause Analysis",[15,246,247,250],{},[219,248,249],{},"1. Natural property choices."," Designers and developers think in positions and sizes.",[15,252,253,256,257,260,261,263],{},[219,254,255],{},"2. Library defaults."," Some animation libraries animate ",[30,258,259],{},"top","\u002F",[30,262,32],{}," or dimensions by default.",[15,265,266,269],{},[219,267,268],{},"3. Paint-heavy effects."," Shadows, gradients and blurs are expensive to repaint every frame.",[15,271,272,275],{},[219,273,274],{},"4. Content reflow requirements."," Some designs want surrounding content to move, which needs layout.",[208,277,279],{"id":278},"step-by-step-resolution","Step-by-Step Resolution",[281,282,284],"h3",{"id":283},"_1-replace-position-animations-with-translate","1. Replace position animations with translate",[286,287,292],"pre",{"className":288,"code":289,"language":290,"meta":291,"style":291},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast",".toast { transform: translateY(120%); transition: transform 220ms ease-out; }\n.toast.show { transform: translateY(0); }\n\u002F* trade-off: the toast's layout box never moves, so it must be positioned\n   (fixed or absolute) where it will finally appear. *\u002F\n","css","",[30,293,294,347,368,375],{"__ignoreMap":291},[295,296,299,303,307,310,313,316,319,322,326,329,332,335,338,341,344],"span",{"class":297,"line":298},"line",1,[295,300,302],{"class":301},"sPXB4",".toast",[295,304,306],{"class":305},"saISM"," { ",[295,308,309],{"class":301},"transform",[295,311,312],{"class":305},": ",[295,314,315],{"class":301},"translateY",[295,317,318],{"class":305},"(",[295,320,321],{"class":301},"120",[295,323,325],{"class":324},"sPARh","%",[295,327,328],{"class":305},"); ",[295,330,331],{"class":301},"transition",[295,333,334],{"class":305},": transform ",[295,336,337],{"class":301},"220",[295,339,340],{"class":324},"ms",[295,342,343],{"class":301}," ease-out",[295,345,346],{"class":305},"; }\n",[295,348,350,353,355,357,359,361,363,365],{"class":297,"line":349},2,[295,351,352],{"class":301},".toast.show",[295,354,306],{"class":305},[295,356,309],{"class":301},[295,358,312],{"class":305},[295,360,315],{"class":301},[295,362,318],{"class":305},[295,364,63],{"class":301},[295,366,367],{"class":305},"); }\n",[295,369,371],{"class":297,"line":370},3,[295,372,374],{"class":373},"sjfSM","\u002F* trade-off: the toast's layout box never moves, so it must be positioned\n",[295,376,378],{"class":297,"line":377},4,[295,379,380],{"class":373},"   (fixed or absolute) where it will finally appear. *\u002F\n",[281,382,384],{"id":383},"_2-replace-size-animations-with-scale","2. Replace size animations with scale",[286,386,388],{"className":288,"code":387,"language":290,"meta":291,"style":291},".card { transition: transform 160ms ease-out; }\n.card:hover { transform: scale(1.03); }\n\u002F* Progress bar: scale a full-width bar instead of animating width. *\u002F\n.progress-fill { transform-origin: left; transform: scaleX(var(--p, 0)); transition: transform 300ms; }\n\u002F* trade-off: scaling distorts children (text, borders); keep scale small or\n   counter-scale children for larger changes. *\u002F\n",[30,389,390,410,430,435,488,494],{"__ignoreMap":291},[295,391,392,395,397,399,401,404,406,408],{"class":297,"line":298},[295,393,394],{"class":301},".card",[295,396,306],{"class":305},[295,398,331],{"class":301},[295,400,334],{"class":305},[295,402,403],{"class":301},"160",[295,405,340],{"class":324},[295,407,343],{"class":301},[295,409,346],{"class":305},[295,411,412,415,417,419,421,423,425,428],{"class":297,"line":349},[295,413,414],{"class":301},".card:hover",[295,416,306],{"class":305},[295,418,309],{"class":301},[295,420,312],{"class":305},[295,422,156],{"class":301},[295,424,318],{"class":305},[295,426,427],{"class":301},"1.03",[295,429,367],{"class":305},[295,431,432],{"class":297,"line":370},[295,433,434],{"class":373},"\u002F* Progress bar: scale a full-width bar instead of animating width. *\u002F\n",[295,436,437,440,442,445,447,449,452,454,456,459,461,464,466,470,472,474,477,479,481,484,486],{"class":297,"line":377},[295,438,439],{"class":301},".progress-fill",[295,441,306],{"class":305},[295,443,444],{"class":301},"transform-origin",[295,446,312],{"class":305},[295,448,32],{"class":301},[295,450,451],{"class":305},"; ",[295,453,309],{"class":301},[295,455,312],{"class":305},[295,457,458],{"class":301},"scaleX",[295,460,318],{"class":305},[295,462,463],{"class":301},"var",[295,465,318],{"class":305},[295,467,469],{"class":468},"sQw3B","--p",[295,471,33],{"class":305},[295,473,63],{"class":301},[295,475,476],{"class":305},")); ",[295,478,331],{"class":301},[295,480,334],{"class":305},[295,482,483],{"class":301},"300",[295,485,340],{"class":324},[295,487,346],{"class":305},[295,489,491],{"class":297,"line":490},5,[295,492,493],{"class":373},"\u002F* trade-off: scaling distorts children (text, borders); keep scale small or\n",[295,495,497],{"class":297,"line":496},6,[295,498,499],{"class":373},"   counter-scale children for larger changes. *\u002F\n",[281,501,503],{"id":502},"_3-replace-shadow-animations-with-opacity","3. Replace shadow animations with opacity",[286,505,507],{"className":288,"code":506,"language":290,"meta":291,"style":291},".tile { position: relative; }\n.tile::after {\n  content: ''; position: absolute; inset: 0; border-radius: inherit;\n  box-shadow: 0 12px 28px rgba(0, 0, 0, .18); opacity: 0; transition: opacity 200ms;\n}\n.tile:hover::after { opacity: 1; }\n\u002F* trade-off: an extra pseudo-element per tile adds a little memory; fine for\n   dozens of tiles, worth reconsidering for thousands. *\u002F\n",[30,508,509,526,534,576,638,643,658,664],{"__ignoreMap":291},[295,510,511,514,516,519,521,524],{"class":297,"line":298},[295,512,513],{"class":301},".tile",[295,515,306],{"class":305},[295,517,518],{"class":301},"position",[295,520,312],{"class":305},[295,522,523],{"class":301},"relative",[295,525,346],{"class":305},[295,527,528,531],{"class":297,"line":349},[295,529,530],{"class":301},".tile::after",[295,532,533],{"class":305}," {\n",[295,535,536,539,541,545,547,549,551,554,556,559,561,563,565,568,570,573],{"class":297,"line":370},[295,537,538],{"class":301},"  content",[295,540,312],{"class":305},[295,542,544],{"class":543},"sZ8jY","''",[295,546,451],{"class":305},[295,548,518],{"class":301},[295,550,312],{"class":305},[295,552,553],{"class":301},"absolute",[295,555,451],{"class":305},[295,557,558],{"class":301},"inset",[295,560,312],{"class":305},[295,562,63],{"class":301},[295,564,451],{"class":305},[295,566,567],{"class":301},"border-radius",[295,569,312],{"class":305},[295,571,572],{"class":301},"inherit",[295,574,575],{"class":305},";\n",[295,577,578,581,583,585,588,591,594,596,599,601,603,605,607,609,611,613,616,618,620,622,624,626,628,631,634,636],{"class":297,"line":377},[295,579,580],{"class":301},"  box-shadow",[295,582,312],{"class":305},[295,584,63],{"class":301},[295,586,587],{"class":301}," 12",[295,589,590],{"class":324},"px",[295,592,593],{"class":301}," 28",[295,595,590],{"class":324},[295,597,598],{"class":301}," rgba",[295,600,318],{"class":305},[295,602,63],{"class":301},[295,604,33],{"class":305},[295,606,63],{"class":301},[295,608,33],{"class":305},[295,610,63],{"class":301},[295,612,33],{"class":305},[295,614,615],{"class":301},".18",[295,617,328],{"class":305},[295,619,206],{"class":301},[295,621,312],{"class":305},[295,623,63],{"class":301},[295,625,451],{"class":305},[295,627,331],{"class":301},[295,629,630],{"class":305},": opacity ",[295,632,633],{"class":301},"200",[295,635,340],{"class":324},[295,637,575],{"class":305},[295,639,640],{"class":297,"line":490},[295,641,642],{"class":305},"}\n",[295,644,645,648,650,652,654,656],{"class":297,"line":496},[295,646,647],{"class":301},".tile:hover::after",[295,649,306],{"class":305},[295,651,206],{"class":301},[295,653,312],{"class":305},[295,655,74],{"class":301},[295,657,346],{"class":305},[295,659,661],{"class":297,"line":660},7,[295,662,663],{"class":373},"\u002F* trade-off: an extra pseudo-element per tile adds a little memory; fine for\n",[295,665,667],{"class":297,"line":666},8,[295,668,669],{"class":373},"   dozens of tiles, worth reconsidering for thousands. *\u002F\n",[281,671,673],{"id":672},"_4-use-flip-for-layout-changes-that-must-animate","4. Use FLIP for layout changes that must animate",[15,675,676],{},"When an element must actually change layout position (reordering a list, expanding a card into a modal), use the FLIP technique: record the First position, apply the Last layout, Invert with a transform so it appears unchanged, then Play by animating the transform to none.",[286,678,682],{"className":679,"code":680,"language":681,"meta":291,"style":291},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","function flip(el, change) {\n  const first = el.getBoundingClientRect();\n  change();                                             \u002F\u002F apply the new layout\n  const last = el.getBoundingClientRect();\n  const dx = first.left - last.left, dy = first.top - last.top;\n  const sx = first.width \u002F last.width, sy = first.height \u002F last.height;\n  el.animate([{ transform: `translate(${dx}px, ${dy}px) scale(${sx}, ${sy})` }, { transform: 'none' }],\n    { duration: 250, easing: 'ease-out' });\n}\n\u002F\u002F trade-off: two layout reads (one forced) per FLIP; for many elements, read all\n\u002F\u002F \"first\" rects together, apply the change once, then read all \"last\" rects.\n","javascript",[30,683,684,706,726,737,752,783,813,858,875,880,886],{"__ignoreMap":291},[295,685,686,689,693,695,698,700,703],{"class":297,"line":298},[295,687,688],{"class":324},"function",[295,690,692],{"class":691},"smZ65"," flip",[295,694,318],{"class":305},[295,696,697],{"class":468},"el",[295,699,33],{"class":305},[295,701,702],{"class":468},"change",[295,704,705],{"class":305},") {\n",[295,707,708,711,714,717,720,723],{"class":297,"line":349},[295,709,710],{"class":324},"  const",[295,712,713],{"class":301}," first",[295,715,716],{"class":324}," =",[295,718,719],{"class":305}," el.",[295,721,722],{"class":691},"getBoundingClientRect",[295,724,725],{"class":305},"();\n",[295,727,728,731,734],{"class":297,"line":370},[295,729,730],{"class":691},"  change",[295,732,733],{"class":305},"();                                             ",[295,735,736],{"class":373},"\u002F\u002F apply the new layout\n",[295,738,739,741,744,746,748,750],{"class":297,"line":377},[295,740,710],{"class":324},[295,742,743],{"class":301}," last",[295,745,716],{"class":324},[295,747,719],{"class":305},[295,749,722],{"class":691},[295,751,725],{"class":305},[295,753,754,756,759,761,764,767,770,773,775,778,780],{"class":297,"line":490},[295,755,710],{"class":324},[295,757,758],{"class":301}," dx",[295,760,716],{"class":324},[295,762,763],{"class":305}," first.left ",[295,765,766],{"class":324},"-",[295,768,769],{"class":305}," last.left, ",[295,771,772],{"class":301},"dy",[295,774,716],{"class":324},[295,776,777],{"class":305}," first.top ",[295,779,766],{"class":324},[295,781,782],{"class":305}," last.top;\n",[295,784,785,787,790,792,795,797,800,803,805,808,810],{"class":297,"line":496},[295,786,710],{"class":324},[295,788,789],{"class":301}," sx",[295,791,716],{"class":324},[295,793,794],{"class":305}," first.width ",[295,796,260],{"class":324},[295,798,799],{"class":305}," last.width, ",[295,801,802],{"class":301},"sy",[295,804,716],{"class":324},[295,806,807],{"class":305}," first.height ",[295,809,260],{"class":324},[295,811,812],{"class":305}," last.height;\n",[295,814,815,818,821,824,827,830,833,835,838,841,844,846,849,852,855],{"class":297,"line":660},[295,816,817],{"class":305},"  el.",[295,819,820],{"class":691},"animate",[295,822,823],{"class":305},"([{ transform: ",[295,825,826],{"class":543},"`translate(${",[295,828,829],{"class":305},"dx",[295,831,832],{"class":543},"}px, ${",[295,834,772],{"class":305},[295,836,837],{"class":543},"}px) scale(${",[295,839,840],{"class":305},"sx",[295,842,843],{"class":543},"}, ${",[295,845,802],{"class":305},[295,847,848],{"class":543},"})`",[295,850,851],{"class":305}," }, { transform: ",[295,853,854],{"class":543},"'none'",[295,856,857],{"class":305}," }],\n",[295,859,860,863,866,869,872],{"class":297,"line":666},[295,861,862],{"class":305},"    { duration: ",[295,864,865],{"class":301},"250",[295,867,868],{"class":305},", easing: ",[295,870,871],{"class":543},"'ease-out'",[295,873,874],{"class":305}," });\n",[295,876,878],{"class":297,"line":877},9,[295,879,642],{"class":305},[295,881,883],{"class":297,"line":882},10,[295,884,885],{"class":373},"\u002F\u002F trade-off: two layout reads (one forced) per FLIP; for many elements, read all\n",[295,887,889],{"class":297,"line":888},11,[295,890,891],{"class":373},"\u002F\u002F \"first\" rects together, apply the change once, then read all \"last\" rects.\n",[15,893,894],{},[50,895,57,898,57,901,57,904,57,906,57,923,57,926,57,928,57,932,57,938,57,942,57,945,57,949,57,952,57,956,57,959,57,963,57,966,57,969,57,973,57,976,57,978,57,986,57,990,57],{"viewBox":896,"width":53,"role":54,"ariaLabel":897,"style":56},"0 0 760 149","The four steps of FLIP for animating layout changes with transforms.",[59,899],{"className":900,"x":63,"y":63,"width":53,"height":53,"fill":64},[62],[66,902,903],{},"The FLIP technique",[70,905,897],{},[907,908,909],"defs",{},[910,911,918],"marker",{"id":912,"viewBox":913,"refX":914,"refY":915,"markerWidth":916,"markerHeight":916,"orient":917},"fa6b3aaa32","0 0 10 10","9","5","7","auto-start-reverse",[919,920],"path",{"d":921,"fill":79,"style":922},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[59,924],{"x":74,"y":74,"width":75,"height":925,"rx":77,"fill":78,"stroke":79,"style":80},"147",[82,927,903],{"x":84,"y":85,"fill":79,"style":86},[59,929],{"x":84,"y":89,"width":159,"height":930,"rx":931,"fill":135,"stroke":135,"style":136},"71.0","6",[82,933,937],{"x":934,"y":935,"fill":79,"style":936,"textAnchor":107},"101.0","78.0","font-size:13px;font-weight:700","First",[82,939,941],{"x":934,"y":940,"fill":79,"style":131,"textAnchor":107},"96.0","measure start",[59,943],{"x":944,"y":89,"width":159,"height":930,"rx":931,"fill":135,"stroke":135,"style":136},"214.0",[82,946,948],{"x":947,"y":935,"fill":79,"style":936,"textAnchor":107},"287.0","Last",[82,950,951],{"x":947,"y":940,"fill":79,"style":131,"textAnchor":107},"apply new layout,",[82,953,955],{"x":947,"y":954,"fill":79,"style":131,"textAnchor":107},"112.0","measure",[59,957],{"x":958,"y":89,"width":159,"height":930,"rx":931,"fill":135,"stroke":135,"style":136},"400.0",[82,960,962],{"x":961,"y":935,"fill":79,"style":936,"textAnchor":107},"473.0","Invert",[82,964,965],{"x":961,"y":940,"fill":79,"style":131,"textAnchor":107},"transform back to start",[59,967],{"x":968,"y":89,"width":159,"height":930,"rx":931,"fill":126,"stroke":127,"style":128},"586.0",[82,970,972],{"x":971,"y":935,"fill":79,"style":936,"textAnchor":107},"659.0","Play",[82,974,975],{"x":971,"y":940,"fill":79,"style":131,"textAnchor":107},"animate transform to",[82,977,78],{"x":971,"y":954,"fill":79,"style":131,"textAnchor":107},[297,979],{"x1":980,"y1":981,"x2":982,"y2":981,"stroke":79,"strokeWidth":983,"style":984,"markerEnd":985},"174.0","91.5","212.0","1.5","stroke-opacity:0.6","url(#fa6b3aaa32)",[297,987],{"x1":988,"y1":981,"x2":989,"y2":981,"stroke":79,"strokeWidth":983,"style":984,"markerEnd":985},"360.0","398.0",[297,991],{"x1":992,"y1":981,"x2":993,"y2":981,"stroke":79,"strokeWidth":983,"style":984,"markerEnd":985},"546.0","584.0",[208,995,997],{"id":996},"verification","Verification",[15,999,1000],{},"With Paint flashing on, the animated element should flash once at most (when its layer is created), not on every frame. A Performance trace of the animation should show frames without Layout or Paint events, and the Frames track should show no dropped frames under 4x CPU throttling. Run a busy-main-thread test: start the animation and simultaneously run a 300ms task in the console; compositor animations continue smoothly.",[208,1002,1004],{"id":1003},"worked-example-a-dashboards-sidebar-collapse","Worked Example: A Dashboard's Sidebar Collapse",[15,1006,1007,1008,1010],{},"An analytics dashboard collapsed its sidebar by animating ",[30,1009,36],{}," from 260px to 64px; the main content area, with several charts, re-laid out every frame. On laptops with integrated graphics, the 300ms animation dropped half its frames, and charts redrew during the transition. The team changed the sidebar to a fixed 260px element that translates left by 196px, with the content area's offset switching once at the start of the transition (no animation), and charts resizing once at the end. The animation became smooth, and chart redraws dropped from 18 per collapse to one.",[208,1012,1014],{"id":1013},"when-layout-animation-is-acceptable","When Layout Animation Is Acceptable",[15,1016,1017,1018,1021,1022,1025,1026,1029,1030,1033,1034,1037],{},"Sometimes a layout animation is the right choice: an accordion where content below must move down, or a list item that grows and pushes siblings. Make it as cheap as possible. Keep the animated subtree small and contained (",[30,1019,1020],{},"contain: layout"," on the container), keep durations short (150–250ms), avoid running heavy JavaScript during the animation, and test on mid-range devices. For accordions, the ",[30,1023,1024],{},"grid-template-rows: 0fr"," → ",[30,1027,1028],{},"1fr"," technique animates a single grid track rather than many elements' heights, and ",[30,1031,1032],{},"interpolate-size: allow-keywords"," allows animating to ",[30,1035,1036],{},"height: auto"," where supported. Accept the cost knowingly rather than by default.",[15,1039,1040],{},[50,1041,57,1044,57,1047,57,1050,57,1052,57,1055,57,1057,57,1064,57,1071,57,1076,57,1079,57,1084,57],{"viewBox":1042,"width":53,"role":54,"ariaLabel":1043,"style":56},"0 0 760 132","Bar chart of dropped frames for a sidebar collapse animated with width compared with translateX.",[59,1045],{"className":1046,"x":63,"y":63,"width":53,"height":53,"fill":64},[62],[66,1048,1049],{},"Dropped frames in a 300ms sidebar collapse (4x CPU throttle)",[70,1051,1043],{},[59,1053],{"x":74,"y":74,"width":75,"height":1054,"rx":77,"fill":78,"stroke":79,"style":80},"130",[82,1056,1049],{"x":84,"y":85,"fill":79,"style":86},[82,1058,1063],{"x":1059,"y":1060,"fill":79,"style":1061,"textAnchor":1062},"250.3","70.0","font-size:13px","end","width 260px to 64px",[59,1065],{"x":1066,"y":89,"width":1067,"height":1068,"rx":1069,"fill":78,"stroke":79,"style":1070},"262.3","405.7","19","3","fill-opacity:0.7;stroke-opacity:0.35",[82,1072,1075],{"x":1073,"y":1060,"fill":79,"style":1074},"674.0","font-size:12px;font-weight:600","9frames",[82,1077,1078],{"x":1059,"y":934,"fill":79,"style":1061,"textAnchor":1062},"translateX with one-time offset change",[59,1080],{"x":1066,"y":1081,"width":1082,"height":1068,"rx":1069,"fill":126,"stroke":127,"style":1083},"87.0","2.0","fill-opacity:0.7;stroke-opacity:0.9",[82,1085,1087],{"x":1086,"y":934,"fill":79,"style":1074},"270.3","0frames",[208,1089,1091],{"id":1090},"common-mistakes","Common Mistakes",[213,1093,1094,1100,1106,1115],{},[216,1095,1096,1099],{},[219,1097,1098],{},"Animating top or left on fixed elements."," Still layout every frame; use translate.",[216,1101,1102,1105],{},[219,1103,1104],{},"Scaling text-heavy elements a lot."," Text looks blurry mid-animation; keep scale changes small.",[216,1107,1108,1111,1112,1114],{},[219,1109,1110],{},"Animating width for progress bars."," Use ",[30,1113,458],{}," with a left origin.",[216,1116,1117,1120],{},[219,1118,1119],{},"FLIP for many elements without batching reads."," Thrashing on the measurement step.",[208,1122,1124],{"id":1123},"edge-cases","Edge Cases",[15,1126,1127,1130],{},[219,1128,1129],{},"Transforms and stacking contexts."," A transform creates a stacking context and a containing block for fixed-position descendants, which can change how children render.",[15,1132,1133,1136],{},[219,1134,1135],{},"Sub-pixel text rendering."," Text on composited layers may render with grayscale antialiasing; usually acceptable.",[15,1138,1139,1142],{},[219,1140,1141],{},"Hit testing."," Transformed elements are hit-tested at their visual position, so buttons work as expected during and after animation.",[15,1144,1145,1148,1149,1152,1153,1156],{},[219,1146,1147],{},"Reduced motion."," Wrap animations in ",[30,1150,1151],{},"prefers-reduced-motion: no-preference"," or disable them under ",[30,1154,1155],{},"reduce",".",[208,1158,1160],{"id":1159},"faq","FAQ",[1162,1163,1166,1170],"details",{"className":1164},[1165],"faq-item",[1167,1168,1169],"summary",{},"Why are transform and opacity cheap to animate?",[15,1171,1172],{},"They do not change layout or the painted content of a layer. The compositor can apply them to an existing layer each frame without involving the main thread.",[1162,1174,1176,1179],{"className":1175},[1165],[1167,1177,1178],{},"Does translate3d still matter?",[15,1180,1181,1182,1185],{},"It used to force layer promotion. Modern browsers promote elements with active transform animations automatically; ",[30,1183,1184],{},"will-change: transform"," is the explicit, standard way to request it in advance.",[1162,1187,1189,1192],{"className":1188},[1165],[1167,1190,1191],{},"Can I animate colours cheaply?",[15,1193,1194],{},"Colour changes require repainting. For large areas, cross-fade two layers with opacity instead; small colour transitions on buttons are usually fine.",[1162,1196,1198,1201],{"className":1197},[1165],[1167,1199,1200],{},"Do individual transform properties (translate, scale, rotate) perform the same?",[15,1202,1203,1204,33,1207,1209,1210,1213,1214,1216],{},"Yes. The individual ",[30,1205,1206],{},"translate",[30,1208,156],{}," and ",[30,1211,1212],{},"rotate"," properties are composited like ",[30,1215,309],{}," and make it easier to animate one without overwriting others.",[1162,1218,1220,1223],{"className":1219},[1165],[1167,1221,1222],{},"Does the Web Animations API run on the compositor?",[15,1224,1225],{},"For transform and opacity, yes, in the same way as CSS animations. It also provides control methods like pause and reverse.",[1162,1227,1229,1232],{"className":1228},[1165],[1167,1230,1231],{},"Is filter compositor-friendly?",[15,1233,1234],{},"Some filter animations can be composited in some browsers, but blur in particular can be expensive. Test on target devices.",[1162,1236,1238,1241],{"className":1237},[1165],[1167,1239,1240],{},"How do I animate an element appearing in the layout?",[15,1242,1243],{},"Let it take its space immediately (no layout animation), and animate opacity and a small translate for the visual entrance. Surrounding content moves once rather than continuously.",[1162,1245,1247,1250],{"className":1246},[1165],[1167,1248,1249],{},"How do I check whether an animation is composited?",[15,1251,1252],{},"Open the Animations panel or record a trace. If animation frames show no Recalculate Style, Layout or Paint events for the element, and Paint flashing stays dark, the animation is running on the compositor.",[1162,1254,1256,1259],{"className":1255},[1165],[1167,1257,1258],{},"Is it worth rewriting short hover effects?",[15,1260,1261],{},"For small elements and short durations, the cost of painting is low and rewriting may not matter. Prioritise animations on large areas, animations that run during page load, and anything that moves layout around other content.",[208,1263,1265],{"id":1264},"related","Related",[213,1267,1268,1275,1282],{},[216,1269,1270,1274],{},[19,1271,1273],{"href":1272},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fwill-change-overuse-and-layer-explosion\u002F","will-change overuse and layer explosion"," — layers for these animations.",[216,1276,1277,1281],{},[19,1278,1280],{"href":1279},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-animating-layout-properties\u002F","Fixing CLS from animating layout properties"," — the CLS view of the same problem.",[216,1283,1284,1288],{},[19,1285,1287],{"href":1286},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002F","Batching DOM reads and writes"," — FLIP measurement without thrashing.",[1290,1291,1293],"script",{"type":1292},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Animate Only transform and opacity\",\n  \"description\": \"Practical rewrites of common UI animations into transform and opacity, with the trade-offs each rewrite brings.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Replace position animations with translate\",\n      \"text\": \"Replace position animations with translate\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Replace size animations with scale\",\n      \"text\": \"Replace size animations with scale\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Replace shadow animations with opacity\",\n      \"text\": \"Replace shadow animations with opacity\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Use FLIP for layout changes that must animate\",\n      \"text\": \"When an element must actually change layout position (reordering a list, expanding a card into a modal), use the FLIP technique: record the First position, apply the Last layout, Invert with a transform so it appears unchanged, then Play by animating the transform to none.\"\n    }\n  ]\n}\n",[1290,1295,1296],{"type":1292},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Animate Only transform and opacity\",\n  \"description\": \"Practical rewrites of common UI animations into transform and opacity, with the trade-offs each rewrite brings.\",\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\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002F\"\n  }\n}\n",[1290,1298,1299],{"type":1292},"\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      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Animation & Compositing Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Animating Only transform and opacity\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002F\"\n    }\n  ]\n}\n",[1301,1302,1303],"style",{},"html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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 .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}",{"title":291,"searchDepth":349,"depth":349,"links":1305},[1306,1307,1308,1314,1315,1316,1317,1318,1319,1320],{"id":210,"depth":349,"text":211},{"id":243,"depth":349,"text":244},{"id":278,"depth":349,"text":279,"children":1309},[1310,1311,1312,1313],{"id":283,"depth":370,"text":284},{"id":383,"depth":370,"text":384},{"id":502,"depth":370,"text":503},{"id":672,"depth":370,"text":673},{"id":996,"depth":349,"text":997},{"id":1003,"depth":349,"text":1004},{"id":1013,"depth":349,"text":1014},{"id":1090,"depth":349,"text":1091},{"id":1123,"depth":349,"text":1124},{"id":1159,"depth":349,"text":1160},{"id":1264,"depth":349,"text":1265},"Practical rewrites of common UI animations into transform and opacity, with the trade-offs each rewrite brings.","md",{"slug":1324,"type":1325,"breadcrumb":1326,"datePublished":1333,"dateModified":1333},"animating-only-transform-and-opacity","article",[1327,1329,1330,1331],{"name":1328,"url":260},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1332},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002F","2026-10-06",true,"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity",{"title":1337,"description":1338},"Animating Only transform and opacity for 60fps","Rewrite slide, resize, fade, shadow and accordion animations to use transform and opacity so they run on the compositor, avoiding layout, paint and CLS.","rendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002Findex","cfKWbxW45N4rnioBXZIUf0CDcCaVgA0M3_ev33JwEvU",[1342,1345],{"title":22,"path":1343,"stem":1344,"children":-1},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance","rendering-css-performance\u002Fanimation-and-compositing-performance\u002Findex",{"title":1346,"path":1347,"stem":1348,"children":-1},"Scroll-Driven Animations Without JavaScript","\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fscroll-driven-animations-without-javascript","rendering-css-performance\u002Fanimation-and-compositing-performance\u002Fscroll-driven-animations-without-javascript\u002Findex",1791308077713]