{"id":6457,"date":"2025-07-15T18:38:20","date_gmt":"2025-07-15T23:38:20","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=6457"},"modified":"2025-07-15T18:38:21","modified_gmt":"2025-07-15T23:38:21","slug":"stacked-transforms","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/stacked-transforms\/","title":{"rendered":"Stacked Transforms"},"content":{"rendered":"\n<p>I think the best way for me to show you what I want to show you is to make this blog post a bit like a story. So I&#8217;m gonna do that.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>So I&#8217;m at <a href=\"https:\/\/cssday.nl\/\">CSS Day<\/a> in Amsterdam this past month, and there was a lovely side event called <a href=\"https:\/\/www.css.cafe\/\">CSS Caf\u00e9<\/a>. <span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">I&#8217;m 90% sure it was during a talk by Johannes Odland and a coworker of his at&nbsp;<a href=\"https:\/\/www.nrk.no\/\" target=\"_blank\">NRK<\/a>&nbsp;(whose name I embarrassingly cannot remember) where they showed off something like an illustration of a buoy floating in the water with waves in front of it. Somehow, someway, the CSS property animation-composition was involved, and I was like <em>what the heck is that?<\/em> I took notes during the presentation, and my notes simply said &#8220;animation-composition&#8221;, which wasn&#8217;t<\/span> exactly helpful.<\/p>\n\n\n\n<p>I nearly forgot about it when I read Josh Comeau&#8217;s blog post <a href=\"https:\/\/www.joshwcomeau.com\/animation\/partial-keyframes\/\">Partial Keyframes<\/a>, where he talks about &#8220;dynamic, composable CSS keyframes&#8221;, which, as I recall was similar to what Johannes was talking about. There is some interesting stuff in Josh&#8217;s post \u2014 I liked the stuff about comma-separating multiple animations \u2014 but alas, nothing about <code>animation-composition<\/code>. <\/p>\n\n\n\n<p>So I figured I&#8217;d <a href=\"https:\/\/www.twitch.tv\/chriscoyier\">stream<\/a> about it, and so <a href=\"https:\/\/www.youtube.com\/watch?v=hL2FnCnVcb4\">I did that<\/a>, where I literally read the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/animation-composition\"><code>animation-composition<\/code> docs on MDN<\/a> and played with things. I found their basic\/weird demo intriguing and learned from that. Say you&#8217;ve got a <em>thing<\/em> and it&#8217;s got some transfoms already on it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.thing<\/span> {\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then you put a <code>@keyframes<\/code> animation on it also:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.thing<\/span> {\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>);\n  <span class=\"hljs-attribute\">animation<\/span>: doAnimation <span class=\"hljs-number\">5s<\/span> infinite alternate;\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> doAnimation {\n  <span class=\"hljs-selector-tag\">from<\/span> {\n    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">0<\/span>)\n  }\n  <span class=\"hljs-selector-tag\">to<\/span> {\n    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">100px<\/span>)\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Pop quiz: what is the <code>translateX()<\/code> value going to be at the beginning of that animation?<\/p>\n\n\n\n<p>It&#8217;s not a trick question. If you intuition tells you that it&#8217;s going to be <code>translateX(0)<\/code>, you&#8217;re right. The &#8220;new&#8221; <code>transform<\/code> in the <code>@keyframes<\/code> is going to &#8220;wipe out&#8221; any existing <code>transform<\/code> on that element and replace it with what is described in the <code>@keyframes<\/code> animation.<\/p>\n\n\n\n<p>That&#8217;s because the default behavior is <code>animation-composition: replace;<\/code>. It&#8217;s a perfectly fine default and likely what you&#8217;re used to doing. <\/p>\n\n\n\n<p>But there are other possible values for <code>animation-composition<\/code> that behave differently, and we&#8217;ll look at those in a second. But first, the fact that <code>transform<\/code> can take a &#8220;space-separated&#8221; list of values is already kind of interesting. When you do <code>transform: translateX(50px) rotate(20deg);<\/code>, <em>both<\/em> of those values are going to apply. That&#8217;s also relatively intuitive once you know it&#8217;s possible.<\/p>\n\n\n\n<p>What is less intuitive but very interesting is that you can keep going with more space-separated values, even <em>repeating<\/em> ones that are already there. And there I definitely learned something! Say we tack on another <code>translateX()<\/code> value onto it:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.thing<\/span> {\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>) <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>My brain goes: <em>oh, it&#8217;s probably basically the same as<\/em> <code>translateX(100px) rotate(20deg);<\/code>. But that&#8217;s not true. The transforms apply <em>one at a time, and in order. <\/em>So what actually happens is:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"900\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-10.54.15%E2%80%AFAM.png?resize=1024%2C900&#038;ssl=1\" alt=\"Illustration depicting three rectangles with arrows indicating movement and rotation, labeled with numbers 1, 2, and 3, on a dotted background.\" class=\"wp-image-6543\" style=\"width:479px;height:auto\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-10.54.15%E2%80%AFAM.png?resize=1024%2C900&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-10.54.15%E2%80%AFAM.png?resize=300%2C264&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-10.54.15%E2%80%AFAM.png?resize=768%2C675&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-10.54.15%E2%80%AFAM.png?w=1154&amp;ssl=1 1154w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n<\/div>\n\n\n<p>I&#8217;m starting to get this in my head, so I streamed <a href=\"https:\/\/www.youtube.com\/watch?v=5HCMskfZf4U\">again the next day<\/a> and put it to work.<\/p>\n\n\n\n<p>What popped into my head was a computer language called <a href=\"https:\/\/en.wikipedia.org\/wiki\/Logo_(programming_language)\">Logo<\/a> that I played with as a kid in elementary school. Just look at the main image from <a href=\"https:\/\/en.wikipedia.org\/wiki\/Logo_(programming_language)\">the Wikipedia page<\/a>. And the homepage of <a href=\"https:\/\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/lego-tc-logo-teaching-the-turtle_text.pdf\">the manual<\/a> is very nostoligic for me. <\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\"><div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"400\" height=\"300\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/KochTurtleAnim.gif?resize=400%2C300&#038;ssl=1\" alt=\"\" class=\"wp-image-6544\"\/><\/figure>\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"727\" height=\"1024\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-12.44.50%E2%80%AFPM.png?resize=727%2C1024&#038;ssl=1\" alt=\"Cover of the LEGO TC logo book titled 'Teaching the Turtle,' featuring a blue and red LEGO robotic structure on a baseplate with a computer in the background.\" class=\"wp-image-6545\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-12.44.50%E2%80%AFPM.png?resize=727%2C1024&amp;ssl=1 727w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-12.44.50%E2%80%AFPM.png?resize=213%2C300&amp;ssl=1 213w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-12.44.50%E2%80%AFPM.png?resize=768%2C1082&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-12.44.50%E2%80%AFPM.png?resize=1090%2C1536&amp;ssl=1 1090w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Screenshot-2025-07-15-at-12.44.50%E2%80%AFPM.png?w=1292&amp;ssl=1 1292w\" sizes=\"auto, (max-width: 727px) 100vw, 727px\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p>We can totally make a &#8220;turtle&#8221; move like that.<\/p>\n\n\n\n<p>All I did here is put a couple of buttons on the page that append more <code>transform<\/code> values to this turtle element. And sure enough, it moves around just like the turtle of my childhood. <\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_RNPmRWP\" src=\"\/\/codepen.io\/editor\/anon\/embed\/RNPmRWP?height=450&amp;theme-id=1&amp;slug-hash=RNPmRWP&amp;default-tab=js,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed RNPmRWP\" title=\"CodePen Embed RNPmRWP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>But Mr. Turtle there doesn&#8217;t really have anything to do with <code>animation-composition<\/code>, which was the origin of this whole story. But it&#8217;s sets up understanding what <em>happens<\/em> with <code>animation-composition<\/code>. Remember this setup?<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.thing<\/span> {\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>);\n  <span class=\"hljs-attribute\">animation<\/span>: doAnimation <span class=\"hljs-number\">5s<\/span> infinite alternate;\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> doAnimation {\n  <span class=\"hljs-selector-tag\">from<\/span> {\n    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">0<\/span>)\n  }\n  <span class=\"hljs-selector-tag\">to<\/span> {\n    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">100px<\/span>)\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The big question is: <strong>what happens to the <code>transform<\/code> that is already on the element when the <code>@keyframes<\/code> run?<\/strong><\/p>\n\n\n\n<p>If we add <code>animation-composition: add;<\/code> it adds what is going on in the <code>@keyframes<\/code> to what is already there, by appending to the end of the list, as it were.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.thing<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation<\/span>: doAnimation <span class=\"hljs-number\">5s<\/span> infinite alternate;\n<\/span><\/span><mark class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n<\/span><\/mark><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@keyframes<\/span> doAnimation {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">from<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">0<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/* starts as if: <\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">       transform: translateX(50px) rotate(20deg) translateX(0); *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">to<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">100px<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/* ends as if:<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">      transform: translateX(50px) rotate(20deg) translateX(100px); *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>If we did <code>animation-composition: accumulate;<\/code> it&#8217;s slightly different behavior. Rather than appending to the list of space-separated values, it increments the values if it finds a match. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.thing<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation<\/span>: doAnimation <span class=\"hljs-number\">5s<\/span> infinite alternate;\n<\/span><\/span><mark class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation-composition<\/span>: accumulate;\n<\/span><\/mark><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@keyframes<\/span> doAnimation {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">from<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">0<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/* starts as if: <\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">       transform: translateX(50px) rotate(20deg); *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">to<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">100px<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-comment\">\/* ends as if:<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">      transform: translateX(150px) rotate(20deg) *\/<\/span>\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It&#8217;s not <em>just<\/em> <code>transform<\/code> that behave this way, I just found it a useful way to grok it. (Which is also why I had space-separated <code>filter<\/code> <a href=\"https:\/\/frontendmasters.com\/blog\/blur1px-blur1px\/\">on the mind<\/a>.) For instance, if a <code>@keyframes<\/code> was adjusting opacity and we used <code>add<\/code> or <code>accumulate<\/code>, it would only ever <em>increase<\/em> an opacity value. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.thing<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">opacity<\/span>: .<span class=\"hljs-number\">5<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  \n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">translateX<\/span>(<span class=\"hljs-number\">50px<\/span>) <span class=\"hljs-built_in\">rotate<\/span>(<span class=\"hljs-number\">20deg<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation<\/span>: doAnimation <span class=\"hljs-number\">2s<\/span> infinite alternate;\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@keyframes<\/span> doAnimation {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">from<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n<\/span><\/span><mark class='shcb-loc'><span>    <span class=\"hljs-comment\">\/* thing would never actually be 0 opacity, it would start at 0.5 and go up *\/<\/span>\n<\/span><\/mark><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">to<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">1<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So that&#8217;s that! Understanding how &#8220;stacked&#8221; transforms works is very interesting to me and I have a feeling will come in useful someday. And I feel the same way about <code>animation-composition<\/code>. You won&#8217;t need it until you need it. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>A look at what happens when you add a whole list of transforms to an element, and how that interacts with `animation-composition`. <\/p>\n","protected":false},"author":1,"featured_media":6558,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[100,369,7,370],"class_list":["post-6457","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-animation","tag-animation-composition","tag-css","tag-transform"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2025\/07\/Stacked-Transforms.jpg?fit=1140%2C676&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6457","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=6457"}],"version-history":[{"count":11,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6457\/revisions"}],"predecessor-version":[{"id":6550,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/6457\/revisions\/6550"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/6558"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=6457"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=6457"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=6457"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}