[{"data":1,"prerenderedAt":1440},["ShallowReactive",2],{"content:\u002Frendering-css-performance\u002Fanimation-and-compositing-performance":3,"surroundings:\u002Frendering-css-performance\u002Fanimation-and-compositing-performance":1432},{"id":4,"title":5,"body":6,"description":1412,"extension":1413,"meta":1414,"navigation":1425,"path":1426,"seo":1427,"stem":1430,"__hash__":1431},"content\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Findex.md","Animation & Compositing Performance",{"type":7,"value":8,"toc":1390},"minimark",[9,14,24,64,67,232,237,255,259,272,276,291,295,298,302,316,468,472,488,727,844,848,860,873,876,880,886,892,914,920,930,934,937,988,992,1016,1079,1083,1105,1109,1115,1119,1126,1130,1133,1136,1140,1143,1149,1153,1180,1184,1224,1228,1244,1253,1262,1271,1291,1303,1316,1320,1350,1354,1376,1381,1384,1387],[10,11,13],"h1",{"id":12},"animation-compositing-performance-smooth-motion-without-main-thread-work","Animation & Compositing Performance: Smooth Motion Without Main-Thread Work",[15,16,17,18,23],"p",{},"This topic is part of ",[19,20,22],"a",{"href":21},"\u002Frendering-css-performance\u002F","Rendering & CSS Performance",". Smooth animation requires producing a new frame every 16.7ms on a 60Hz display, or every 8.3ms at 120Hz. Whether that is easy or impossible depends on what each frame has to do. If an animation changes a property that affects layout, each frame needs style recalculation, layout, paint and compositing, all coordinated by the main thread, which is also running JavaScript, handling input and parsing data. If the main thread is busy for 50ms, the animation stutters.",[15,25,26,27,31,32,36,37,40,41,43,44,46,47,50,51,50,54,50,57,43,60,63],{},"Browsers have a way out: the ",[28,29,30],"strong",{},"compositor",". Pages are drawn into layers, and the compositor thread (with the GPU) assembles those layers into the final frame. Changes to a layer's ",[33,34,35],"code",{},"transform"," or ",[33,38,39],{},"opacity"," — and a few other properties such as some filters — can be applied by the compositor alone, without asking the main thread to re-run style, layout or paint. Animations of those properties keep running smoothly even when JavaScript is busy. This is why the core rule of animation performance is: animate ",[33,42,35],{}," and ",[33,45,39],{},", and avoid animating layout properties like ",[33,48,49],{},"width",", ",[33,52,53],{},"height",[33,55,56],{},"top",[33,58,59],{},"left",[33,61,62],{},"margin",".",[15,65,66],{},"Layers themselves have costs — memory and compositing time — so promoting everything is counterproductive. And modern CSS adds new tools: scroll-driven animations that run on the compositor without scroll listeners, and the View Transitions API, which animates between DOM states using snapshots. Each has its own performance profile, covered in the guides of this topic.",[15,68,69],{},[70,71,77,78,77,85,77,89,77,92,77,101,77,107,77,113,77,120,77,124,77,129,77,132,77,136,77,139,77,143,77,147,77,151,77,156,77,160,77,162,77,164,77,166,77,169,77,172,77,176,77,180,77,183,77,185,77,187,77,189,77,191,77,194,77,198,77,200,77,202,77,204,77,206,77,208,77,211,77,214,77,218,77,220,77,222,77,224,77,227,77,229,77],"svg",{"viewBox":72,"width":73,"role":74,"ariaLabel":75,"style":76},"0 0 760 228","100%","img","Which rendering stages run each frame when animating different kinds of CSS properties.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[79,80],"rect",{"className":81,"x":83,"y":83,"width":73,"height":73,"fill":84},[82],"svg-canvas","0","#ffffff",[86,87,88],"title",{},"Cost of animating different CSS properties",[90,91,75],"desc",{},[79,93],{"x":94,"y":94,"width":95,"height":96,"rx":97,"fill":98,"stroke":99,"style":100},"1","758","226","10","none","currentColor","stroke-opacity:0.18",[102,103,88],"text",{"x":104,"y":105,"fill":99,"style":106},"28.0","34.0","font-size:16px;font-weight:700",[79,108],{"x":104,"y":109,"width":110,"height":111,"rx":83,"fill":99,"stroke":99,"style":112},"56.0","198.5","30.0","fill-opacity:0.06;stroke-opacity:0.4",[102,114,119],{"x":115,"y":116,"fill":99,"style":117,"textAnchor":118},"38.0","75.5","font-size:12.5px;font-weight:700","start","Property type",[79,121],{"x":122,"y":109,"width":123,"height":111,"rx":83,"fill":99,"stroke":99,"style":112},"226.5","168.5",[102,125,128],{"x":126,"y":116,"fill":99,"style":117,"textAnchor":127},"310.8","middle","Layout",[79,130],{"x":131,"y":109,"width":123,"height":111,"rx":83,"fill":99,"stroke":99,"style":112},"395.0",[102,133,135],{"x":134,"y":116,"fill":99,"style":117,"textAnchor":127},"479.3","Paint",[79,137],{"x":138,"y":109,"width":123,"height":111,"rx":83,"fill":99,"stroke":99,"style":112},"563.5",[102,140,142],{"x":141,"y":116,"fill":99,"style":117,"textAnchor":127},"647.8","Composite only",[79,144],{"x":104,"y":145,"width":110,"height":111,"rx":83,"fill":98,"stroke":99,"style":146},"86.0","stroke-opacity:0.35",[102,148,150],{"x":115,"y":149,"fill":99,"style":117,"textAnchor":118},"105.5","width, height, top, left, margin",[79,152],{"x":122,"y":145,"width":123,"height":111,"rx":83,"fill":153,"stroke":154,"style":155},"#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[102,157,159],{"x":126,"y":149,"fill":99,"style":158,"textAnchor":127},"font-size:12px","every frame",[79,161],{"x":131,"y":145,"width":123,"height":111,"rx":83,"fill":153,"stroke":154,"style":155},[102,163,159],{"x":134,"y":149,"fill":99,"style":158,"textAnchor":127},[79,165],{"x":138,"y":145,"width":123,"height":111,"rx":83,"fill":153,"stroke":154,"style":155},[102,167,168],{"x":141,"y":149,"fill":99,"style":158,"textAnchor":127},"no",[79,170],{"x":104,"y":171,"width":110,"height":111,"rx":83,"fill":98,"stroke":99,"style":146},"116.0",[102,173,175],{"x":115,"y":174,"fill":99,"style":117,"textAnchor":118},"135.5","color, background, box-shadow",[79,177],{"x":122,"y":171,"width":123,"height":111,"rx":83,"fill":178,"stroke":178,"style":179},"#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[102,181,182],{"x":126,"y":174,"fill":99,"style":158,"textAnchor":127},"skipped",[79,184],{"x":131,"y":171,"width":123,"height":111,"rx":83,"fill":153,"stroke":154,"style":155},[102,186,159],{"x":134,"y":174,"fill":99,"style":158,"textAnchor":127},[79,188],{"x":138,"y":171,"width":123,"height":111,"rx":83,"fill":153,"stroke":154,"style":155},[102,190,168],{"x":141,"y":174,"fill":99,"style":158,"textAnchor":127},[79,192],{"x":104,"y":193,"width":110,"height":111,"rx":83,"fill":98,"stroke":99,"style":146},"146.0",[102,195,197],{"x":115,"y":196,"fill":99,"style":117,"textAnchor":118},"165.5","transform, opacity",[79,199],{"x":122,"y":193,"width":123,"height":111,"rx":83,"fill":178,"stroke":178,"style":179},[102,201,182],{"x":126,"y":196,"fill":99,"style":158,"textAnchor":127},[79,203],{"x":131,"y":193,"width":123,"height":111,"rx":83,"fill":178,"stroke":178,"style":179},[102,205,182],{"x":134,"y":196,"fill":99,"style":158,"textAnchor":127},[79,207],{"x":138,"y":193,"width":123,"height":111,"rx":83,"fill":178,"stroke":178,"style":179},[102,209,210],{"x":141,"y":196,"fill":99,"style":158,"textAnchor":127},"yes, on its own layer",[79,212],{"x":104,"y":213,"width":110,"height":111,"rx":83,"fill":98,"stroke":99,"style":146},"176.0",[102,215,217],{"x":115,"y":216,"fill":99,"style":117,"textAnchor":118},"195.5","filter (many), clip-path (some)",[79,219],{"x":122,"y":213,"width":123,"height":111,"rx":83,"fill":178,"stroke":178,"style":179},[102,221,182],{"x":126,"y":216,"fill":99,"style":158,"textAnchor":127},[79,223],{"x":131,"y":213,"width":123,"height":111,"rx":83,"fill":99,"stroke":99,"style":112},[102,225,226],{"x":134,"y":216,"fill":99,"style":158,"textAnchor":127},"depends",[79,228],{"x":138,"y":213,"width":123,"height":111,"rx":83,"fill":99,"stroke":99,"style":112},[102,230,231],{"x":141,"y":216,"fill":99,"style":158,"textAnchor":127},"browser-dependent",[233,234,236],"h2",{"id":235},"the-metric-degradation-this-topic-addresses","The Metric Degradation This Topic Addresses",[15,238,239,240,243,244,247,248,250,251,254],{},"Animation problems show up first as ",[28,241,242],{},"jank",": visibly uneven motion, dropped frames, stuttering scrolling. Chrome reports dropped frames in the Performance panel's Frames track, and some RUM tools collect smoothness metrics. They also show up in Core Web Vitals. Animations of layout properties cause ",[28,245,246],{},"layout shifts"," that count towards CLS when not triggered by user input within the last 500ms — a banner sliding in by animating ",[33,249,56],{},", or an expanding element pushing content down. Main-thread animations compete with input handling, adding to ",[28,252,253],{},"INP",": an interaction during a JavaScript-driven animation has to wait for animation frames to finish. Heavy layer usage on low-memory devices can cause the browser to drop layers or even crash tabs.",[233,256,258],{"id":257},"prerequisites","Prerequisites",[260,261,262,266,269],"ul",{},[263,264,265],"li",{},"A Performance panel recording of the animation or interaction, with CPU throttling, and the Frames track visible.",[263,267,268],{},"The Rendering drawer in DevTools: paint flashing, layer borders and the frame rendering stats overlay.",[263,270,271],{},"The Layers panel, to inspect layer count and memory.",[233,273,275],{"id":274},"_1-environment-setup-make-rendering-work-visible","1. Environment Setup: Make Rendering Work Visible",[15,277,278,279,282,283,286,287,290],{},"Enable ",[28,280,281],{},"Paint flashing"," to see what repaints during an animation (green flashes on areas being painted), ",[28,284,285],{},"Layer borders"," to see compositing layers, and ",[28,288,289],{},"Frame Rendering Stats"," for a live FPS meter. Throttle CPU to 4x or 6x to approximate a mid-range phone.",[233,292,294],{"id":293},"_2-capture-a-baseline","2. Capture a Baseline",[15,296,297],{},"Record the animation in the Performance panel. In the Frames track, note dropped and partially presented frames. In the Main track, note whether each frame contains Recalculate Style, Layout and Paint events — their presence means the animation is not compositor-only.",[233,299,301],{"id":300},"_3-isolate-the-animated-properties","3. Isolate the Animated Properties",[15,303,304,305,50,308,311,312,315],{},"List the properties each animation changes, from CSS (",[33,306,307],{},"transition",[33,309,310],{},"@keyframes",") or JavaScript (style changes in ",[33,313,314],{},"requestAnimationFrame",", Web Animations API, animation libraries). Identify those that trigger layout or paint.",[317,318,323],"pre",{"className":319,"code":320,"language":321,"meta":322,"style":322},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F\u002F List running animations and the properties they animate.\ndocument.getAnimations().map((a) => ({\n  target: a.effect?.target?.className,\n  props: a.effect?.getKeyframes().flatMap((k) => Object.keys(k)).filter((p) => !['offset', 'easing', 'composite', 'computedOffset'].includes(p)),\n}));\n\u002F\u002F trade-off: this only lists CSS and Web Animations; JavaScript loops that set\n\u002F\u002F styles directly do not appear and must be found in the Performance panel.\n","javascript","",[33,324,325,334,367,373,450,456,462],{"__ignoreMap":322},[326,327,330],"span",{"class":328,"line":329},"line",1,[326,331,333],{"class":332},"sjfSM","\u002F\u002F List running animations and the properties they animate.\n",[326,335,337,341,345,348,351,354,357,360,364],{"class":328,"line":336},2,[326,338,340],{"class":339},"saISM","document.",[326,342,344],{"class":343},"smZ65","getAnimations",[326,346,347],{"class":339},"().",[326,349,350],{"class":343},"map",[326,352,353],{"class":339},"((",[326,355,19],{"class":356},"sQw3B",[326,358,359],{"class":339},") ",[326,361,363],{"class":362},"sPARh","=>",[326,365,366],{"class":339}," ({\n",[326,368,370],{"class":328,"line":369},3,[326,371,372],{"class":339},"  target: a.effect?.target?.className,\n",[326,374,376,379,382,384,387,389,392,394,396,399,402,405,408,410,412,414,416,419,422,426,428,431,433,436,438,441,444,447],{"class":328,"line":375},4,[326,377,378],{"class":339},"  props: a.effect?.",[326,380,381],{"class":343},"getKeyframes",[326,383,347],{"class":339},[326,385,386],{"class":343},"flatMap",[326,388,353],{"class":339},[326,390,391],{"class":356},"k",[326,393,359],{"class":339},[326,395,363],{"class":362},[326,397,398],{"class":339}," Object.",[326,400,401],{"class":343},"keys",[326,403,404],{"class":339},"(k)).",[326,406,407],{"class":343},"filter",[326,409,353],{"class":339},[326,411,15],{"class":356},[326,413,359],{"class":339},[326,415,363],{"class":362},[326,417,418],{"class":362}," !",[326,420,421],{"class":339},"[",[326,423,425],{"class":424},"sZ8jY","'offset'",[326,427,50],{"class":339},[326,429,430],{"class":424},"'easing'",[326,432,50],{"class":339},[326,434,435],{"class":424},"'composite'",[326,437,50],{"class":339},[326,439,440],{"class":424},"'computedOffset'",[326,442,443],{"class":339},"].",[326,445,446],{"class":343},"includes",[326,448,449],{"class":339},"(p)),\n",[326,451,453],{"class":328,"line":452},5,[326,454,455],{"class":339},"}));\n",[326,457,459],{"class":328,"line":458},6,[326,460,461],{"class":332},"\u002F\u002F trade-off: this only lists CSS and Web Animations; JavaScript loops that set\n",[326,463,465],{"class":328,"line":464},7,[326,466,467],{"class":332},"\u002F\u002F styles directly do not appear and must be found in the Performance panel.\n",[233,469,471],{"id":470},"_4-apply-move-motion-to-transform-and-opacity","4. Apply: Move Motion to transform and opacity",[15,473,474,475,477,478,480,481,484,485,63],{},"Rewrite animations to use ",[33,476,35],{}," (translate, scale, rotate) and ",[33,479,39],{},". Use ",[33,482,483],{},"will-change"," sparingly for elements about to animate. Replace scroll-linked JavaScript with scroll-driven animations. Respect ",[33,486,487],{},"prefers-reduced-motion",[317,489,493],{"className":490,"code":491,"language":492,"meta":322,"style":322},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u002F* Slide-in panel: compositor-only. *\u002F\n.panel { transform: translateX(100%); transition: transform 280ms cubic-bezier(.2,.8,.2,1); }\n.panel.open { transform: translateX(0); }\n\u002F* Fade + scale for a dialog. *\u002F\n@keyframes pop { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }\n.dialog[open] { animation: pop 180ms ease-out; }\n@media (prefers-reduced-motion: reduce) { .panel, .dialog[open] { transition: none; animation: none; } }\n\u002F* trade-off: transform does not change layout, so siblings do not reflow around\n   the moving element; designs that need content to move aside need another approach. *\u002F\n","css",[33,494,495,500,565,584,589,649,679,715,721],{"__ignoreMap":322},[326,496,497],{"class":328,"line":329},[326,498,499],{"class":332},"\u002F* Slide-in panel: compositor-only. *\u002F\n",[326,501,502,506,509,511,514,517,520,523,526,529,531,534,537,540,543,545,548,551,554,556,558,560,562],{"class":328,"line":336},[326,503,505],{"class":504},"sPXB4",".panel",[326,507,508],{"class":339}," { ",[326,510,35],{"class":504},[326,512,513],{"class":339},": ",[326,515,516],{"class":504},"translateX",[326,518,519],{"class":339},"(",[326,521,522],{"class":504},"100",[326,524,525],{"class":362},"%",[326,527,528],{"class":339},"); ",[326,530,307],{"class":504},[326,532,533],{"class":339},": transform ",[326,535,536],{"class":504},"280",[326,538,539],{"class":362},"ms",[326,541,542],{"class":504}," cubic-bezier",[326,544,519],{"class":339},[326,546,547],{"class":504},".2",[326,549,550],{"class":339},",",[326,552,553],{"class":504},".8",[326,555,550],{"class":339},[326,557,547],{"class":504},[326,559,550],{"class":339},[326,561,94],{"class":504},[326,563,564],{"class":339},"); }\n",[326,566,567,570,572,574,576,578,580,582],{"class":328,"line":369},[326,568,569],{"class":504},".panel.open",[326,571,508],{"class":339},[326,573,35],{"class":504},[326,575,513],{"class":339},[326,577,516],{"class":504},[326,579,519],{"class":339},[326,581,83],{"class":504},[326,583,564],{"class":339},[326,585,586],{"class":328,"line":375},[326,587,588],{"class":332},"\u002F* Fade + scale for a dialog. *\u002F\n",[326,590,591,593,596,598,601,603,605,607,609,612,614,616,619,621,624,627,630,632,634,636,638,640,642,644,646],{"class":328,"line":452},[326,592,310],{"class":362},[326,594,595],{"class":356}," pop",[326,597,508],{"class":339},[326,599,600],{"class":504},"from",[326,602,508],{"class":339},[326,604,39],{"class":504},[326,606,513],{"class":339},[326,608,83],{"class":504},[326,610,611],{"class":339},"; ",[326,613,35],{"class":504},[326,615,513],{"class":339},[326,617,618],{"class":504},"scale",[326,620,519],{"class":339},[326,622,623],{"class":504},".96",[326,625,626],{"class":339},"); } ",[326,628,629],{"class":504},"to",[326,631,508],{"class":339},[326,633,39],{"class":504},[326,635,513],{"class":339},[326,637,94],{"class":504},[326,639,611],{"class":339},[326,641,35],{"class":504},[326,643,513],{"class":339},[326,645,98],{"class":504},[326,647,648],{"class":339},"; } }\n",[326,650,651,654,656,659,662,665,668,671,673,676],{"class":328,"line":458},[326,652,653],{"class":504},".dialog",[326,655,421],{"class":339},[326,657,658],{"class":504},"open",[326,660,661],{"class":339},"] { ",[326,663,664],{"class":504},"animation",[326,666,667],{"class":339},": pop ",[326,669,670],{"class":504},"180",[326,672,539],{"class":362},[326,674,675],{"class":504}," ease-out",[326,677,678],{"class":339},"; }\n",[326,680,681,684,687,689,691,693,695,697,699,701,703,705,707,709,711,713],{"class":328,"line":464},[326,682,683],{"class":362},"@media",[326,685,686],{"class":339}," (prefers-reduced-motion: reduce) { ",[326,688,505],{"class":504},[326,690,50],{"class":339},[326,692,653],{"class":504},[326,694,421],{"class":339},[326,696,658],{"class":504},[326,698,661],{"class":339},[326,700,307],{"class":504},[326,702,513],{"class":339},[326,704,98],{"class":504},[326,706,611],{"class":339},[326,708,664],{"class":504},[326,710,513],{"class":339},[326,712,98],{"class":504},[326,714,648],{"class":339},[326,716,718],{"class":328,"line":717},8,[326,719,720],{"class":332},"\u002F* trade-off: transform does not change layout, so siblings do not reflow around\n",[326,722,724],{"class":328,"line":723},9,[326,725,726],{"class":332},"   the moving element; designs that need content to move aside need another approach. *\u002F\n",[15,728,729],{},[70,730,77,733,77,736,77,739,77,741,77,744,77,746,77,755,77,759,77,766,77,772,77,776,77,780,77,784,77,788,77,792,77,796,77,800,77,802,77,807,77,812,77,818,77,822,77,826,77,830,77,834,77,838,77],{"viewBox":731,"width":73,"role":74,"ariaLabel":732,"style":76},"0 0 760 200","Timeline of work in a single frame for an animation of left versus an animation of transform.",[79,734],{"className":735,"x":83,"y":83,"width":73,"height":73,"fill":84},[82],[86,737,738],{},"One frame: layout animation vs compositor animation",[90,740,732],{},[79,742],{"x":94,"y":94,"width":95,"height":743,"rx":97,"fill":98,"stroke":99,"style":100},"198",[102,745,738],{"x":104,"y":105,"fill":99,"style":106},[328,747],{"x1":748,"y1":749,"x2":748,"y2":750,"stroke":154,"strokeWidth":751,"strokeDashArray":752,"style":754},"632.7","72.0","156.0","1.5",[753,753],"4","stroke-opacity:0.9",[102,756,758],{"x":104,"y":757,"fill":99,"style":117},"96.0","Animating left",[79,760],{"x":761,"y":762,"width":763,"height":764,"rx":765,"fill":99,"stroke":99,"style":112},"161.2","78.0","83.3","26.0","3",[102,767,771],{"x":768,"y":769,"fill":99,"style":770,"textAnchor":127},"202.8","95.5","font-size:11.5px","style",[79,773],{"x":774,"y":762,"width":775,"height":764,"rx":765,"fill":98,"stroke":99,"style":146},"246.0","224.7",[102,777,779],{"x":778,"y":769,"fill":99,"style":770,"textAnchor":127},"358.4","layout",[79,781],{"x":782,"y":762,"width":783,"height":764,"rx":765,"fill":98,"stroke":99,"style":146},"472.2","168.2",[102,785,787],{"x":786,"y":769,"fill":99,"style":770,"textAnchor":127},"556.3","paint",[79,789],{"x":790,"y":762,"width":791,"height":764,"rx":765,"fill":99,"stroke":99,"style":112},"641.9","69.2",[102,793,795],{"x":794,"y":769,"fill":99,"style":770,"textAnchor":127},"676.5","comp.",[102,797,799],{"x":104,"y":798,"fill":99,"style":117},"134.0","Animating transform",[79,801],{"x":761,"y":171,"width":791,"height":764,"rx":765,"fill":153,"stroke":154,"style":155},[102,803,806],{"x":804,"y":805,"fill":99,"style":770,"textAnchor":127},"195.8","133.5","composite",[328,808],{"x1":809,"y1":750,"x2":810,"y2":750,"stroke":99,"style":811},"160.4","726.0","stroke-opacity:0.4",[102,813,817],{"x":814,"y":815,"fill":816,"style":770,"textAnchor":127},"171.1","173.0","#51617a","0ms",[102,819,821],{"x":820,"y":815,"fill":816,"style":770,"textAnchor":127},"273.5","4ms",[102,823,825],{"x":824,"y":815,"fill":816,"style":770,"textAnchor":127},"386.7","8ms",[102,827,829],{"x":828,"y":815,"fill":816,"style":770,"textAnchor":127},"499.8","12ms",[102,831,833],{"x":832,"y":815,"fill":816,"style":770,"textAnchor":127},"612.9","16ms",[102,835,837],{"x":836,"y":815,"fill":816,"style":770,"textAnchor":127},"718.3","20ms",[102,839,843],{"x":748,"y":840,"fill":841,"style":842,"textAnchor":127},"68.0","#8a6608","font-size:12px;font-weight:700","60Hz budget",[233,845,847],{"id":846},"deconstructing-the-compositor","Deconstructing the Compositor",[15,849,850,851,854,855,36,857,859],{},"When the browser paints, it records drawing commands for layers. Some elements get their own layer (for example those with 3D transforms, video, canvas, ",[33,852,853],{},"will-change: transform",", or elements animating ",[33,856,35],{},[33,858,39],{},"). The compositor rasterises layers into tiles (bitmaps), often on the GPU, and composites them into frames. For a compositor-only animation, each frame simply reuses existing tiles and applies a different transform or opacity to the layer — no main-thread work, no repainting.",[15,861,862,863,866,867,869,870,872],{},"This only works if the layer's content does not change. If the animation also changes, say, ",[33,864,865],{},"background-color",", the layer must be repainted on the main thread each frame. If it changes ",[33,868,49],{},", layout must run first. And if the element is not already on its own layer when the animation starts, the browser may need to create a layer and paint it, which can cause a hitch on the first frame — this is what ",[33,871,483],{}," is for.",[15,874,875],{},"Layers cost GPU memory roughly proportional to their pixel area (width × height × 4 bytes, times device pixel ratio squared). A full-screen layer on a 3x phone can take tens of megabytes. Hundreds of layers, or a few very large ones, can exhaust GPU memory on low-end devices, leading to slow compositing or checkerboarding.",[233,877,879],{"id":878},"advanced-diagnostics-and-edge-cases","Advanced Diagnostics and Edge Cases",[15,881,882,885],{},[28,883,884],{},"Implicit layer promotion."," Elements that overlap a composited layer may be promoted too (\"layer squashing\" and overlap reasons). The Layers panel shows the reason for each layer.",[15,887,888,891],{},[28,889,890],{},"Animating box-shadow."," Shadows are expensive to paint. Animate the opacity of a pseudo-element that carries the final shadow instead.",[15,893,894,897,898,901,902,905,906,909,910,913],{},[28,895,896],{},"Animating height for accordions."," A layout animation by nature. Alternatives: animate ",[33,899,900],{},"grid-template-rows"," from ",[33,903,904],{},"0fr"," to ",[33,907,908],{},"1fr"," (still layout, but in a contained area), use ",[33,911,912],{},"interpolate-size"," where supported, or use transforms for a reveal effect.",[15,915,916,919],{},[28,917,918],{},"JavaScript animation libraries."," Many animate via inline styles on the main thread. Prefer libraries or modes that use the Web Animations API with transform and opacity, which can run on the compositor.",[15,921,922,925,926,929],{},[28,923,924],{},"Main-thread-dependent compositor animations."," Some animations (for example, those with ",[33,927,928],{},"steps()"," timing or certain filters) may fall back to the main thread. Check the Animations panel for \"not composited\" hints.",[233,931,933],{"id":932},"validation-and-budgeting","Validation and Budgeting",[15,935,936],{},"Validate with the Frames track (no dropped frames during the animation under throttling), Paint flashing (no green flashes on the animating element after the first frame), and the Layers panel (reasonable layer count and memory). In the field, monitor CLS attributed to animated elements and INP during animations.",[938,939,940,953],"table",{},[941,942,943],"thead",{},[944,945,946,950],"tr",{},[947,948,949],"th",{},"Check",[947,951,952],{},"Target",[954,955,956,965,973,981],"tbody",{},[944,957,958,962],{},[959,960,961],"td",{},"Dropped frames during key animations (4x throttle)",[959,963,964],{},"None visible",[944,966,967,970],{},[959,968,969],{},"Paint during animation frames",[959,971,972],{},"None after first frame",[944,974,975,978],{},[959,976,977],{},"Layer count on key pages",[959,979,980],{},"Tens, not hundreds",[944,982,983,986],{},[959,984,985],{},"CLS from animated elements",[959,987,83],{},[233,989,991],{"id":990},"worked-example-a-mobile-navigation-drawer","Worked Example: A Mobile Navigation Drawer",[15,993,994,995,901,997,905,1000,1002,1003,1005,1006,1009,1010,1012,1013,1015],{},"An e-commerce site's mobile navigation drawer animated ",[33,996,59],{},[33,998,999],{},"-300px",[33,1001,83],{}," and dimmed the page with an overlay animating ",[33,1004,865],{},". On a mid-range Android phone, the drawer dropped about 40% of frames, and when product recommendations were rendering in the background, the animation froze for 200ms. The team switched the drawer to ",[33,1007,1008],{},"transform: translateX()"," and the overlay to ",[33,1011,39],{}," on a pre-rendered black layer, adding ",[33,1014,853],{}," only while the drawer was open or opening. Dropped frames fell to near zero even while the main thread was busy, because the compositor ran the animation independently. INP for the menu button improved from 260ms to 120ms, because the click handler no longer started a main-thread animation loop.",[15,1017,1018],{},[70,1019,77,1022,77,1025,77,1028,77,1030,77,1033,77,1035,77,1042,77,1048,77,1053,77,1057,77,1062,77,1066,77,1070,77,1075,77],{"viewBox":1020,"width":73,"role":74,"ariaLabel":1021,"style":76},"0 0 760 163","Bar chart of dropped frames for a navigation drawer animation before and after moving to transform and opacity.",[79,1023],{"className":1024,"x":83,"y":83,"width":73,"height":73,"fill":84},[82],[86,1026,1027],{},"Dropped frames opening a navigation drawer (mid-range phone)",[90,1029,1021],{},[79,1031],{"x":94,"y":94,"width":95,"height":1032,"rx":97,"fill":98,"stroke":99,"style":100},"161",[102,1034,1027],{"x":104,"y":105,"fill":99,"style":106},[102,1036,1041],{"x":1037,"y":1038,"fill":99,"style":1039,"textAnchor":1040},"190.6","70.0","font-size:13px","end","left + background-color",[79,1043],{"x":1044,"y":109,"width":1045,"height":1046,"rx":765,"fill":98,"stroke":99,"style":1047},"202.6","465.4","19","fill-opacity:0.7;stroke-opacity:0.35",[102,1049,1052],{"x":1050,"y":1038,"fill":99,"style":1051},"674.0","font-size:12px;font-weight:600","40%",[102,1054,1056],{"x":1037,"y":1055,"fill":99,"style":1039,"textAnchor":1040},"101.0","left + opacity overlay",[79,1058],{"x":1044,"y":1059,"width":1060,"height":1046,"rx":765,"fill":178,"stroke":178,"style":1061},"87.0","302.5","fill-opacity:0.55;stroke-opacity:0.9",[102,1063,1065],{"x":1064,"y":1055,"fill":99,"style":1051},"511.1","26%",[102,1067,1069],{"x":1037,"y":1068,"fill":99,"style":1039,"textAnchor":1040},"132.0","translateX + opacity overlay",[79,1071],{"x":1044,"y":1072,"width":1073,"height":1046,"rx":765,"fill":153,"stroke":154,"style":1074},"118.0","11.6","fill-opacity:0.7;stroke-opacity:0.9",[102,1076,1078],{"x":1077,"y":1068,"fill":99,"style":1051},"220.2","1%",[233,1080,1082],{"id":1081},"choosing-an-animation-technology","Choosing an Animation Technology",[15,1084,1085,1088,1089,1092,1093,1096,1097,1100,1101,1104],{},[28,1086,1087],{},"CSS transitions and keyframes"," are the default for UI state changes: declarative, compositor-friendly for transform and opacity, and automatically paused when offscreen or in background tabs. ",[28,1090,1091],{},"The Web Animations API"," gives JavaScript control (play, pause, reverse, sequence) with the same performance characteristics as CSS animations. ",[28,1094,1095],{},"requestAnimationFrame loops"," are needed for physics, gestures and canvas drawing; keep them doing as little as possible, writing only transforms. ",[28,1098,1099],{},"Scroll-driven animations"," replace scroll listeners for scroll-linked effects. ",[28,1102,1103],{},"View Transitions"," handle animated changes between page states, including cross-document navigations. Libraries that wrap these (for example those built on WAAPI) inherit their performance; libraries that interpolate values in JavaScript and write styles each frame are main-thread bound.",[233,1106,1108],{"id":1107},"animations-and-accessibility","Animations and Accessibility",[15,1110,1111,1112,1114],{},"Performance and accessibility align on motion. Users who set ",[33,1113,487],{}," should get reduced or no motion; that also removes animation work on devices where users often choose reduced motion for comfort or battery. Avoid animations that cause large layout shifts, flash, or move content the user is reading. Ensure animations do not delay access to content: a page that fades in its main content over 600ms delays LCP perception and can delay LCP itself if opacity starts at zero.",[233,1116,1118],{"id":1117},"animations-and-lcp","Animations and LCP",[15,1120,1121,1122,1125],{},"Entrance animations on the hero are a common, avoidable LCP regression. An element animated from ",[33,1123,1124],{},"opacity: 0"," is not considered painted for LCP until it becomes visible, so a 1-second fade-in on the hero headline or image can add up to a second to LCP. Hero content should be visible immediately; if motion is required, animate a transform from a nearly final position with opacity starting high, or animate secondary decorative elements instead. Slide-in animations that start offscreen have the same problem: the element is not in the viewport at first paint.",[233,1127,1129],{"id":1128},"measuring-smoothness-in-the-lab-and-field","Measuring Smoothness in the Lab and Field",[15,1131,1132],{},"Frame rate alone is a crude measure; what users notice is irregularity. In the lab, the Performance panel's Frames track marks each frame as presented, partially presented or dropped, and the summary shows how many frames missed their deadline during the recording. Record the same animation several times under the same throttling and compare the share of dropped frames. Pay attention to the first frames of an animation, where layer creation and painting cause hitches even for compositor-friendly animations.",[15,1134,1135],{},"In the field, there is no standard smoothness metric in Core Web Vitals, but the Long Animation Frames API reveals frames over 50ms, which are always visible as stutter. Collect them during known animations (for example, after opening a menu) along with their script attribution. A drop in long animation frames during interactions after an animation rewrite confirms the change helped real users, not just the lab profile.",[233,1137,1139],{"id":1138},"animation-libraries-and-frameworks","Animation Libraries and Frameworks",[15,1141,1142],{},"Animation libraries differ widely in how they drive motion. Libraries built on the Web Animations API or CSS (and many modern React and Vue animation libraries in their default modes) hand transform and opacity animations to the browser, which can then composite them. Libraries that compute values in JavaScript every frame and write inline styles — common for spring physics and gesture-driven motion — keep the main thread busy and stutter when it is blocked. Check what your library does with a quick trace: if every animation frame shows scripting and style recalculation for the animated element, it is main-thread driven. Many such libraries offer a hardware-accelerated or WAAPI mode for simple transitions; use it for UI state changes and reserve per-frame JavaScript for interactions that truly need physics.",[15,1144,1145,1146,1148],{},"Framework transitions on mount and unmount (such as enter and leave transitions in Vue, or animated presence components in React) frequently animate ",[33,1147,53],{}," for collapse effects. Replace them with transform and opacity where the design allows, or contain the animated area so that its layout cost stays local.",[233,1150,1152],{"id":1151},"a-rollout-plan","A Rollout Plan",[1154,1155,1156,1159,1162,1168,1171,1177],"ol",{},[263,1157,1158],{},"Inventory animations on key templates with the Animations panel and code search.",[263,1160,1161],{},"Rewrite layout-property animations to transform and opacity.",[263,1163,1164,1165,1167],{},"Remove blanket ",[33,1166,483],{}," rules; add targeted, temporary ones where first-frame hitches appear.",[263,1169,1170],{},"Replace scroll listeners for visual effects with scroll-driven animations.",[263,1172,1173,1174,1176],{},"Add ",[33,1175,487],{}," handling everywhere.",[263,1178,1179],{},"Verify frames under throttling and check CLS and LCP in the field.",[233,1181,1183],{"id":1182},"common-pitfalls","Common Pitfalls",[260,1185,1186,1200,1206,1212,1218],{},[263,1187,1188,77,1191,50,1193,50,1195,50,1197,1199],{},[28,1189,1190],{},"Animating layout properties.",[33,1192,56],{},[33,1194,59],{},[33,1196,49],{},[33,1198,53],{}," and margins run layout every frame.",[263,1201,1202,1205],{},[28,1203,1204],{},"will-change everywhere."," Layer explosion and wasted memory.",[263,1207,1208,1211],{},[28,1209,1210],{},"Fading in the hero."," Delays LCP.",[263,1213,1214,1217],{},[28,1215,1216],{},"JavaScript loops writing styles."," Main-thread bound and vulnerable to long tasks.",[263,1219,1220,1223],{},[28,1221,1222],{},"Ignoring reduced motion."," Accessibility and performance both suffer.",[233,1225,1227],{"id":1226},"faq","FAQ",[1229,1230,1233,1237],"details",{"className":1231},[1232],"faq-item",[1234,1235,1236],"summary",{},"Which CSS properties are cheapest to animate?",[15,1238,1239,43,1241,1243],{},[33,1240,35],{},[33,1242,39],{},". On an element with its own compositing layer, they can be animated by the compositor without layout or paint.",[1229,1245,1247,1250],{"className":1246},[1232],[1234,1248,1249],{},"Does will-change make animations faster?",[15,1251,1252],{},"It promotes an element to its own layer ahead of time, avoiding a hitch when the animation starts. It does not make layout-property animations cheaper, and overuse wastes memory.",[1229,1254,1256,1259],{"className":1255},[1232],[1234,1257,1258],{},"Are CSS animations faster than JavaScript animations?",[15,1260,1261],{},"CSS animations and the Web Animations API can run on the compositor for transform and opacity. JavaScript loops that set styles every frame run on the main thread and are vulnerable to jank.",[1229,1263,1265,1268],{"className":1264},[1232],[1234,1266,1267],{},"Do animations affect CLS?",[15,1269,1270],{},"Animations that move elements by changing layout properties can produce layout shifts. Transform-based movement does not count as a layout shift.",[1229,1272,1274,1277],{"className":1273},[1232],[1234,1275,1276],{},"How do I animate height smoothly?",[15,1278,1279,1280,901,1282,905,1284,1286,1287,1290],{},"Height animation always involves layout. Options include animating ",[33,1281,900],{},[33,1283,904],{},[33,1285,908],{}," with containment, ",[33,1288,1289],{},"interpolate-size: allow-keywords"," where supported, or a transform-based reveal.",[1229,1292,1294,1297],{"className":1293},[1232],[1234,1295,1296],{},"Why does my animation stutter only on the first run?",[15,1298,1299,1300,1302],{},"Often because a layer is created and painted when the animation starts. Promote the element shortly before animating with ",[33,1301,483],{},", then remove it afterwards.",[1229,1304,1306,1309],{"className":1305},[1232],[1234,1307,1308],{},"Do animations run in background tabs?",[15,1310,1311,1312,1315],{},"CSS animations and requestAnimationFrame are paused or throttled in background tabs. Timers with ",[33,1313,1314],{},"setInterval"," may still run, throttled.",[233,1317,1319],{"id":1318},"guides-in-this-topic","Guides in This Topic",[260,1321,1322,1329,1336,1343],{},[263,1323,1324,1328],{},[19,1325,1327],{"href":1326},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002F","Animating only transform and opacity"," — rewrite common animations for the compositor.",[263,1330,1331,1335],{},[19,1332,1334],{"href":1333},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fwill-change-overuse-and-layer-explosion\u002F","will-change overuse and layer explosion"," — managing layers and memory.",[263,1337,1338,1342],{},[19,1339,1341],{"href":1340},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fscroll-driven-animations-without-javascript\u002F","Scroll-driven animations without JavaScript"," — scroll-linked effects off the main thread.",[263,1344,1345,1349],{},[19,1346,1348],{"href":1347},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fview-transitions-api-performance\u002F","View Transitions API performance"," — animated state and page changes.",[233,1351,1353],{"id":1352},"related","Related",[260,1355,1356,1362,1369],{},[263,1357,1358,1361],{},[19,1359,1360],{"href":21},"Rendering & CSS performance"," — the full rendering pipeline.",[263,1363,1364,1368],{},[19,1365,1367],{"href":1366},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Ffixing-cls-from-animating-layout-properties\u002F","Fixing CLS from animating layout properties"," — the CLS side of animation.",[263,1370,1371,1375],{},[19,1372,1374],{"href":1373},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F","Layout thrashing & forced reflow"," — main-thread layout costs.",[1377,1378,1380],"script",{"type":1379},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Animation & Compositing Performance: Smooth Motion Without Main-Thread Work\",\n  \"description\": \"How browsers animate on the compositor, which properties are cheap to animate, how layers work, and how modern CSS animation features affect performance.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Environment Setup: Make Rendering Work Visible\",\n      \"text\": \"Enable Paint flashing to see what repaints during an animation (green flashes on areas being painted), Layer borders to see compositing layers, and Frame Rendering Stats for a live FPS meter.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Capture a Baseline\",\n      \"text\": \"Record the animation in the Performance panel.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Isolate the Animated Properties\",\n      \"text\": \"List the properties each animation changes, from CSS (transition, @keyframes) or JavaScript (style changes in requestAnimationFrame, Web Animations API, animation libraries).\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Apply: Move Motion to transform and opacity\",\n      \"text\": \"Rewrite animations to use transform (translate, scale, rotate) and opacity.\"\n    }\n  ]\n}\n",[1377,1382,1383],{"type":1379},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Animation & Compositing Performance: Smooth Motion Without Main-Thread Work\",\n  \"description\": \"How browsers animate on the compositor, which properties are cheap to animate, how layers work, and how modern CSS animation features affect performance.\",\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\u002F\"\n  }\n}\n",[1377,1385,1386],{"type":1379},"\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}\n",[771,1388,1389],{},"html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}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 .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}",{"title":322,"searchDepth":336,"depth":336,"links":1391},[1392,1393,1394,1395,1396,1397,1398,1399,1400,1401,1402,1403,1404,1405,1406,1407,1408,1409,1410,1411],{"id":235,"depth":336,"text":236},{"id":257,"depth":336,"text":258},{"id":274,"depth":336,"text":275},{"id":293,"depth":336,"text":294},{"id":300,"depth":336,"text":301},{"id":470,"depth":336,"text":471},{"id":846,"depth":336,"text":847},{"id":878,"depth":336,"text":879},{"id":932,"depth":336,"text":933},{"id":990,"depth":336,"text":991},{"id":1081,"depth":336,"text":1082},{"id":1107,"depth":336,"text":1108},{"id":1117,"depth":336,"text":1118},{"id":1128,"depth":336,"text":1129},{"id":1138,"depth":336,"text":1139},{"id":1151,"depth":336,"text":1152},{"id":1182,"depth":336,"text":1183},{"id":1226,"depth":336,"text":1227},{"id":1318,"depth":336,"text":1319},{"id":1352,"depth":336,"text":1353},"How browsers animate on the compositor, which properties are cheap to animate, how layers work, and how modern CSS animation features affect performance.","md",{"slug":1415,"type":1416,"breadcrumb":1417,"datePublished":1424,"dateModified":1424},"animation-and-compositing-performance","topic",[1418,1421,1422],{"name":1419,"url":1420},"Home","\u002F",{"name":22,"url":21},{"name":5,"url":1423},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002F","2026-10-06",true,"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance",{"title":1428,"description":1429},"Animation and Compositing Performance for Smooth UI","Keep animations at 60fps and off the main thread: animate transform and opacity, manage layers and will-change, use scroll-driven animations and View Transitions.","rendering-css-performance\u002Fanimation-and-compositing-performance\u002Findex","CNwo0kOKMEwhmyXrxYPdFMYA2xcnMEbrToMN7ii2Kuo",[1433,1436],{"title":22,"path":1434,"stem":1435,"children":-1},"\u002Frendering-css-performance","rendering-css-performance\u002Findex",{"title":1437,"path":1438,"stem":1439,"children":-1},"Animating Only transform and opacity","\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity","rendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002Findex",1791308072521]