{"id":9584,"date":"2026-05-08T16:26:34","date_gmt":"2026-05-08T21:26:34","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9584"},"modified":"2026-05-08T16:26:36","modified_gmt":"2026-05-08T21:26:36","slug":"how-to-control-infinite-css-animations-part-1-of-2","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/how-to-control-infinite-css-animations-part-1-of-2\/","title":{"rendered":"How to Control Infinite CSS Animations (Part 1 of 2)"},"content":{"rendered":"\n<p>Applying an infinite animation to an element is a simple task. For example, to make an element rotate indefinitely, you add the code below, and it\u2019s done.<\/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\">.box<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: rotate <span class=\"hljs-number\">5s<\/span> infinite linear;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotate {\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/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>Now, what if I ask you to <em>interact<\/em> with that rotation? Accelerate it, slow it down, stop it smoothly, etc. It would be tricky, right? The only thing we can easily do is to pause the animation using:<\/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-tag\">animation-play-state<\/span>: <span class=\"hljs-selector-tag\">paused<\/span>;<\/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>In this series of articles, we will learn much more than just how to pause an infinite animation.<\/p>\n\n\n\n<p>Let\u2019s jump straight into the first example.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"give-me-more-speed\">More Speed!<\/h2>\n\n\n\n<p>Consider the following code:<\/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 shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-selector-class\">.box<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation<\/span>: \n<\/span><\/span><span class='shcb-loc'><span>   rotate <span class=\"hljs-number\">5s<\/span> linear infinite,\n<\/span><\/span><span class='shcb-loc'><span>   rotate <span class=\"hljs-number\">5s<\/span> linear infinite paused;\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><span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n<\/span><\/span><mark class='shcb-loc'><span>  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n<\/span><\/mark><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">@keyframes<\/span> rotate {\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>; }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/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>I am defining the same animation twice, with the second one paused. On hover, both are running, resulting in faster rotation!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ZEZLRXd\" src=\"\/\/codepen.io\/anon\/embed\/ZEZLRXd?height=450&amp;theme-id=1&amp;slug-hash=ZEZLRXd&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ZEZLRXd\" title=\"CodePen Embed ZEZLRXd\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Cool, right? This trick was made possible using&nbsp;<code>animation-composition: add<\/code>. A quick look at&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Reference\/Properties\/animation-composition\">the MDN page<\/a>&nbsp;explains everything:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>The\u00a0<code>animation-composition<\/code> CSS property specifies the composite operation to use when multiple animations affect the same property simultaneously.<\/p>\n<\/blockquote>\n\n\n\n<p>Then<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><code>add<\/code><\/p>\n\n\n\n<p>The effect value builds on the underlying value of the property. This operation produces an additive effect<\/p>\n<\/blockquote>\n\n\n\n<p>We are applying the same animation twice, so they add up. Initially, only one rotation runs, and on hover, the second starts, making the overall rotation faster.<\/p>\n\n\n\n<p>The result is similar to dividing the duration by 2.<\/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-tag\">animation<\/span>: <span class=\"hljs-selector-tag\">rotate<\/span> 2<span class=\"hljs-selector-class\">.5s<\/span> <span class=\"hljs-selector-tag\">linear<\/span> <span class=\"hljs-selector-tag\">infinite<\/span>;<\/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>Or, multiplying the rotation by 2.<\/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\"><span class=\"hljs-keyword\">@keyframes<\/span> rotate {\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">2turn<\/span>; }\n}<\/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>In the demo below, if you hover the first box, it will have the same speed as the second box (where I am using a duration equal to&nbsp;<code>2.5s<\/code>) and the third box (where I am using&nbsp;<code>rotate: 2turn<\/code>).<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_XJNJaBz\/ce301a5550a31d4aa61e49370a044828\" src=\"\/\/codepen.io\/anon\/embed\/XJNJaBz\/ce301a5550a31d4aa61e49370a044828?height=450&amp;theme-id=1&amp;slug-hash=XJNJaBz\/ce301a5550a31d4aa61e49370a044828&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed XJNJaBz\/ce301a5550a31d4aa61e49370a044828\" title=\"CodePen Embed XJNJaBz\/ce301a5550a31d4aa61e49370a044828\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Now the question is: Why not simply adjust the duration on hover?<\/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\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: rotate <span class=\"hljs-number\">5s<\/span> linear infinite;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-duration<\/span>: <span class=\"hljs-number\">2.5s<\/span>;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotate {\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>; }\n}<\/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>Simple, and no need to duplicate the animation or use&nbsp;<code>animation-composition<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_WbobEaw\/d8052fd6a62b56d2c5ec41b63f0d46c9\" src=\"\/\/codepen.io\/anon\/embed\/WbobEaw\/d8052fd6a62b56d2c5ec41b63f0d46c9?height=450&amp;theme-id=1&amp;slug-hash=WbobEaw\/d8052fd6a62b56d2c5ec41b63f0d46c9&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed WbobEaw\/d8052fd6a62b56d2c5ec41b63f0d46c9\" title=\"CodePen Embed WbobEaw\/d8052fd6a62b56d2c5ec41b63f0d46c9\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Well, it doesn\u2019t work. The rotation is indeed faster on hover, but the element \u201cjumps\u201d to a random position before it accelerates. You will get a similar behavior if you keep the same duration and update the keyframes.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_xbRGqZq\/77241b662d2a29186c3844a7e1c90fde\" src=\"\/\/codepen.io\/anon\/embed\/xbRGqZq\/77241b662d2a29186c3844a7e1c90fde?height=450&amp;theme-id=1&amp;slug-hash=xbRGqZq\/77241b662d2a29186c3844a7e1c90fde&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed xbRGqZq\/77241b662d2a29186c3844a7e1c90fde\" title=\"CodePen Embed xbRGqZq\/77241b662d2a29186c3844a7e1c90fde\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>No need to waste time explaining why it behaves that way, but it\u2019s by design. Keep in mind that messing with the animation values when it\u2019s running is never a good idea. For this reason, we are here to learn a few tricks to avoid those strange behaviors.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"controlling-the-speed\">Controlling the Speed<\/h2>\n\n\n\n<p>Now, let\u2019s update the code and make it more flexible:<\/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\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">2<\/span>;  <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">--_a<\/span>: rotation linear infinite;\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">var<\/span>(--d),\n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">calc<\/span>(var(--d)\/(<span class=\"hljs-built_in\">var<\/span>(--s) - <span class=\"hljs-number\">1<\/span>)) paused;\n  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotation {\n  <span class=\"hljs-selector-tag\">to<\/span> {<span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>}\n}<\/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>We make the duration and the speed factor CSS variables to easily control the animation. A speed factor of&nbsp;<code>1<\/code>&nbsp;means there is no acceleration (the duration of the second animation will be equal to 0). A higher value will cause acceleration; the higher the value, the faster the element will be.<\/p>\n\n\n\n<p>The speed factor is given by the following formula. It\u2019s a multiplier for the initial speed.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">speed_on_hover = var(--s) * initial_speed<\/pre>\n\n\n\n<p>By simply changing one variable, you get a different speed on hover without touching the CSS code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"box\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--s: 1\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- no acceleration --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"box\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--s: 2\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- 2 times faster --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"box\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--s: 8\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- 8 times faster !! --&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_raWazgJ\/0c69147c6ac17fe4f532fa877c182704\" src=\"\/\/codepen.io\/anon\/embed\/raWazgJ\/0c69147c6ac17fe4f532fa877c182704?height=450&amp;theme-id=1&amp;slug-hash=raWazgJ\/0c69147c6ac17fe4f532fa877c182704&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed raWazgJ\/0c69147c6ac17fe4f532fa877c182704\" title=\"CodePen Embed raWazgJ\/0c69147c6ac17fe4f532fa877c182704\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"slow-down\">Slow Down!<\/h2>\n\n\n\n<p>To decelerate, we use the same technique, but the second rotation needs to play in reverse. Running two rotations in the same direction will always make the overall rotation faster, but running a second rotation in the opposite direction will make it slower!<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: .<span class=\"hljs-number\">5<\/span>; <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">--_a<\/span>: rotation linear infinite;\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">var<\/span>(--d),\n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">calc<\/span>(var(--d)\/(<span class=\"hljs-number\">1<\/span> - <span class=\"hljs-built_in\">var<\/span>(--s))) paused reverse;\n  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotation {\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>; }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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>I added&nbsp;<code>reverse<\/code>&nbsp;to the second animation, and the formula is the negation of the previous one. Following the same logic, using 1 as a speed factor will do nothing, and a smaller speed factor will reduce the speed, so we are still following the same formula:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">speed_on_hover = var(--s) * initial_speed<\/pre>\n\n\n\n<p>This also means we can stop the animation by setting the speed factor to 0.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"box\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--s: 1\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- nothing will happen --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"box\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--s: .5\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- 2 times slower --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"box\"<\/span> <span class=\"hljs-attr\">style<\/span>=<span class=\"hljs-string\">\"--s: 0\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span> <span class=\"hljs-comment\">&lt;!-- stop the animation --&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_VYmYMeJ\/be79f554e1e01437686162f4de3da5f6\" src=\"\/\/codepen.io\/anon\/embed\/VYmYMeJ\/be79f554e1e01437686162f4de3da5f6?height=450&amp;theme-id=1&amp;slug-hash=VYmYMeJ\/be79f554e1e01437686162f4de3da5f6&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed VYmYMeJ\/be79f554e1e01437686162f4de3da5f6\" title=\"CodePen Embed VYmYMeJ\/be79f554e1e01437686162f4de3da5f6\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The first code uses a speed factor in the range&nbsp;<code>[1 +infinity[<\/code>&nbsp;to accelerate, while the second code uses a speed factor in the range&nbsp;<code>[0 1]<\/code>&nbsp;to decelerate.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-about-negative-values\">What about negative values?<\/h2>\n\n\n\n<p>When adding two rotations that move in opposite directions, the final direction will be the one of the faster rotation. If we use values in the range&nbsp;<code>[0 1]<\/code>, the first rotation is always faster than the second, so we get a deceleration while keeping the same direction.<\/p>\n\n\n\n<p>If we use negative values, the second rotation is faster than the first one, and the direction changes.<strong>\u00a0<\/strong>This also explains why 0 stops the elements; both rotations will have the same duration (same speed) and will negate each other (no more movement).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">&lt;<span class=\"hljs-selector-tag\">div<\/span> <span class=\"hljs-selector-tag\">class<\/span>=\"<span class=\"hljs-selector-tag\">box<\/span>\" <span class=\"hljs-selector-tag\">style<\/span>=\"<span class=\"hljs-selector-tag\">--s<\/span>: <span class=\"hljs-selector-tag\">-<\/span><span class=\"hljs-selector-class\">.5<\/span>\"&gt;&lt;\/<span class=\"hljs-selector-tag\">div<\/span>&gt;\n&lt;<span class=\"hljs-selector-tag\">div<\/span> <span class=\"hljs-selector-tag\">class<\/span>=\"<span class=\"hljs-selector-tag\">box<\/span>\" <span class=\"hljs-selector-tag\">style<\/span>=\"<span class=\"hljs-selector-tag\">--s<\/span>: <span class=\"hljs-selector-tag\">-1<\/span>\"&gt;&lt;\/<span class=\"hljs-selector-tag\">div<\/span>&gt;\n&lt;<span class=\"hljs-selector-tag\">div<\/span> <span class=\"hljs-selector-tag\">class<\/span>=\"<span class=\"hljs-selector-tag\">box<\/span>\" <span class=\"hljs-selector-tag\">style<\/span>=\"<span class=\"hljs-selector-tag\">--s<\/span>: <span class=\"hljs-selector-tag\">-4<\/span>\"&gt;&lt;\/<span class=\"hljs-selector-tag\">div<\/span>&gt;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_KwNwXQQ\/ef28423d60b9a27743162131d7df1350\" src=\"\/\/codepen.io\/anon\/embed\/KwNwXQQ\/ef28423d60b9a27743162131d7df1350?height=450&amp;theme-id=1&amp;slug-hash=KwNwXQQ\/ef28423d60b9a27743162131d7df1350&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed KwNwXQQ\/ef28423d60b9a27743162131d7df1350\" title=\"CodePen Embed KwNwXQQ\/ef28423d60b9a27743162131d7df1350\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Not only are the three boxes moving in opposite directions, but they also have different speeds. The speed factor still follows the same formula.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">speed_on_hover = var(--s) * initial_speed<\/pre>\n\n\n\n<p>Using&nbsp;<code>-1<\/code>&nbsp;means the same initial speed in reverse.&nbsp;<code>-4<\/code>&nbsp;means four times the initial speed in reverse, and so on. The absolute value controls the speed, and the sign controls the direction!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-generic-code\">The Generic Code<\/h2>\n\n\n\n<p>Let\u2019s sum up what we have until now. Both codes have everything in common except the duration of the second animation and the&nbsp;<code>reverse<\/code>&nbsp;keyword.<\/p>\n\n\n\n<p>For the first code, we have the following duration:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>)\/(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--s<\/span>) <span class=\"hljs-selector-tag\">-<\/span> 1))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>Where&nbsp;<code>--s<\/code>&nbsp;is a value in the range&nbsp;<code>[1 +infinity[<\/code>.<\/p>\n\n\n\n<p>And for the second code, we have<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>)\/(1 <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--s<\/span>)))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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>Where&nbsp;<code>--s<\/code>&nbsp;is a value in the range&nbsp;<code>]-infinity 1]<\/code>.<\/p>\n\n\n\n<p>It would be good if we could merge the two codes into a single generic one where the&nbsp;<code>--s<\/code>&nbsp;variable can take any value.<\/p>\n\n\n\n<p>The second duration can also be written as follows:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">-1<\/span>*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>)\/(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--s<\/span>) <span class=\"hljs-selector-tag\">-<\/span> 1))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><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>That negative sign is needed because the value&nbsp;<code>var(--s) - 1<\/code>&nbsp;is negative, and the duration needs to be a positive value. But instead of using that negative sign, we can rely on the absolute value using&nbsp;<code>abs()<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">abs<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--d<\/span>)\/(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--s<\/span>) <span class=\"hljs-selector-tag\">-<\/span> 1))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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 the value of&nbsp;<code>--s<\/code>&nbsp;is bigger than one, we get a positive value and&nbsp;<code>abs()<\/code>&nbsp;will do nothing (that\u2019s the first code), and if&nbsp;<code>--s<\/code>&nbsp;is smaller than one, we get a negative value and&nbsp;<code>abs()<\/code>&nbsp;will transform it into a positive value (that\u2019s the second code).<\/p>\n\n\n\n<p>We have our generic code that can handle all the values of&nbsp;<code>--s<\/code>&nbsp;(acceleration, deceleration, stop, moving in the opposite direction)<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">2<\/span>; <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">--_a<\/span>: rotation linear infinite;\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">var<\/span>(--d),\n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">abs<\/span>(var(--d)\/(<span class=\"hljs-built_in\">var<\/span>(--s) - <span class=\"hljs-number\">1<\/span>)) paused;\n  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotation {\n  <span class=\"hljs-selector-tag\">to<\/span> {<span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>}\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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 class=\"learn-more\">Wait, you forgot about the\u00a0<code>reverse<\/code>\u00a0keywords!<\/p>\n\n\n\n<p>I won\u2019t rely on it! Instead of using&nbsp;<code>reverse<\/code>, I will \u201creverse\u201d the angle of rotation inside the animation. For this, I will use the&nbsp;<code>sign()<\/code>&nbsp;function like below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@keyframes<\/span> rotation {\n  <span class=\"hljs-selector-tag\">to<\/span> {<span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-built_in\">calc<\/span>(sign(var(--s) - <span class=\"hljs-number\">1<\/span>)*<span class=\"hljs-number\">1turn<\/span>)}\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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>When&nbsp;<code>--s<\/code>&nbsp;is bigger than 1, the sign is positive (same direction), and when&nbsp;<code>--s<\/code>&nbsp;is smaller than 1, the sign is negative (reverse direction). The only drawback is that I need to define a new animation since the initial one must remain the same.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">2<\/span>;  <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">animation<\/span>: \n    init    linear infinite <span class=\"hljs-built_in\">var<\/span>(--d),\n    control linear infinite <span class=\"hljs-built_in\">abs<\/span>(var(--d)\/(<span class=\"hljs-built_in\">var<\/span>(--s) - <span class=\"hljs-number\">1<\/span>)) paused;\n  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> init {\n  <span class=\"hljs-selector-tag\">to<\/span> {<span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>}\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> control {\n  <span class=\"hljs-selector-tag\">to<\/span> {<span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-built_in\">calc<\/span>(sign(var(--s) - <span class=\"hljs-number\">1<\/span>)*<span class=\"hljs-number\">1turn<\/span>)}\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><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>We are done! A few lines of code and we can easily control the speed and direction of an infinite rotation using one variable.<\/p>\n\n\n\n<p class=\"learn-more\">But you told us not to mess with the animation when it\u2019s running. Why are you updating the keyframes?<\/p>\n\n\n\n<p>I am not \u201cupdating\u201d the keyframes. I am \u201csetting\u201d them. The&nbsp;<code>--s<\/code>&nbsp;variable is fixed per element, so all the animation values are fixed and only the state changes on hover. We get a different animation for each element (based on the value of&nbsp;<code>--s<\/code>), but it\u2019s not changing for the concerned element.<\/p>\n\n\n\n<p>Here is the final code with different elements having different speed factors:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_pvNvdVX\/d3e859117bd129f0200b171c8326f7d3\" src=\"\/\/codepen.io\/anon\/embed\/pvNvdVX\/d3e859117bd129f0200b171c8326f7d3?height=500&amp;theme-id=1&amp;slug-hash=pvNvdVX\/d3e859117bd129f0200b171c8326f7d3&amp;default-tab=result\" height=\"500\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed pvNvdVX\/d3e859117bd129f0200b171c8326f7d3\" title=\"CodePen Embed pvNvdVX\/d3e859117bd129f0200b171c8326f7d3\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>In the near future, we can keep working with one animation and reintroduce the&nbsp;<code>reverse<\/code>&nbsp;keyword using an&nbsp;<code>if()<\/code>&nbsp;condition.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">2<\/span>; <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">--_a<\/span>: rotation linear infinite;\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">var<\/span>(--d),\n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">abs<\/span>(var(--d)\/(<span class=\"hljs-built_in\">var<\/span>(--s) - <span class=\"hljs-number\">1<\/span>)) paused\n    <span class=\"hljs-built_in\">if<\/span>(style(--s &lt; <span class=\"hljs-number\">1<\/span>):reverse;<span class=\"hljs-attribute\">else<\/span>: ;);\n  <span class=\"hljs-attribute\">animation-composition<\/span>: add;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotation {\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>; }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><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>You can test it using the latest version of Chrome:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_OPbPOwo\/55b0c3134460046f861243f4d63560cc\" src=\"\/\/codepen.io\/anon\/embed\/OPbPOwo\/55b0c3134460046f861243f4d63560cc?height=500&amp;theme-id=1&amp;slug-hash=OPbPOwo\/55b0c3134460046f861243f4d63560cc&amp;default-tab=result\" height=\"500\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed OPbPOwo\/55b0c3134460046f861243f4d63560cc\" title=\"CodePen Embed OPbPOwo\/55b0c3134460046f861243f4d63560cc\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>If you are new to&nbsp;<code>if()<\/code>, check this small article to understand how to correctly use it: \u201c<a href=\"https:\/\/css-tip.com\/if-trick\/\">The Hidden Trick of Style Queries and if()<\/a>\u201d<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"more-examples\">More Examples<\/h2>\n\n\n\n<p>We studied the entire logic with rotation in mind, but the trick works with any kind of infinite animation!<\/p>\n\n\n\n<p>Here is an example from a previous article: \u201c<a href=\"https:\/\/frontendmasters.com\/blog\/infinite-marquee-animation-using-modern-css\/\">Infinite Marquee Animation using Modern CSS<\/a>\u201d where I am animating&nbsp;<code>offset-distance<\/code>. Hover to adjust the speed\/direction of the animation:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_QwGjxPv\" src=\"\/\/codepen.io\/anon\/embed\/QwGjxPv?height=550&amp;theme-id=1&amp;slug-hash=QwGjxPv&amp;default-tab=result\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed QwGjxPv\" title=\"CodePen Embed QwGjxPv\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Another example with a glowing border animation. For this one, I am animating a CSS variable, which is interesting because variables can be used almost everywhere, which means you can apply this technique to any CSS value.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_RNoNJLW\" src=\"\/\/codepen.io\/anon\/embed\/RNoNJLW?height=450&amp;theme-id=1&amp;slug-hash=RNoNJLW&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed RNoNJLW\" title=\"CodePen Embed RNoNJLW\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>We can even consider two interactions like the animated gallery below. A faster animation if you click the right arrow and a faster animation with a direction change if you click the left arrow.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_wBoKLOy\/643d1c704de6e6eb275bbcacbe445e37\" src=\"\/\/codepen.io\/anon\/embed\/wBoKLOy\/643d1c704de6e6eb275bbcacbe445e37?height=400&amp;theme-id=1&amp;slug-hash=wBoKLOy\/643d1c704de6e6eb275bbcacbe445e37&amp;default-tab=result\" height=\"400\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed wBoKLOy\/643d1c704de6e6eb275bbcacbe445e37\" title=\"CodePen Embed wBoKLOy\/643d1c704de6e6eb275bbcacbe445e37\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This time, I am considering three animations. Initially, only one is running, and each button click will run a different animation.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.container<\/span><span class=\"hljs-selector-pseudo\">:has(+<\/span> <span class=\"hljs-selector-tag\">button<\/span><span class=\"hljs-selector-pseudo\">:active)<\/span> <span class=\"hljs-selector-tag\">img<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running, paused, running; <span class=\"hljs-comment\">\/* 1st and 3rd *\/<\/span>\n}\n<span class=\"hljs-selector-class\">.container<\/span><span class=\"hljs-selector-pseudo\">:has(+<\/span> * + <span class=\"hljs-selector-tag\">button<\/span><span class=\"hljs-selector-pseudo\">:active)<\/span> <span class=\"hljs-selector-tag\">img<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running, running, paused; <span class=\"hljs-comment\">\/* 1st and 2nd *\/<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><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<h2 class=\"wp-block-heading\" id=\"the-math-behind-the-formula\">The Math Behind The Formula<\/h2>\n\n\n\n<p>Now that we have our working code, let\u2019s dissect the logic behind the formula I used to define the duration. I should have started with this, but I decided it wasn\u2019t useful for understanding the overall idea, so I made this section last. Feel free to skip it if you are not interested in the math.<\/p>\n\n\n\n<p>I wanted to have a speed factor that allows me to have the following relation between the initial speed and the speed on hover:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">speed_on_hover = var(--s) * initial_speed<\/pre>\n\n\n\n<p>This makes the control intuitive as the number makes sense from a user perspective.<\/p>\n\n\n\n<p>The&nbsp;<code>initial_speed<\/code>&nbsp;is the speed of the first animation (since the second one is paused), and the&nbsp;<code>speed_on_hover<\/code>&nbsp;is the sum of the speed of both animations (since we have an add composition). It means we have the following formula as well:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">speed_on_hover = initial_speed + speed_of_second_animation<\/pre>\n\n\n\n<p>Merging both formulas, we get the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">var(--s) * initial_speed = initial_speed + speed_of_second_animation\ninitial_speed * (var(--s) - 1) = speed_of_second_animation\nspeed_of_first_animation * (var(--s) - 1) = speed_of_second_animation<\/pre>\n\n\n\n<p>A speed can be expressed as a&nbsp;<code>distance\/duration<\/code>, and in our case, we have two animations that animate the same way but with different durations, which leads us to:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">(var(--s) - 1)\/duration_of_first_animation = 1\/duration_of_second_animation\nduration_of_second_animation = duration_of_first_animation\/(var(--s) - 1)<\/pre>\n\n\n\n<p>That translates to the CSS we used:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">2<\/span>; <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">--_a<\/span>: rotation linear infinite;\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-built_in\">var<\/span>(--_a)      <span class=\"hljs-built_in\">var<\/span>(--d),\n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">calc<\/span>(var(--d)\/(<span class=\"hljs-built_in\">var<\/span>(--s) - <span class=\"hljs-number\">1<\/span>)) paused\n  animation-composition: add;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><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>And when using the&nbsp;<code>reverse<\/code>&nbsp;keyword, the second animation will run in the opposite direction, so we consider the same math with a&nbsp;<code>-1<\/code>&nbsp;in the formula.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--d<\/span>: <span class=\"hljs-number\">5s<\/span>; <span class=\"hljs-comment\">\/* animation duration *\/<\/span>\n  <span class=\"hljs-attribute\">--s<\/span>: <span class=\"hljs-number\">2<\/span>; <span class=\"hljs-comment\">\/* speed factor *\/<\/span>\n  \n  <span class=\"hljs-attribute\">--_a<\/span>: rotation linear infinite;\n  <span class=\"hljs-attribute\">animation<\/span>: \n    <span class=\"hljs-built_in\">var<\/span>(--_a)      <span class=\"hljs-built_in\">var<\/span>(--d),\n    <span class=\"hljs-built_in\">var<\/span>(--_a) <span class=\"hljs-built_in\">calc<\/span>(var(--d)\/(<span class=\"hljs-number\">1<\/span> - <span class=\"hljs-built_in\">var<\/span>(--s))) reverse paused\n  animation-composition: add;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><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<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h2>\n\n\n\n<p>That\u2019s all for this first part. Thanks to the mechanism behind <code>animation-composition<\/code>, we were able to create a code that lets us easily control the speed and direction of any infinite animation. Can you think about a good use case for this technique? If so, share your demo in the comments section.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Something like manipulating the speed of an animation isn&#8217;t a big deal, but it&#8217;s harder when the animation is *already running*. We got tricks. <\/p>\n","protected":false},"author":12,"featured_media":9586,"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],"class_list":["post-9584","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-animation","tag-animation-composition","tag-css"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/05\/infinite-spinner.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9584","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\/12"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=9584"}],"version-history":[{"count":10,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9584\/revisions"}],"predecessor-version":[{"id":9603,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9584\/revisions\/9603"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/9586"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=9584"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9584"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9584"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}