{"id":9604,"date":"2026-05-15T08:23:47","date_gmt":"2026-05-15T13:23:47","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9604"},"modified":"2026-05-15T08:23:48","modified_gmt":"2026-05-15T13:23:48","slug":"how-to-control-infinite-css-animations-part-2-of-2","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/how-to-control-infinite-css-animations-part-2-of-2\/","title":{"rendered":"How to Control Infinite CSS Animations (Part 2 of 2)"},"content":{"rendered":"\n<p>Time for part 2! This time, we will see how to start\/stop infinite animations <em>smoothly<\/em>. The goal is to make the effect look natural and realistic. As in the previous article, we will study the effects using rotation and then explore additional examples.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/how-to-control-infinite-css-animations-part-1-of-2\/\">How to Control Infinite CSS Animations (Part 1 of 2)<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/how-to-control-infinite-css-animations-part-2-of-2\/\">How to Control Infinite CSS Animations (Part 2 of 2)<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"lets-smooth-start\">Smooth Starts<\/h2>\n\n\n\n<p>Hover the element in the demo below:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_JjVNXxY\" src=\"\/\/codepen.io\/anon\/embed\/JjVNXxY?height=450&amp;theme-id=1&amp;slug-hash=JjVNXxY&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed JjVNXxY\" title=\"CodePen Embed JjVNXxY\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The element starts rotating slowly, then reaches a constant speed. And when you unhover, it will slowly stop as well. Cool, right? It\u2019s even more satisfying if you keep hovering\/un-hovering.<\/p>\n\n\n\n<p>That effect is made possible like this:<\/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\">2s<\/span> linear infinite paused;\n  <span class=\"hljs-attribute\">transition<\/span>: transform <span class=\"hljs-number\">1s<\/span> ease-out;\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  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">rotate<\/span>(-.<span class=\"hljs-number\">2turn<\/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-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>I am still using the same technique I used in the first article, where I consider two rotations with an additive effect, but this time, we don&#8217;t have two animations. We have an animation and a transition.<\/p>\n\n\n\n<p>On hover, two things happen at the same time:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The infinite linear animation starts to play (it\u2019s paused initially): The element rotates from&nbsp;<code>0turn<\/code>&nbsp;to&nbsp;<code>1turn<\/code>&nbsp;with a duration of 2s<\/li>\n\n\n\n<li>A transition is triggered: The element rotates from&nbsp;<code>0turn<\/code>&nbsp;to&nbsp;<code>-.2turn<\/code>&nbsp;with a duration of&nbsp;<code>1s<\/code>.<\/li>\n<\/ol>\n\n\n\n<p>During the first second, the transition will rotate the element in the opposite direction, creating a \u201cbrake\u201d effect on the first animation. After that, the linear behavior of the first animation takes the lead, and we get a rotation at constant speed.<\/p>\n\n\n\n<p>When you unhover, the animation pauses and the transition is triggered in the opposite direction, rotating the element from&nbsp;<code>-.2turn<\/code>&nbsp;to&nbsp;<code>0turn<\/code>. This gives us a smooth stop.<\/p>\n\n\n\n<p>Choosing the right values is very important for the illusion to work. If the transition is too fast or too slow compared to the animation, the effect will be bad. Even the easing is crucial to have a realistic effect.<\/p>\n\n\n\n<p>Here are a few bad examples:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_xbRGKov\/4d24cb9ef8d17634f13a3a2a30331fae\" src=\"\/\/codepen.io\/anon\/embed\/xbRGKov\/4d24cb9ef8d17634f13a3a2a30331fae?height=450&amp;theme-id=1&amp;slug-hash=xbRGKov\/4d24cb9ef8d17634f13a3a2a30331fae&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed xbRGKov\/4d24cb9ef8d17634f13a3a2a30331fae\" title=\"CodePen Embed xbRGKov\/4d24cb9ef8d17634f13a3a2a30331fae\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>I don\u2019t have a formula to share for this effect because I simply did some trial &amp; error. It was quite easy to find values that performed well.<\/p>\n\n\n\n<p>The same code can be adjusted slightly to have the animation running initially and smoothly stop it on hover:<\/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\">.box<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: rotate <span class=\"hljs-number\">2s<\/span> linear infinite paused;\n  <span class=\"hljs-attribute\">transition<\/span>: transform <span class=\"hljs-number\">1s<\/span> ease-out;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:not(<\/span><span class=\"hljs-selector-pseudo\">:hover)<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">rotate<\/span>(-.<span class=\"hljs-number\">2turn<\/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-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>I simply changed&nbsp;<code>:hover<\/code>&nbsp;with&nbsp;<code>:not(:hover)<\/code>!<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_qEqdBEE\/00c4f22ff316e8dcb1c3948972addcb6\" src=\"\/\/codepen.io\/anon\/embed\/qEqdBEE\/00c4f22ff316e8dcb1c3948972addcb6?height=450&amp;theme-id=1&amp;slug-hash=qEqdBEE\/00c4f22ff316e8dcb1c3948972addcb6&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed qEqdBEE\/00c4f22ff316e8dcb1c3948972addcb6\" title=\"CodePen Embed qEqdBEE\/00c4f22ff316e8dcb1c3948972addcb6\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>You can also write it this way:<\/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\">.box<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: rotate <span class=\"hljs-number\">2s<\/span> linear infinite;\n  <span class=\"hljs-attribute\">transition<\/span>: transform <span class=\"hljs-number\">1s<\/span> ease-out;\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>: paused;\n  <span class=\"hljs-attribute\">transform<\/span>: <span class=\"hljs-built_in\">rotate<\/span>(.<span class=\"hljs-number\">2turn<\/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-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>We start with the animation running, then pause it on hover. We also remove the negative sign from the rotation used with the transition.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_XWQMPqY\" src=\"\/\/codepen.io\/anon\/embed\/XWQMPqY?height=450&amp;theme-id=1&amp;slug-hash=XWQMPqY&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed XWQMPqY\" title=\"CodePen Embed XWQMPqY\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"a-generic-code\">More Examples<\/h2>\n\n\n\n<p>The main trick relies on the fact that we can express rotation using two different properties (transform and rotate), but this won\u2019t be possible for other properties because, as far as I know, only rotate and translate can be defined using two different properties.<\/p>\n\n\n\n<p>That said, we can still find other alternatives to achieve the same effect. Here is an example using&nbsp;<a href=\"https:\/\/frontendmasters.com\/blog\/infinite-marquee-animation-using-modern-css\/\">the infinite marquee animation<\/a>. Hover to start the animation<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LEbVPNq\/e493e4cb959447c1d7c942a965f7c201\" src=\"\/\/codepen.io\/anon\/embed\/LEbVPNq\/e493e4cb959447c1d7c942a965f7c201?height=350&amp;theme-id=1&amp;slug-hash=LEbVPNq\/e493e4cb959447c1d7c942a965f7c201&amp;default-tab=result\" height=\"350\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LEbVPNq\/e493e4cb959447c1d7c942a965f7c201\" title=\"CodePen Embed LEbVPNq\/e493e4cb959447c1d7c942a965f7c201\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The main animation uses&nbsp;<code>offset-distance<\/code>&nbsp;to move the elements from left to right, and to have the opposite movement, I used a negative&nbsp;<code>translate<\/code>.<\/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\">img<\/span> {\n  <span class=\"hljs-attribute\">animation<\/span>: \n    x linear infinite <span class=\"hljs-built_in\">var<\/span>(--d) \n    <span class=\"hljs-built_in\">calc<\/span>(-<span class=\"hljs-number\">1<\/span>*sibling-index()*<span class=\"hljs-built_in\">var<\/span>(--d)\/<span class=\"hljs-built_in\">sibling-count<\/span>()) paused;\n  <span class=\"hljs-attribute\">transition<\/span>: translate .<span class=\"hljs-number\">8s<\/span> ease-out;\n}\n<span class=\"hljs-selector-class\">.container<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> <span class=\"hljs-selector-tag\">img<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n  <span class=\"hljs-attribute\">translate<\/span>: -<span class=\"hljs-number\">50px<\/span>;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> x { \n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">offset-distance<\/span>: <span class=\"hljs-number\">100%<\/span>; }\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>Let\u2019s take the example of the glowing border:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_raWVxbG\/9ba3b6b0b582bc8ffbbd48f732c68716\" src=\"\/\/codepen.io\/anon\/embed\/raWVxbG\/9ba3b6b0b582bc8ffbbd48f732c68716?height=450&amp;theme-id=1&amp;slug-hash=raWVxbG\/9ba3b6b0b582bc8ffbbd48f732c68716&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed raWVxbG\/9ba3b6b0b582bc8ffbbd48f732c68716\" title=\"CodePen Embed raWVxbG\/9ba3b6b0b582bc8ffbbd48f732c68716\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>For this one, I am using CSS variables:<\/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-selector-tag\">img<\/span> {\n  <span class=\"hljs-attribute\">mask<\/span>: \n    <span class=\"hljs-built_in\">conic-gradient<\/span>(#<span class=\"hljs-number\">000<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">0<\/span>) content-box,\n    <span class=\"hljs-built_in\">linear-gradient<\/span>(calc(<span class=\"hljs-number\">45deg<\/span> + var(--a) + <span class=\"hljs-built_in\">var<\/span>(--b)), \n      <span class=\"hljs-number\">#0000<\/span> <span class=\"hljs-number\">30%<\/span>,<span class=\"hljs-number\">#000<\/span> <span class=\"hljs-number\">40%<\/span> <span class=\"hljs-number\">60%<\/span>,<span class=\"hljs-number\">#0000<\/span> <span class=\"hljs-number\">70%<\/span>) subtract,\n    <span class=\"hljs-built_in\">conic-gradient<\/span>(#<span class=\"hljs-number\">000<\/span> <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">0<\/span>) padding-box;\n  \n  <span class=\"hljs-attribute\">animation<\/span>: rotation <span class=\"hljs-number\">2s<\/span> linear infinite paused;\n  <span class=\"hljs-attribute\">transition<\/span>: --b .<span class=\"hljs-number\">8s<\/span> ease-out;\n}\n<span class=\"hljs-selector-tag\">img<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">animation-play-state<\/span>: running;\n  <span class=\"hljs-attribute\">--b<\/span>: -.<span class=\"hljs-number\">2turn<\/span>;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotation {\n  <span class=\"hljs-selector-tag\">to<\/span> {<span class=\"hljs-attribute\">--a<\/span>: <span class=\"hljs-number\">1turn<\/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>The angle of the gradient is defined as the sum of two variables&nbsp;<code>--a<\/code>&nbsp;and&nbsp;<code>--b<\/code>. The first variable will have the linear infinite animation, and the second variable will have the transition effect. Since I am adding them, I get the \u201cadditive effect\u201d!<\/p>\n\n\n\n<p>This code can be considered generic because any value can be expressed as a sum of two variables, and as long as you register both variables using&nbsp;<code>@property<\/code>, you can animate them.<\/p>\n\n\n\n<p>Here is the initial code written using variables:<\/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\">rotate<\/span>: <span class=\"hljs-built_in\">calc<\/span>(var(--a) + <span class=\"hljs-built_in\">var<\/span>(--b));\n  <span class=\"hljs-attribute\">animation<\/span>: rotate <span class=\"hljs-number\">2s<\/span> linear infinite paused;\n  <span class=\"hljs-attribute\">transition<\/span>: --b <span class=\"hljs-number\">1s<\/span> ease-out;\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  <span class=\"hljs-attribute\">--b<\/span>: -.<span class=\"hljs-number\">2turn<\/span>;\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> rotate {\n  <span class=\"hljs-selector-tag\">to<\/span> { <span class=\"hljs-attribute\">--a<\/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>And it works the same way:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_LEbVGwg\/6122620ba936a6a9631f2247ed81f36a\" src=\"\/\/codepen.io\/anon\/embed\/LEbVGwg\/6122620ba936a6a9631f2247ed81f36a?height=450&amp;theme-id=1&amp;slug-hash=LEbVGwg\/6122620ba936a6a9631f2247ed81f36a&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed LEbVGwg\/6122620ba936a6a9631f2247ed81f36a\" title=\"CodePen Embed LEbVGwg\/6122620ba936a6a9631f2247ed81f36a\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>In this case, I would use the first version as it is less verbose, but if you are unable to express a movement and its opposite using properties, you can rely on CSS variables, which work in all cases.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"stop-there\">Stop There!<\/h2>\n\n\n\n<p>We can control the speed of infinite animations and smooth out their stopping and starting, but what about controlling the stop position? It would be interesting to hover an infinite animation and smoothly stop it at the same position.<\/p>\n\n\n\n<p>A demo worth a thousand words:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_bNBdpee\/07c5d1bda61985665fbf3408f340e6a4\" src=\"\/\/codepen.io\/anon\/embed\/preview\/bNBdpee\/07c5d1bda61985665fbf3408f340e6a4?height=450&amp;theme-id=1&amp;slug-hash=bNBdpee\/07c5d1bda61985665fbf3408f340e6a4&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed bNBdpee\/07c5d1bda61985665fbf3408f340e6a4\" title=\"CodePen Embed bNBdpee\/07c5d1bda61985665fbf3408f340e6a4\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>On hover, the element will always return to the same position. And when you unhover, the animation runs again.<\/p>\n\n\n\n<p>For this effect, I am going to ditch animations and keyframes and rely on something else. I will define&nbsp;<a href=\"https:\/\/css-tip.com\/animation-without-keyframes\/\">an infinite animation using&nbsp;<code>@starting-style<\/code><\/a>.<\/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-keyword\">@property<\/span> --a {\n  <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">angle<\/span>&gt;\";\n  <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n  <span class=\"hljs-selector-tag\">initial-value<\/span>: <span class=\"hljs-selector-tag\">-100turn<\/span>; \n}\n<span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-built_in\">var<\/span>(--a);\n  <span class=\"hljs-attribute\">transition<\/span>: --a <span class=\"hljs-number\">200s<\/span> linear;\n  @starting-style {\n    <span class=\"hljs-attribute\">--a<\/span>: <span class=\"hljs-number\">0turn<\/span>;\n  }\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>The code looks strange, but the logic is pretty simple. I am registering the variable&nbsp;<code>--a<\/code>&nbsp;with an initial value equal to&nbsp;<code>-100turn<\/code>. The&nbsp;<code>@starting-style<\/code>&nbsp;block will define a \u201cstarting\u201d value equal to&nbsp;<code>0turn<\/code>. This means that on page load, the element will \u201cstart\u201d with a value equal to&nbsp;<code>0turn<\/code>&nbsp;that updates to the value&nbsp;<code>-100turn<\/code>&nbsp;and since we are applying a transition, we get a smooth change.<\/p>\n\n\n\n<p>The use of large values creates the illusion of an infinite animation. The value will animate from&nbsp;<code>0turn<\/code>&nbsp;to&nbsp;<code>-100turn<\/code>&nbsp;in&nbsp;<code>200s<\/code>&nbsp;which is equivalent to 1 turn every 2 seconds for more than 3 minutes. You can go even bigger if 3 minutes aren\u2019t enough.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_zxoGWrX\/775d1d85d034fce7c283e2decca1221e\" src=\"\/\/codepen.io\/anon\/embed\/preview\/zxoGWrX\/775d1d85d034fce7c283e2decca1221e?height=450&amp;theme-id=1&amp;slug-hash=zxoGWrX\/775d1d85d034fce7c283e2decca1221e&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed zxoGWrX\/775d1d85d034fce7c283e2decca1221e\" title=\"CodePen Embed zxoGWrX\/775d1d85d034fce7c283e2decca1221e\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Now, we introduce a new variable and update the code as follows:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@property<\/span> --a {\n  <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">angle<\/span>&gt;\";\n  <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n  <span class=\"hljs-selector-tag\">initial-value<\/span>: <span class=\"hljs-selector-tag\">-100turn<\/span>; \n}\n<span class=\"hljs-keyword\">@property<\/span> --i {\n  <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">number<\/span>&gt;\";\n  <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n  <span class=\"hljs-selector-tag\">initial-value<\/span>: 1; \n}\n<span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-built_in\">calc<\/span>(mod(var(--a),<span class=\"hljs-number\">1turn<\/span>)*<span class=\"hljs-built_in\">var<\/span>(--i));\n  <span class=\"hljs-attribute\">transition<\/span>: --i <span class=\"hljs-number\">0s<\/span>,--a <span class=\"hljs-number\">200s<\/span> linear;\n  @starting-style {\n    <span class=\"hljs-attribute\">--a<\/span>: <span class=\"hljs-number\">0turn<\/span>;\n  }\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-attribute\">--a<\/span>: <span class=\"hljs-number\">0turn<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: --i .<span class=\"hljs-number\">8s<\/span> ease-out,--a <span class=\"hljs-number\">0s<\/span> .<span class=\"hljs-number\">8s<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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 rotation formula is very important. I am using&nbsp;<code>mod()<\/code>to make sure the value gets clamped to the range&nbsp;<code>[0 1turn]<\/code>&nbsp;then I multiply that value by the variable&nbsp;<code>--i<\/code>&nbsp;which is set to&nbsp;<code>1<\/code>&nbsp;initially.<\/p>\n\n\n\n<p>Until now, we still have our infinite rotation. The only change is the <code>rotate<\/code> value, but it won\u2019t affect the visual result. Instead of going from&nbsp;<code>0turn<\/code>&nbsp;to&nbsp;<code>-100turn<\/code>, we will go from&nbsp;<code>0turn<\/code>&nbsp;to&nbsp;<code>1turn<\/code>&nbsp;100 times.<\/p>\n\n\n\n<p>Now let\u2019s see what happens on hover. Let\u2019s suppose our element is at&nbsp;<code>.3turn<\/code>&nbsp;when we hover it. The transition on hover will update the value of&nbsp;<code>--i<\/code>&nbsp;with a duration equal to&nbsp;<code>.8s<\/code>&nbsp;and the value of&nbsp;<code>--a<\/code>&nbsp;instantly after a delay equal to&nbsp;<code>.8s<\/code>. In other words, we first update the value of&nbsp;<code>--i<\/code>&nbsp;to make it equal to&nbsp;<code>0<\/code>&nbsp;then we update the value of&nbsp;<code>--a<\/code>&nbsp;to make it&nbsp;<code>0<\/code>&nbsp;as well.<\/p>\n\n\n\n<p>The first update of&nbsp;<code>--i<\/code>&nbsp;will make the rotation value equal to&nbsp;<code>0turn<\/code>&nbsp;hence we get a transition from&nbsp;<code>.3turn<\/code>&nbsp;to&nbsp;<code>0turn<\/code>; the element is back to its initial position! Updating the value of&nbsp;<code>--a<\/code>&nbsp;will do nothing; it will be useful when you unhover.<\/p>\n\n\n\n<p>When you unhover, the value of&nbsp;<code>--i<\/code>&nbsp;updates instantly from&nbsp;<code>0<\/code>&nbsp;to&nbsp;<code>1<\/code>. Nothing will happen since&nbsp;<code>--a<\/code>&nbsp;is still equal to&nbsp;<code>0<\/code>, which means the rotation is equal to&nbsp;<code>0<\/code>&nbsp;as well. Then, the transition of&nbsp;<code>--a<\/code>&nbsp;is triggered, moving it from&nbsp;<code>0turn<\/code>&nbsp;to&nbsp;<code>-100turn<\/code>. We restart the \u201cinfinite\u201d animation.<\/p>\n\n\n\n<p>Do you get the trick? On hover, the variable&nbsp;<code>--i<\/code>&nbsp;will force the element to return to its initial position regardless of the actual rotation, and when you unhover, the animation starts again because we have set&nbsp;<code>--a<\/code>&nbsp;equal to 0, triggering the transition that simulates the infinite rotation.<\/p>\n\n\n\n<p>You can also adjust the modulo based on your use case. We used&nbsp;<code>1turn<\/code>&nbsp;since the element needs to make&nbsp;<code>1turn<\/code>&nbsp;to get back to the initial position, but we can have more examples with different moduli.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_NPbGvaJ\/18e4fc94b9e476c4126e793afa8a4436\" src=\"\/\/codepen.io\/anon\/embed\/preview\/NPbGvaJ\/18e4fc94b9e476c4126e793afa8a4436?height=450&amp;theme-id=1&amp;slug-hash=NPbGvaJ\/18e4fc94b9e476c4126e793afa8a4436&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed NPbGvaJ\/18e4fc94b9e476c4126e793afa8a4436\" title=\"CodePen Embed NPbGvaJ\/18e4fc94b9e476c4126e793afa8a4436\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>And what about the opposite effect? You activate the animation on hover, and when you unhover, the element gets back to its initial position.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_yyVNEOb\/2513b3380fe8b742500db96d4da035a0\" src=\"\/\/codepen.io\/anon\/embed\/preview\/yyVNEOb\/2513b3380fe8b742500db96d4da035a0?height=450&amp;theme-id=1&amp;slug-hash=yyVNEOb\/2513b3380fe8b742500db96d4da035a0&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed yyVNEOb\/2513b3380fe8b742500db96d4da035a0\" title=\"CodePen Embed yyVNEOb\/2513b3380fe8b742500db96d4da035a0\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Here is the relevant code:<\/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-keyword\">@property<\/span> --a {\n  <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">angle<\/span>&gt;\";\n  <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n  <span class=\"hljs-selector-tag\">initial-value<\/span>: 0<span class=\"hljs-selector-tag\">turn<\/span>; \n}\n<span class=\"hljs-keyword\">@property<\/span> --i {\n  <span class=\"hljs-selector-tag\">syntax<\/span>: \"&lt;<span class=\"hljs-selector-tag\">number<\/span>&gt;\";\n  <span class=\"hljs-selector-tag\">inherits<\/span>: <span class=\"hljs-selector-tag\">false<\/span>;\n  <span class=\"hljs-selector-tag\">initial-value<\/span>: 0; \n}\n<span class=\"hljs-selector-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-built_in\">calc<\/span>(mod(var(--a),<span class=\"hljs-number\">1turn<\/span>)*<span class=\"hljs-built_in\">var<\/span>(--i));\n  <span class=\"hljs-attribute\">transition<\/span>: --i .<span class=\"hljs-number\">5s<\/span> ease-out,--a <span class=\"hljs-number\">0s<\/span> .<span class=\"hljs-number\">5s<\/span>;\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">--a<\/span>: <span class=\"hljs-number\">10turn<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: --i <span class=\"hljs-number\">0s<\/span>,--a <span class=\"hljs-number\">10s<\/span> linear;\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>Try to dissect the code as homework. Compare it with the previous implementation, and you will notice that I mainly switched a few values. We also don\u2019t need to rely on&nbsp;<code>@starting-style<\/code>&nbsp;since we don\u2019t need the infinite animation to run on page load.<\/p>\n\n\n\n<p>Here is the effect applied to the glowing border:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_dPbomxP\" src=\"\/\/codepen.io\/anon\/embed\/preview\/dPbomxP?height=450&amp;theme-id=1&amp;slug-hash=dPbomxP&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dPbomxP\" title=\"CodePen Embed dPbomxP\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"follow-the-shortest-path\">Follow the Shortest Path!<\/h2>\n\n\n\n<p>Ready for a last effect? This time, when you unhover, the element will follow the shortest path to get back to the initial position.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_EaNjpyB\/ed03181af2b962b2a54cee4bdad8bbc7\" src=\"\/\/codepen.io\/anon\/embed\/EaNjpyB\/ed03181af2b962b2a54cee4bdad8bbc7?height=450&amp;theme-id=1&amp;slug-hash=EaNjpyB\/ed03181af2b962b2a54cee4bdad8bbc7&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed EaNjpyB\/ed03181af2b962b2a54cee4bdad8bbc7\" title=\"CodePen Embed EaNjpyB\/ed03181af2b962b2a54cee4bdad8bbc7\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>The direction of the rotation will be different depending on the current position of the element when you un-hover.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"769\" height=\"299\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/05\/s_C6AA43C3C1B478B9638AC2829B138248E95A7DEAE36E24FA8C99B3A572BF120C_1778024165331_image.png?resize=769%2C299&#038;ssl=1\" alt=\"\" class=\"wp-image-9647\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/05\/s_C6AA43C3C1B478B9638AC2829B138248E95A7DEAE36E24FA8C99B3A572BF120C_1778024165331_image.png?w=769&amp;ssl=1 769w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/05\/s_C6AA43C3C1B478B9638AC2829B138248E95A7DEAE36E24FA8C99B3A572BF120C_1778024165331_image.png?resize=300%2C117&amp;ssl=1 300w\" sizes=\"auto, (max-width: 769px) 100vw, 769px\" \/><\/figure>\n\n\n\n<p>To achieve this effect, we update the rotation formula from this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">mod<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>),1<span class=\"hljs-selector-tag\">turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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>to this:<\/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\"><span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-tag\">mod<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>),1<span class=\"hljs-selector-tag\">turn<\/span>) <span class=\"hljs-selector-tag\">-<\/span> (1 + <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">mod<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>),1<span class=\"hljs-selector-tag\">turn<\/span>) <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-class\">.5turn<\/span>))*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));<\/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<p>If we omit the multiplication with the variable&nbsp;<code>--i<\/code>, here is the new addition to the formula:<\/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\">(1 + <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">mod<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--a<\/span>),1<span class=\"hljs-selector-tag\">turn<\/span>) <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-class\">.5turn<\/span>))*<span class=\"hljs-selector-class\">.5turn<\/span><\/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>The&nbsp;<code>mod()<\/code>&nbsp;function gives us a value in the range&nbsp;<code>[0turn 1turn]<\/code>&nbsp;and the goal is to get back to&nbsp;<code>0turn<\/code>&nbsp;on hover following the shortest path. To follow the shortest path, we have only two options: clockwise and counterclockwise.<\/p>\n\n\n\n<p>Two options mean that we have two different ranges of values:&nbsp;<code>[0turn .5turn]<\/code>&nbsp;and&nbsp;<code>[.5turn 1turn]<\/code>. To make it easy to understand, let\u2019s pick one value of each range and apply the formula.<\/p>\n\n\n\n<p>For&nbsp;<code>--a: .3turn<\/code>&nbsp;we get:<\/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\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.3turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> (1 + <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-class\">.3turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-class\">.5turn<\/span>))*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.3turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> (1 + <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">-<\/span><span class=\"hljs-selector-class\">.2turn<\/span>))*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.3turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> (1 + <span class=\"hljs-selector-tag\">-1<\/span>)*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-class\">.3turn<\/span>*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/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>When&nbsp;<code>--i<\/code>&nbsp;is set to 0, we get a transition from&nbsp;<code>.3turn<\/code>&nbsp;to&nbsp;<code>0turn<\/code>.<\/p>\n\n\n\n<p>For&nbsp;<code>--a: .7turn<\/code>&nbsp;we get:<\/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\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.7turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> (1 + <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-class\">.7turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> <span class=\"hljs-selector-class\">.5turn<\/span>))*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.7turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> (1 + <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-class\">.2turn<\/span>))*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.7turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> (1 + 1)*<span class=\"hljs-selector-class\">.5turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>((<span class=\"hljs-selector-class\">.7turn<\/span> <span class=\"hljs-selector-tag\">-<\/span> 1<span class=\"hljs-selector-tag\">turn<\/span>)*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));\n<span class=\"hljs-selector-tag\">rotate<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">-<\/span><span class=\"hljs-selector-class\">.3turn<\/span>*<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--i<\/span>));<\/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>When&nbsp;<code>--i<\/code>&nbsp;is set to 0, we get a transition from&nbsp;<code>-.3turn<\/code>&nbsp;to&nbsp;<code>0turn<\/code>.<\/p>\n\n\n\n<p class=\"learn-more\">But the angle is no longer the same. How does it work?<\/p>\n\n\n\n<p>It\u2019s actually the same angle!&nbsp;<code>-.3turn<\/code>&nbsp;is equivalent to&nbsp;<code>.7turn<\/code>&nbsp;since the difference between them is&nbsp;<code>1turn<\/code>. Instead of having a transition from&nbsp;<code>.7turn<\/code>&nbsp;to&nbsp;<code>0turn<\/code>, which is the long path. I transform it to another angle closer to&nbsp;<code>0turn<\/code>&nbsp;and in this case it\u2019s&nbsp;<code>-.3turn<\/code>. That negative sign is what makes the element rotate in the opposite direction.<\/p>\n\n\n\n<p>The formula looks a bit complex, but at the end it will subtract&nbsp;<code>1turn<\/code>&nbsp;from all the angles in the range&nbsp;<code>[.5turn 1turn]<\/code>&nbsp;to place them in the range&nbsp;<code>[-.5turn 0turn]<\/code>, which is closer to&nbsp;<code>0turn<\/code>. The range&nbsp;<code>[0turn .5turn]<\/code>&nbsp;is not affected.<\/p>\n\n\n\n<p>This last example is probably overkill, but it\u2019s still a cool effect to implement with some modern CSS features. At the end, you don\u2019t need to bother yourself with the formula. All you have to do is adjust the modulo variable to suit your use case.<\/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-class\">.box<\/span> {\n  <span class=\"hljs-attribute\">--t<\/span>: <span class=\"hljs-number\">1turn<\/span>; <span class=\"hljs-comment\">\/* control the modulo *\/<\/span>\n  <span class=\"hljs-attribute\">--d<\/span>: .<span class=\"hljs-number\">5s<\/span>;   <span class=\"hljs-comment\">\/* the transition *\/<\/span>\n  \n  <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-built_in\">calc<\/span>((mod(var(--a),<span class=\"hljs-built_in\">var<\/span>(--t)) - (<span class=\"hljs-number\">1<\/span> + <span class=\"hljs-built_in\">sign<\/span>(mod(var(--a),<span class=\"hljs-built_in\">var<\/span>(--t)) - <span class=\"hljs-built_in\">var<\/span>(--t)\/<span class=\"hljs-number\">2<\/span>))*<span class=\"hljs-built_in\">var<\/span>(--t)\/<span class=\"hljs-number\">2<\/span>)*<span class=\"hljs-built_in\">var<\/span>(--i));\n  <span class=\"hljs-attribute\">transition<\/span>: --i <span class=\"hljs-built_in\">var<\/span>(--d) ease-out,--a <span class=\"hljs-number\">0s<\/span> <span class=\"hljs-built_in\">var<\/span>(--d);\n}\n<span class=\"hljs-selector-class\">.box<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n  <span class=\"hljs-attribute\">--i<\/span>: <span class=\"hljs-number\">1<\/span>;\n  <span class=\"hljs-attribute\">--a<\/span>: <span class=\"hljs-number\">10turn<\/span>;\n  <span class=\"hljs-attribute\">transition<\/span>: --i <span class=\"hljs-number\">0s<\/span>,--a <span class=\"hljs-number\">10s<\/span> linear;\n}<\/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<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_emBNMjq\/714263afa9ff8994d64c3317f0006e5d\" src=\"\/\/codepen.io\/anon\/embed\/emBNMjq\/714263afa9ff8994d64c3317f0006e5d?height=450&amp;theme-id=1&amp;slug-hash=emBNMjq\/714263afa9ff8994d64c3317f0006e5d&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed emBNMjq\/714263afa9ff8994d64c3317f0006e5d\" title=\"CodePen Embed emBNMjq\/714263afa9ff8994d64c3317f0006e5d\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>And here is the effect applied to the glowing border:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_YPKXoEo\" src=\"\/\/codepen.io\/anon\/embed\/YPKXoEo?height=450&amp;theme-id=1&amp;slug-hash=YPKXoEo&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed YPKXoEo\" title=\"CodePen Embed YPKXoEo\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h2>\n\n\n\n<p>That\u2019s all for this second part. Now you have a lot of handy CSS tricks to control infinite animations. You probably won&#8217;t use them all, but it was a good opportunity to study some cool features, such as&nbsp;<code>animation-composition<\/code>,&nbsp;<code>@starting-style<\/code>, and many math functions.<\/p>\n\n\n\n<p>I leave you with a last demo to study. On hover, an infinite animation starts. On click, it gets faster. On unhover, the element snaps back to its initial position following the shortest path:<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_azoVpzN\" src=\"\/\/codepen.io\/anon\/embed\/azoVpzN?height=450&amp;theme-id=1&amp;slug-hash=azoVpzN&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed azoVpzN\" title=\"CodePen Embed azoVpzN\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/how-to-control-infinite-css-animations-part-1-of-2\/\">How to Control Infinite CSS Animations (Part 1 of 2)<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/how-to-control-infinite-css-animations-part-2-of-2\/\">How to Control Infinite CSS Animations (Part 2 of 2)<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>This time we get into very smooth starts and stops for infinite animations using CSS. One of the tricks is layering on a transition on top of an animation.<\/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":[229,100,481,7],"class_list":["post-9604","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-starting-style","tag-animation","tag-animation-play-state","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\/9604","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=9604"}],"version-history":[{"count":9,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9604\/revisions"}],"predecessor-version":[{"id":9670,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9604\/revisions\/9670"}],"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=9604"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9604"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9604"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}