<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Css &#8211; COMPUTERCOURSESONLINE</title>
	<atom:link href="http://www.computercoursesonline.com/index.php/category/css/feed" rel="self" type="application/rss+xml" />
	<link>http://computercoursesonline.com</link>
	<description></description>
	<lastBuildDate>Thu, 19 Jun 2025 21:03:10 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.9.2</generator>
	<item>
		<title>CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control</title>
		<link>http://computercoursesonline.com/index.php/2025/06/19/css-cascade-layers-vs-bem-vs-utility-classes-specificity-control/</link>
					<comments>http://computercoursesonline.com/index.php/2025/06/19/css-cascade-layers-vs-bem-vs-utility-classes-specificity-control/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Thu, 19 Jun 2025 15:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=947</guid>

					<description><![CDATA[CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control Victor Ayomipo 2025-06-19T15:00:00+00:00 2025-06-19T20:32:57+00:00 CSS is wild, really wild. And tricky. But let’s talk specifically about specificity. When writing CSS, it’s close to impossible that you haven’t faced the frustration of styles not applying as...]]></description>
										<content:encoded><![CDATA[<p>              <title>CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control</title></p>
<article>
<header>
<h1>CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control</h1>
<address>Victor Ayomipo</address>
<p>                  2025-06-19T15:00:00+00:00<br />
                  2025-06-19T20:32:57+00:00<br />
                </header>
<p>CSS is wild, really wild. And tricky. But let’s talk specifically about <strong>specificity</strong>.</p>
<p>When writing CSS, it’s close to impossible that you haven’t faced the frustration of styles not applying as expected &mdash; that’s specificity. You applied a style, it worked, and later, you try to override it with a different style and… nothing, it just ignores you. Again, specificity.</p>
<p>Sure, there’s the option of resorting to <code>!important</code> flags, but like all developers before us, it’s always <a href="https://cssguidelin.es/#important">risky and discouraged</a>. It’s way better to fully understand specificity than go down that route because otherwise you wind up fighting your own important styles.</p>
<h2 id="specificity-101">Specificity 101</h2>
<p>Lots of developers understand the concept of specificity in different ways.</p>
<blockquote><p>The core idea of specificity is that the CSS Cascade algorithm used by browsers determines which style declaration is applied when two or more rules match the same element.</p></blockquote>
<p>Think about it. As a project expands, so do the specificity challenges. Let’s say Developer A adds <code>.cart-button</code>, then maybe the button style looks good to be used on the sidebar, but with a little tweak. Then, later, Developer B adds <code>.cart-button .sidebar</code>, and from there, any future changes applied to <code>.cart-button</code> might get overridden by <code>.cart-button .sidebar</code>, and just like that, the specificity war begins.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png"></p>
<p>    <img loading="lazy" width="800" height="500" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Specifity tension represented by a pile of different elements" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/1-specificity-tension.png">Large preview</a>)<br />
    </figcaption></figure>
<p>I’ve written CSS long enough to witness different strategies that developers have used to manage the specificity battles that come with CSS.</p>
<pre><code class="language-css">/&#042; Traditional approach &#042;/
#header .nav li a.active { color: blue; }

/&#042; BEM approach &#042;/
.header__nav-item--active { color: blue; }

/&#042; Utility classes approach &#042;/
.text-blue { color: blue; }

/&#042; Cascade Layers approach &#042;/
@layer components {
  .nav-link.active { color: blue; }
}
</code></pre>
<p>All these methods reflect different strategies on how to control or at least maintain CSS specificity:</p>
<ul>
<li><strong>BEM</strong>: tries to simplify specificity by being explicit.</li>
<li><strong>Utility-first CSS</strong>: tries to bypass specificity by keeping it all atomic.</li>
<li><strong>CSS Cascade Layers</strong>: manage specificity by organizing styles in layered groups.</li>
</ul>
<p>We’re going to put all three side by side and look at how they handle specificity.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png"></p>
<p>    <img loading="lazy" width="800" height="500" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A chart which ilustrates different strategies on how to control or at least maintain CSS specificity" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/2-different-strategies-control-css-specificity.png">Large preview</a>)<br />
    </figcaption></figure>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="my-relationship-with-specificity">My Relationship With Specificity</h2>
<p>I actually used to think that I got the whole picture of CSS specificity. Like the usual inline greater than ID greater than class greater than tag. But, reading <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Cascade">the MDN docs on how the CSS Cascade truly works</a> was an eye-opener.</p>
<p>There’s a code I worked on in an old codebase provided by a client, which looked something like this:</p>
<pre><code class="language-css">/&#042; Legacy code &#042;/
&#035;main-content .product-grid button.add-to-cart {
  background-color: #3a86ff;
  color: white;
  padding: 10px 15px;
  border-radius: 4px;
}

/&#042; 100 lines of other code here &#042;/

/&#042; My new CSS &#042;/
.btn-primary {
  background-color: #4361ee; /&#042; New brand color &#042;/
  color: white;
  padding: 12px 20px;
  border-radius: 4px;
  box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
</code></pre>
<p>Looking at this code, no way that the <code>.btn-primary</code> class stands a chance against whatever specificity chain of selectors was previously written. As far as specification goes, CSS gives the first selector a specificity score of <code>1, 2, 1</code>: one point for the ID, two points for the two classes, and one point for the element selector. Meanwhile, the second selector is scored as <code>0, 1, 0</code> since it only consists of a single class selector.</p>
<p>Sure, I had some options:</p>
<ul>
<li>I could <strong>use <code>!important</code> on the properties</strong> in <code>.btn-primary</code> to override the ones declared in the stronger selector, but the moment that happens, be prepared to use it everywhere. So, I’d rather avoid it.</li>
<li>I could try <strong>going more specific</strong>, but personally, that’s just being cruel to the next developer (who might even be me).</li>
<li>I could <strong>change the styles of the existing code</strong>, but that’s adding to the specificity problem:</li>
</ul>
<pre><code class="language-css">&#035;main-content .product-grid .btn-primary {
  /&#042; edit styles directly &#042;/
}
</code></pre>
<p>Eventually, I ended up writing the whole CSS from scratch.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png"></p>
<p>    <img loading="lazy" width="800" height="500" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Legacy button vs modern button" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/3-legacy-modern-button.png">Large preview</a>)<br />
    </figcaption></figure>
<p>When <em>nesting</em> was introduced, I tried it to control specificity that way:</p>
<div class="break-out">
<pre><code class="language-css">.profile-widget {
  // ... other styles
  .header {
    // ... header styles
    .user-avatar {
      border: 2px solid blue;
      &amp;.is-admin {
        border-color: gold; // This becomes .profile-widget .header .user-avatar.is-admin
      }
    }
  }
}
</code></pre>
</div>
<p>And just like that, I have unintentionally created high-specificity rules. That’s how easily and naturally we can drift toward specificity complexities.</p>
<p>So, to save myself a lot of these issues, I have one principle I always abide by: <a href="https://css-tricks.com/strategies-keeping-css-specificity-low/"><strong>keep specificity as low as possible</strong></a>. And if the selector complexity is becoming a complex chain, I rethink the whole thing.</p>
<div class="partners__lead-place"></div>
<h2 id="bem-the-og-system">BEM: The OG System</h2>
<p>The Block-Element-Modifier (BEM, for short) has been around the block (pun intended) for a long time. It is a methodological system for writing CSS that forces you to make every style hierarchy explicit.</p>
<pre><code class="language-css">/&#042; Block &#042;/
.panel {}

/&#042; Element that depends on the Block &#042;/
.panel&#095;&#095;header {}
.panel&#095;&#095;content {}
.panel&#095;&#095;footer {}

/&#042; Modifier that changes the style of the Block &#042;/
.panel--highlighted {}
.panel&#095;&#095;button--secondary {}
</code></pre>
<p>When I first experienced BEM, I thought it was amazing, despite contrary opinions that it looked ugly. I had no problems with the double hyphens or underscores because they made my CSS predictable and simplified.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png"></p>
<p>    <img loading="lazy" width="800" height="500" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Illustration for BEM methodological system" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/4-bem.png">Large preview</a>)<br />
    </figcaption></figure>
<h3 id="how-bem-handles-specificity">How BEM Handles Specificity</h3>
<p>Take a look at these examples. Without BEM:</p>
<pre><code class="language-css">/&#042; Specificity: 0, 3, 0 &#042;/
.site-header .main-nav .nav-link {
  color: &#035;472EFE;
  text-decoration: none;
}

/&#042; Specificity: 0, 2, 0 &#042;/
.nav-link.special {
  color: &#035;FF5733;
}
</code></pre>
<p>With BEM:</p>
<pre><code class="language-css">/&#042; Specificity: 0, 1, 0 &#042;/
.main-nav&#095;&#095;link {
  color: &#035;472EFE;
  text-decoration: none;
}

/&#042; Specificity: 0, 1, 0 &#042;/
.main-nav&#095;&#095;link--special {
  color: &#035;FF5733;
}
</code></pre>
<p>You see how BEM makes the code look predictable as all selectors are created equal, thus making the code easier to maintain and extend. And if I want to add a button to <code>.main-nav</code>, I just add <code>.main-nav__btn</code>, and if I need a disabled button (modifier), <code>.main-nav__btn--disabled</code>. Specificity is low, as I don’t have to increase it or fight the cascade; I just write a new class.</p>
<p>BEM’s naming principle made sure components lived in isolation, which, for a part of CSS, the specificity part, it worked, i.e, <code>.card__title</code> class will never accidentally clash with a <code>.menu__title</code> class.</p>
<h3 id="where-bem-falls-short">Where BEM Falls Short</h3>
<p>I like the idea of BEM, but it is not perfect, and a lot of people noticed it:</p>
<ul>
<li>The class names can get <em>really</em> long.</li>
</ul>
<div class="break-out">
<pre><code class="language-html">&lt;div class="product-carousel&#095;&#095;slide--featured product-carousel&#095;&#095;slide--on-sale"&gt;
  &lt;!-- yikes --&gt;
&lt;/div&gt;
</code></pre>
</div>
<ul>
<li><strong>Reusability might not be prioritized</strong>, which somewhat contradicts the native CSS ideology. Should a button inside a card be <code>.card__button</code> or reuse a global <code>.button</code> class? With the former, styles are being duplicated, and with the latter, the BEM strict model is being broken.</li>
<li>One of the core pains in software development starts becoming a reality &mdash; <strong>naming things</strong>. <a href="https://css-tricks.com/naming-things-is-only-getting-harder/">I’m sure you know the frustration of that already.</a></li>
</ul>
<p>BEM is good, but sometimes you may need to be flexible with it. A <strong>hybrid system</strong> (maybe using BEM for core components but simpler classes elsewhere) can still keep specificity as low as needed.</p>
<pre><code class="language-css">/&#042; Base button without BEM &#042;/
.button {
  /&#042; Button styles &#042;/
}

/&#042; Component-specific button with BEM &#042;/
.card&#095;&#095;footer .button {
  /&#042; Minor overrides &#042;/
}
</code></pre>
<h2 id="utility-classes-specificity-by-avoidance">Utility Classes: Specificity By Avoidance</h2>
<p>This is also called <a href="https://css-tricks.com/lets-define-exactly-atomic-css/">Atomic CSS</a>. And in its entirety, it <em>avoids specificity.</em></p>
<div class="break-out">
<pre><code class="language-html">&lt;button class="bg-red-300 hover:bg-red-500 text-white py-2 px-4 rounded"&gt;
  A button
&lt;/button&gt;
</code></pre>
</div>
<blockquote><p>The idea behind utility-first classes is that every utility class has the same specificity, which is one class selector. Each class is a tiny CSS property with a single purpose.</p></blockquote>
<p><code>p-2</code>? Padding, nothing more. <code>text-red</code>? Color red for text. <code>text-center</code>? Text alignment. It’s like how LEGOs work, but for styling. You stack classes on top of each other until you get your desired appearance.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png"></p>
<p>    <img loading="lazy" width="800" height="500" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An illustration with a title: Avoiding specifity - one utility at a time" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/css-cascade-layers-bem-utility-classes-specificity-control/5-specificity-by-avoidance.png">Large preview</a>)<br />
    </figcaption></figure>
<h3 id="how-utility-classes-handle-specificity">How Utility Classes Handle Specificity</h3>
<p>Utility classes do not solve specificity, but rather, they take the BEM ideology of low specificity to the extreme. Almost all utility classes have the same lowest possible specificity level of (<code>0</code>, <code>1</code>, <code>0</code>). And because of this, overrides become easy; if more padding is needed, bump <code>.p-2</code> to <code>.p-4</code>.</p>
<p>Another example:</p>
<pre><code class="language-html">&lt;button class="bg-orange-300 hover:bg-orange-700"&gt;
  This can be hovered
&lt;/button&gt;
</code></pre>
<p>If another class, <code>hover:bg-red-500</code>, is added, the order matters for CSS to determine which to use. So, even though the utility classes avoid specificity, the other parts of the CSS Cascade come in, which is the order of appearance, with the last matching selector declared being the winner.</p>
<h3 id="utility-class-trade-offs">Utility Class Trade-Offs</h3>
<p>The most common issue with utility classes is that <strong>they make the code look ugly</strong>. And frankly, I agree. But being able to picture what a component looks like without seeing it rendered is just priceless.</p>
<p>There’s also the argument of reusability, that you repeat yourself every single time. But once one finds a repetition happening, just turn that part into a reusable component. It also has its genuine <strong>limitations</strong> when it comes to specificity:</p>
<ul>
<li>If your brand color changes, which is a global change, and you’re deep in the codebase, you can’t just change one and have others follow like native CSS.</li>
<li>The parent-child relationship that happens naturally in native CSS is out the window due to how atomic utility classes behave.</li>
<li>Some argue the HTML part should be left as markup and the CSS part for styling. Because now, there’s more markup to scan, and if you decide to clean up:</li>
</ul>
<div class="break-out">
<pre><code class="language-html">&lt;!-- Too long --&gt;
&lt;div class="p-4 bg-yellow-100 border border-yellow-300 text-yellow-800 rounded"&gt;

&lt;!-- Better? --&gt;
&lt;div class="alert-warning"&gt;
</code></pre>
</div>
<p>Just like that, we’ve ended up writing CSS. Circle of life.</p>
<p>In my experience with utility classes, they work best for:</p>
<ul>
<li><strong>Speed</strong><br />
Writing the markup, styling it, and seeing the result swiftly.</li>
<li><strong>Predictability</strong><br />
A utility class does exactly what it says it does.</li>
</ul>
<h2 id="cascade-layers-specificity-by-design">Cascade Layers: Specificity By Design</h2>
<p>Now, this is where it gets interesting. BEM offers structure, utility classes gain speed, and CSS Cascade Layers give us something paramount: <strong>control</strong>.</p>
<p>Anyways, Cascade Layers (<code>@layers</code>) groups styles and declares what order the groups should be, regardless of the specificity scores of those rules.</p>
<p>Looking at a set of independent rulesets:</p>
<pre><code class="language-css">button {
  background-color: orange; /&#042; Specificity: 0, 0, 1 &#042;/
}

.button {
  background-color: blue; //&#042; Specificity: 0, 1, 0&#042;/
}

#button {
  background-color: red; /&#042; Specificity: 1, 0, 0 &#042;/
}

/&#042; No matter what, the button is red &#042;/
</code></pre>
<p>But with <code>@layer</code>, let’s say, I want to prioritize the <code>.button</code> class selector. I can shape how the specificity order should go:</p>
<pre><code class="language-css">@layer utilities, defaults, components;

@layer defaults {
  button {
    background-color: orange; /&#042; Specificity: 0, 0, 1 &#042;/
  }
}

@layer components {
  .button {
    background-color: blue; //&#042; Specificity: 0, 1, 0&#042;/
  }
}

@layer utilities {
  #button {
    background-color: red; /&#042; Specificity: 1, 0, 0 &#042;/
  }
}
</code></pre>
<p>Due to how <code>@layer</code> works, <code>.button</code> would win because the <code>components</code> layer is the highest priority, even though <code>#button</code> has higher specificity. Thus, before CSS could even check the usual specificity rules, the layer order would first be respected.</p>
<p>You just have to respect the folks over at W3C, because now one can purposely override an ID selector with a simple class, without even using <code>!important</code>. Fascinating.</p>
<h3 id="cascade-layers-nuances">Cascade Layers Nuances</h3>
<p>Here are some things that are worth calling out when we’re talking about CSS Cascade Layers:</p>
<ul>
<li>Specificity is still part of the game.</li>
<li><code>!important</code> acts differently than expected in <code>@layer</code> (they work in reverse!).</li>
<li><code>@layers</code> aren’t selector-specific but rather style-property-specific.</li>
</ul>
<div class="break-out">
<pre><code class="language-css">@layer base {
  .button {
    background-color: blue;
    color: white;
  }
}

@layer theme {
  .button {
    background-color: red;
    /&#042; No color property here, so white from base layer still applies &#042;/
  }
}
</code></pre>
</div>
<ul>
<li><code>@layer</code> can easily be abused. I’m sure there’s a developer out there with over 20+ layer declarations that’s grown into a monstrosity.</li>
</ul>
<h3 id="comparing-all-three">Comparing All Three</h3>
<p>Now, for the TL;DR folks out there, here’s a side-by-side comparison of the three: BEM, utility classes, and CSS Cascade Layers.</p>
<table class="tablesaw break-out">
<thead>
<tr>
<th>Feature</th>
<th>BEM</th>
<th>Utility Classes</th>
<th>Cascade Layers</th>
</tr>
</thead>
<tbody>
<tr>
<td>Core Idea</td>
<td>Namespace components</td>
<td>Single purpose classes</td>
<td>Control cascade order</td>
</tr>
<tr>
<td>Specificity Control</td>
<td>Low and flat</td>
<td>Avoids entirely</td>
<td>Absolute control due to Layer supremacy</td>
</tr>
<tr>
<td>Code Readability</td>
<td>Clear structure due to naming</td>
<td>Unclear if unfamiliar with the class names</td>
<td>Clear if layer structure is followed</td>
</tr>
<tr>
<td>HTML Verbosity</td>
<td>Moderate class names (can get long)</td>
<td>Many small classes that adds up quickly</td>
<td>No direct impact, stays only in CSS</td>
</tr>
<tr>
<td>CSS Organization</td>
<td>By component</td>
<td>By property</td>
<td>By priority order</td>
</tr>
<tr>
<td>Learning Curve</td>
<td>Requires understanding conventions</td>
<td>Requires knowing the utility names</td>
<td>Easy to pick up, but requires a deep understanding of CSS</td>
</tr>
<tr>
<td>Tools Dependency</td>
<td>Pure CSS</td>
<td>Often depends of third-party e.g Tailwind</td>
<td>Native CSS</td>
</tr>
<tr>
<td>Refactoring Ease</td>
<td>High</td>
<td>Medium</td>
<td>Low</td>
</tr>
<tr>
<td>Best Use Case</td>
<td>Design Systems</td>
<td>Fast builds</td>
<td>Legacy code or third-party codes that need overrides</td>
</tr>
<tr>
<td>Browser Support</td>
<td>All</td>
<td>All</td>
<td>All (except IE)</td>
</tr>
</tbody>
</table>
<p>Among the three, each has its sweet spot:</p>
<ul>
<li><strong>BEM</strong> is best when:
<ul>
<li>There’s a clear design system that needs to be consistent,</li>
<li>There’s a team with different philosophies about CSS (BEM can be the middle ground), and</li>
<li>Styles are less likely to leak between components.</li>
</ul>
</li>
<li><strong>Utility classes</strong> work best when:
<ul>
<li>You need to build fast, like prototypes or MVPs, and</li>
<li>Using a component-based JavaScript framework like React.</li>
</ul>
</li>
<li><strong>Cascade Layers</strong> are most effective when:
<ul>
<li>Working on legacy codebases where you need full specificity control,</li>
<li>You need to integrate third-party libraries or styles from different sources, and</li>
<li>Working on a large, complex application or projects with long-term maintenance.</li>
</ul>
</li>
</ul>
<p>If I had to choose or rank them, I’d go for utility classes with Cascade Layers over using BEM. But that’s just me!</p>
<div class="partners__lead-place"></div>
<h2 id="where-they-intersect-how-they-can-work-together">Where They Intersect (How They Can Work Together)</h2>
<p>Among the three, Cascade Layers should be seen as an orchestrator, as it can work with the other two strategies. <code>@layer</code> is a fundamental tenet of the CSS Cascade’s architecture, unlike BEM and utility classes, which are methodologies for controlling the Cascade’s behavior.</p>
<pre><code class="language-css">/&#042; Cascade Layers + BEM &#042;/
@layer components {
  .card&#095;&#095;title {
    font-size: 1.5rem;
    font-weight: bold;
  }
}

/&#042; Cascade Layers + Utility Classes &#042;/
@layer utilities {
  .text-xl {
    font-size: 1.25rem;
  }
  .font-bold {
    font-weight: 700;
  }
}
</code></pre>
<p>On the other hand, using BEM with utility classes would just end up clashing:</p>
<div class="break-out">
<pre><code class="language-javascript">&lt;!-- This feels wrong --&gt;
&lt;div class="card__container p-4 flex items-center"&gt;
  &lt;p class="card__title text-xl font-bold"&gt;Something seems wrong&lt;/p&gt;
&lt;/div&gt;
</code></pre>
</div>
<p>I’m putting all my cards on the table: I’m a utility-first developer. And most utility class frameworks use <code>@layer</code> behind the scenes (e.g., <a href="https://tailwindcss.com/blog/tailwindcss-v4#designed-for-the-modern-web">Tailwind</a>). So, those two are already together in the bag.</p>
<p>But, do I dislike BEM? Not at all! I’ve used it a lot and still would, if necessary. I just find naming things to be an exhausting exercise.</p>
<p>That said, we’re all different, and you might have opposing thoughts about what you think feels best. It truly doesn’t matter, and that’s the beauty of this web development space. <em>Multiple routes can lead to the same destination</em>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>So, when it comes to comparing BEM, utility classes, and CSS Cascade Layers, is there a true “winning” approach for controlling specificity in the Cascade?</p>
<p>First of all, CSS Cascade Layers are arguably the most powerful CSS feature that we’ve gotten in years. They shouldn’t be confused with BEM or utility classes, which are strategies rather than part of the CSS feature set.</p>
<p>That’s why I like the idea of combining either BEM with Cascade Layers or utility classes with Cascade Layers. Either way, the idea is to <strong>keep specificity low and leverage Cascade Layers to set priorities on those styles</strong>.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/06/19/css-cascade-layers-vs-bem-vs-utility-classes-specificity-control/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Smashing Animations Part 4: Optimising SVGs</title>
		<link>http://computercoursesonline.com/index.php/2025/06/04/smashing-animations-part-4-optimising-svgs/</link>
					<comments>http://computercoursesonline.com/index.php/2025/06/04/smashing-animations-part-4-optimising-svgs/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 04 Jun 2025 08:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=933</guid>

					<description><![CDATA[Smashing Animations Part 4: Optimising SVGs Smashing Animations Part 4: Optimising SVGs Andy Clarke 2025-06-04T08:00:00+00:00 2025-06-05T21:02:28+00:00 SVG animations take me back to the Hanna-Barbera cartoons I watched as a kid. Shows like Wacky Races, The Perils of Penelope Pitstop, and, of course, Yogi Bear. They inspired me to lovingly recreate some classic Toon Titles using...]]></description>
										<content:encoded><![CDATA[<p>              <title>Smashing Animations Part 4: Optimising SVGs</title></p>
<article>
<header>
<h1>Smashing Animations Part 4: Optimising SVGs</h1>
<address>Andy Clarke</address>
<p>                  2025-06-04T08:00:00+00:00<br />
                  2025-06-05T21:02:28+00:00<br />
                </header>
<p>SVG animations take me back to the Hanna-Barbera cartoons I watched as a kid. Shows like <em>Wacky Races</em>, <em>The Perils of Penelope Pitstop</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear"><em>Yogi Bear</em></a>. They inspired me to lovingly recreate some classic <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a> using CSS, SVG, and SMIL animations.</p>
<p>But getting animations to load quickly and work smoothly needs more than nostalgia. It takes clean design, lean code, and a process that makes complex SVGs easier to animate. Here’s how I do it.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://stuffandnonsense.co.uk/toon-titles"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An example of Toon Titles from the website" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      There’s now a website where you can see all my <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a>. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/1-toon-titles.png">Large preview</a>)<br />
    </figcaption></figure>
<div class="refs">
<ul>
<li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li>
<li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li>
<li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-3-smil-not-dead/">Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</a></li>
</ul>
</div>
<p>Whether for personal projects or commercial work, preparing SVGs well ensures they’re accessible. Optimising them ensures they load quickly, especially on mobile, and thinking carefully about how they’re structured makes maintaining them easier. I’ve developed a <strong>process that balances visuals with accessibility and performance</strong> and makes complex SVGs easier to work with.</p>
<p>So, to explain my process, I’ve chosen an episode of <em>The Yogi Bear Show</em> called “Bewitched Bear,” first broadcast in January 1960. In this story, Yogi steals a witch’s broom to help him grab “pic-a-nic” baskets.</p>
<p>“Hey, hey, hey!”</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An illustration from the “Bewitched Bear” episode of The Yogi Bear Show where bear is on a witch’s broom" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/2-yogi-bear-bewitched-bear.png">Large preview</a>)<br />
    </figcaption></figure>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="start-clean-and-design-with-optimisation-in-mind">Start Clean And Design With Optimisation In Mind</h2>
<p>Keeping things simple is key to making SVGs that are optimised and ready to animate. Tools like Adobe Illustrator convert bitmap images to vectors, but the output often contains too many extraneous groups, layers, and masks. Instead, I start cleaning in Sketch, work from a reference image, and use the Pen tool to create paths.</p>
<blockquote><p><strong>Tip</strong>: <a href="https://affinity.serif.com/en-gb/designer/">Affinity Designer</a> (UK) and <a href="https://www.sketch.com">Sketch</a> (Netherlands) are alternatives to Adobe Illustrator and Figma. Both are independent and based in Europe. Sketch has been my default design app since Adobe killed Fireworks.</p></blockquote>
<h2 id="beginning-with-outlines">Beginning With Outlines</h2>
<p>For these Toon Titles illustrations, I first use the Pen tool to draw black outlines with as few anchor points as possible. The more points a shape has, the bigger a file becomes, so simplifying paths and reducing the number of points makes an SVG much smaller, often with no discernible visual difference.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Two outlines with different anchor points" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <strong>Left</strong>: 160 anchor points. <strong>Right</strong>: 80 points. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/3-outlines-anchor-points.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Bearing in mind that parts of this Yogi illustration will ultimately be animated, I keep outlines for this Bewitched Bear’s body, head, collar, and tie separate so that I can move them independently. The head might nod, the tie could flap, and, like in those classic cartoons, Yogi’s collar will hide the joins between them.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Separate outlines for body, head, collar and tie, and broom." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Separate outlines for body, head, collar and tie, and broom. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/4-separate-outlines.png">Large preview</a>)<br />
    </figcaption></figure>
<h2 id="drawing-simple-background-shapes">Drawing Simple Background Shapes</h2>
<p>With the outlines in place, I use the Pen tool again to draw new shapes, which fill the areas with colour. These colours sit behind the outlines, so they don’t need to match them exactly. The fewer anchor points, the smaller the file size.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Original vector artwork and a simplified version with Adobe Illustrator" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <strong>Left</strong>: Original vector artwork, 8 Kb. <strong>Right</strong>: Simplified using Adobe Illustrator, 2 Kb. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/5-simple-background-shapes.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Sadly, neither Affinity Designer nor Sketch has tools that can simplify paths, but if you have it, using Adobe Illustrator can shave a few extra kilobytes off these background shapes.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An illustration how to simplify paths with Adobe Illustrator" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Adobe Illustrator: Object → Path → Simplify. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/6-adobe-illustrator-simplify-paths.png">Large preview</a>)<br />
    </figcaption></figure>
<h2 id="optimising-the-code">Optimising The Code</h2>
<p>It’s not just metadata that makes SVG bulkier. The way you export from your design app also affects file size.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Vector artwork ready for optimisation." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Vector artwork ready for optimisation. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/7-vector-artwork-ready-optimisation.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Exporting just those simple background shapes from Adobe Illustrator includes unnecessary groups, masks, and bloated path data by default. Sketch’s code is barely any better, and there’s plenty of room for improvement, even in its SVGO Compressor code. I rely on Jake Archibald’s <a href="https://jakearchibald.github.io/svgomg/">SVGOMG</a>, which uses SVGO v3 and consistently delivers the best optimised SVGs.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png"></p>
<p>    <img loading="lazy" width="800" height="439" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Jake Archibald’s SVGOMG online optimisation tool." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Jake Archibald’s SVGOMG online optimisation tool. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/8-jake-archibald-svgomg-online-optimisation-tool.png">Large preview</a>)<br />
    </figcaption></figure>
<div class="partners__lead-place"></div>
<h2 id="layering-svg-elements">Layering SVG Elements</h2>
<p>My process for preparing SVGs for animation goes well beyond drawing vectors and optimising paths &mdash; it also includes how I <strong>structure the code</strong> itself. When every visual element is crammed into a single SVG file, even optimised code can be a nightmare to navigate. Locating a specific path or group often feels like searching for a needle in a haystack.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Toon Titles recreation of the Yogi Bear title card" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Toon Titles recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/9-yogi-bear-title-card-toon-titles-recreation.png">Large preview</a>)<br />
    </figcaption></figure>
<p>That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time &mdash; always in the order they’ll appear in the final file. This lets me build the master SVG gradually by pasting it in each cleaned-up section. For example, I start with backgrounds like this gradient and title graphic.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Gradient background and title graphic." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Gradient background and title graphic. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/10-gradient-background-title-graphic.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Instead of facing a wall of SVG code, I can now easily identify the background gradient’s path and its associated <code>linearGradient</code>, and see the group containing the title graphic. I take this opportunity to add a comment to the code, which will make editing and adding animations to it easier in the future:</p>
<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;defs&gt;
    &lt;!-- ... --&gt;
  &lt;/defs&gt;
  &lt;path fill="url(#grad)" d="…"/&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
  &lt;g&gt;
    &lt;path … /&gt;
    &lt;!-- ... --&gt; 
  &lt;/g&gt;
&lt;/svg&gt;
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Trail with Gaussian Blur." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Trail with Gaussian Blur. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/11-trail-gaussian-blur.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Next, I add the blurred trail from Yogi’s airborne broom. This includes defining a Gaussian Blur filter and placing its path between the background and title layers:</p>
<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;defs&gt;
    &lt;linearGradient id="grad" …&gt;…&lt;/linearGradient&gt;
    &lt;filter id="trail" …&gt;…&lt;/filter&gt;
  &lt;/defs&gt;
  &lt;!-- GRADIENT --&gt;
  &lt;!-- TRAIL --&gt;
  &lt;path filter="url(#trail)" …/&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
&lt;/svg&gt;
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear’s magical stars." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear’s magical stars. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/12-yogi-bear-magical-stars.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Then come the magical stars, added in the same sequential fashion:</p>
<pre><code class="language-svg">&lt;svg ...&gt;
  &lt;!-- GRADIENT --&gt;
  &lt;!-- TRAIL --&gt;
  &lt;!-- STARS --&gt;
  &lt;!-- TITLE GRAPHIC --&gt;
&lt;/svg&gt;
</code></pre>
<p>To keep everything organised and animation-ready, I create an empty group that will hold all the parts of Yogi:</p>
<pre><code class="language-svg">&lt;g id="yogi"&gt;...&lt;/g&gt;
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Added Yogi Bear’s component parts" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Sequentially adding Yogi Bear’s component parts. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/13-yogi-bear-component-parts.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Then I build Yogi from the ground up &mdash; starting with background props, like his broom:</p>
<pre><code class="language-svg">&lt;g id="broom"&gt;...&lt;/g&gt;
</code></pre>
<p>Followed by grouped elements for his body, head, collar, and tie:</p>
<pre><code class="language-svg">&lt;g id="yogi"&gt;
  &lt;g id="broom"&gt;…&lt;/g&gt;
  &lt;g id="body"&gt;…&lt;/g&gt;
  &lt;g id="head"&gt;…&lt;/g&gt;
  &lt;g id="collar"&gt;…&lt;/g&gt;
  &lt;g id="tie"&gt;…&lt;/g&gt;
&lt;/g&gt;
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Toon Titles recreation of the Yogi Bear title card" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Toon Titles recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/14-yogi-bear-title-card-toon-titles-recreation.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues later on &mdash; they’ll all slot into place automatically. I keep my code <strong>clean</strong>, <strong>readable</strong>, and <strong>ordered logically</strong> by layering elements this way. It also makes animating smoother, as each component is easier to identify.</p>
<h2 id="reusing-elements-with-use">Reusing Elements With <code>&lt;use&gt;</code></h2>
<p>When duplicate shapes get reused repeatedly, SVG files can get bulky fast. My recreation of the “Bewitched Bear” title card contains 80 stars in three sizes. Combining all those shapes into one optimised path would bring the file size down to 3KB. But I want to animate individual stars, which would almost double that to 5KB:</p>
<pre><code class="language-svg">&lt;g id="stars"&gt;
 &lt;path class="star-small" fill="#eae3da" d="..."/&gt;
 &lt;path class="star-medium" fill="#eae3da" d="..."/&gt;
 &lt;path class="star-large" fill="#eae3da" d="..."/&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>
<p>Moving the stars’ <code>fill</code> attribute values to their parent group reduces the overall weight a little:</p>
<pre><code class="language-svg">&lt;g id="stars" fill="#eae3da"&gt;
 &lt;path class="star-small" d="…"/&gt;
 &lt;path class="star-medium" d="…"/&gt;
 &lt;path class="star-large" d="…"/&gt;
 &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear’s sparkling stars." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear’s sparkling stars. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/15-yogi-bear-sparkling-stars.png">Large preview</a>)<br />
    </figcaption></figure>
<p>But a more efficient and manageable option is to define each star size as a reusable template:</p>
<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
  &lt;path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
  &lt;path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/&gt;
&lt;/defs&gt;
</code></pre>
</div>
<p>With this setup, changing a star’s design only means updating its template once, and every instance updates automatically. Then, I reference each one using <code>&lt;use&gt;</code> and position them with <code>x</code> and <code>y</code> attributes:</p>
<pre><code class="language-svg">&lt;g id="stars"&gt;
  &lt;!-- Large stars --&gt;
  &lt;use href="#star-large" x="1575" y="495"/&gt;
  &lt;!-- ... --&gt;
  &lt;!-- Medium stars --&gt;
  &lt;use href="#star-medium" x="1453" y="696"/&gt;
  &lt;!-- ... --&gt;
  &lt;!-- Small stars --&gt;
  &lt;use href="#star-small" x="1287" y="741"/&gt;
  &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>
<p>This approach makes the SVG easier to manage, lighter to load, and faster to iterate on, especially when working with dozens of repeating elements. Best of all, it keeps the markup clean <strong>without compromising on flexibility or performance</strong>.</p>
<div class="partners__lead-place"></div>
<h2 id="adding-animations">Adding Animations</h2>
<p>The stars trailing behind Yogi’s stolen broom bring so much personality to the animation. I wanted them to sparkle in a seemingly random pattern against the dark blue background, so I started by defining a keyframe animation that cycles through different <code>opacity</code> levels:</p>
<pre><code class="language-css">@keyframes sparkle {
  0%, 100% { opacity: .1; }
  50% { opacity: 1; }
}
</code></pre>
<p>Next, I applied this looping animation to every <code>use</code> element inside my stars group:</p>
<pre><code class="language-css">&#035;stars use {
  animation: sparkle 10s ease-in-out infinite;
}
</code></pre>
<p>The secret to creating a convincing twinkle lies in <strong>variation</strong>. I staggered animation delays and durations across the stars using <code>nth-child</code> selectors, starting with the quickest and most frequent sparkle effects:</p>
<pre><code class="language-css">/&#042; Fast, frequent &#042;/
&#035;stars use:nth-child(n + 1):nth-child(-n + 10) {
  animation-delay: .1s;
  animation-duration: 2s;
}
</code></pre>
<p>From there, I layered in additional timings to mix things up. Some stars sparkle slowly and dramatically, others more randomly, with a variety of rhythms and pauses:</p>
<pre><code class="language-css">/&#042; Medium &#042;/
&#035;stars use:nth-child(n + 11):nth-child(-n + 20) { ... }

/&#042; Slow, dramatic &#042;/
&#035;stars use:nth-child(n + 21):nth-child(-n + 30) { ... }

/&#042; Random &#042;/
&#035;stars use:nth-child(3n + 2) { ... }

/&#042; Alternating &#042;/
&#035;stars use:nth-child(4n + 1) { ... }

/&#042; Scattered &#042;/
&#035;stars use:nth-child(n + 31) { ... }
</code></pre>
<p>By thoughtfully structuring the SVG and reusing elements, I can build complex-looking animations without bloated code, making even a simple effect like changing <code>opacity</code> sparkle.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Subtle movements bring Yogi Bear to life. (<a href="https://files.smashing.media/articles/smashing-animations-part-4-optimising-svgs/16-subtle-movements-yogi-bear.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Then, for added realism, I make Yogi’s head wobble:</p>
<div class="break-out">
<pre><code class="language-css">@keyframes headWobble {
  0% { transform: rotate(-0.8deg) translateY(-0.5px); }
  100% { transform: rotate(0.9deg) translateY(0.3px); }
}

&#035;head {
  animation: headWobble 0.8s cubic-bezier(0.5, 0.15, 0.5, 0.85) infinite alternate;
}
</code></pre>
</div>
<p>His tie waves:</p>
<div class="break-out">
<pre><code class="language-css">@keyframes tieWave {
  0%, 100% { transform: rotateZ(-4deg) rotateY(15deg) scaleX(0.96); }
  33% { transform: rotateZ(5deg) rotateY(-10deg) scaleX(1.05); }
  66% { transform: rotateZ(-2deg) rotateY(5deg) scaleX(0.98); }
}

&#035;tie {
  transform-style: preserve-3d;
  animation: tieWave 10s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite;
}
</code></pre>
</div>
<p>His broom swings:</p>
<div class="break-out">
<pre><code class="language-css">@keyframes broomSwing {
  0%, 20% { transform: rotate(-5deg); }
  30% { transform: rotate(-4deg); }
  50%, 70% { transform: rotate(5deg); }
  80% { transform: rotate(4deg); }
  100% { transform: rotate(-5deg); }
}

&#035;broom {
  animation: broomSwing 4s cubic-bezier(0.5, 0.05, 0.5, 0.95) infinite;
}
</code></pre>
</div>
<p>And, finally, Yogi himself gently rotates as he flies on his magical broom:</p>
<div class="break-out">
<pre><code class="language-css">@keyframes yogiWobble {
  0% { transform: rotate(-2.8deg) translateY(-0.8px) scale(0.998); }
  30% { transform: rotate(1.5deg) translateY(0.3px); }
  100% { transform: rotate(3.2deg) translateY(1.2px) scale(1.002); }
}

&#035;yogi {
  animation: yogiWobble 3.5s cubic-bezier(.37, .14, .3, .86) infinite alternate;
}
</code></pre>
</div>
<p>All these subtle movements bring Yogi to life. By developing structured SVGs, I can create animations that feel full of character without writing a single line of JavaScript.</p>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="bNdwJBN" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Bewitched Bear CSS/SVG animation [forked]](https://codepen.io/smashingmag/pen/bNdwJBN) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNdwJBN">Bewitched Bear CSS/SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="conclusion">Conclusion</h2>
<p>Whether you’re recreating a classic title card or animating icons for an interface, the principles are the same:</p>
<ol>
<li>Start clean,</li>
<li>Optimise early, and</li>
<li>Structure everything with animation in mind.</li>
</ol>
<p>SVGs offer incredible creative freedom, but only if kept <strong>lean</strong> and <strong>manageable</strong>. When you plan your process like a production cell &mdash; layer by layer, element by element &mdash; you’ll spend less time untangling code and more time bringing your work to life.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/06/04/smashing-animations-part-4-optimising-svgs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</title>
		<link>http://computercoursesonline.com/index.php/2025/05/21/smashing-animations-part-3-smils-not-dead-baby-smils-not-dead/</link>
					<comments>http://computercoursesonline.com/index.php/2025/05/21/smashing-animations-part-3-smils-not-dead-baby-smils-not-dead/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 21 May 2025 08:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=935</guid>

					<description><![CDATA[Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead Andy Clarke 2025-05-21T08:00:00+00:00 2025-06-05T21:02:28+00:00 The SMIL specification was introduced by the W3C in 1998 for synchronizing multimedia. This was long before CSS animations or JavaScript-based animation libraries were available. It was built into SVG 1.1, which is why we can still use it there today....]]></description>
										<content:encoded><![CDATA[<p>              <title>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</title></p>
<article>
<header>
<h1>Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead</h1>
<address>Andy Clarke</address>
<p>                  2025-05-21T08:00:00+00:00<br />
                  2025-06-05T21:02:28+00:00<br />
                </header>
<p>The SMIL specification was introduced by the W3C in 1998 for synchronizing multimedia. This was long before CSS animations or JavaScript-based animation libraries were available. It was built into SVG 1.1, which is why we can still use it there today.</p>
<p>Now, you might’ve heard that <a href="https://css-tricks.com/smil-is-dead-long-live-smil-a-guide-to-alternatives-to-smil-features">SMIL is dead</a>. However, it’s alive and well since Google reversed a decision to deprecate the technology almost a decade ago. It remains a terrific choice for designers and developers who want simple, semantic ways to add animations to their designs.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/1-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p><strong>Tip</strong>: <em>There’s now a website where you can see all my <a href="https://stuffandnonsense.co.uk/toon-titles">Toon Titles</a>.</em></p>
<div class="refs">
<ul>
<li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</a></li>
<li><a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-2-css-masking-add-extra-dimension/">Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</a></li>
</ul>
</div>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="introducing-mike-worth">Introducing Mike Worth</h2>
<p>I’ve recently been working on a new website for Emmy-award-winning game composer Mike Worth. He hired me to create a bold, retro-style design that showcases his work. I used animations throughout to delight and surprise his audience as they move through his website.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png"></p>
<p>    <img loading="lazy" width="800" height="550" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Illustrations from Mike Worth’s website" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Design by <a href="https://stuffandnonsense.co.uk/">Andy Clarke, Stuff &amp; Nonsense</a>. Mike Worth’s website will launch in June 2025, but you can see <a href="https://codepen.io/collection/YwMKPb">examples from this article on CodePen</a>. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/2-mike-worth-website.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Mike loves ’90s animation &mdash; especially <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)">Disney’s</a> <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)"><em>Duck Tales</em></a>. Unsurprisingly, my taste in cartoons stretches back a little further to <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear"><em>The Yogi Bear Show</em></a>. So, to explain how this era of animation relates to SVG, I’ll be adding SMIL animations in SVG to title cards from some classic Yogi Bear cartoons.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="300" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustrations" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/3-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Fundamentally, animation changes how an element looks and where it appears over time using a few basic techniques. That might be simply shifting an element up or down, left or right, to create the appearance of motion, like Yogi Bear moving across the screen.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear title card design recreated by Andy Clarke" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/4-yogi-bear-title-card.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Rotating objects around a fixed point can create everything, from simple spinning effects to natural-looking movements of totally normal things, like a bear under a parachute falling from the sky.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear title card recreated by Andy Clarke" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/5-yogi-bear-title-card-design.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Scaling makes an element grow, shrink, or stretch, which can add drama, create perspective, or simulate depth.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear title card recreated by Andy Clarke" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1958). Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/6-yogi-bear-title-card.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Changing colour and transitioning opacity can add atmosphere, create a mood, and enhance visual storytelling. Just these basic principles can create animations that attract attention and improve someone’s experience using a design.</p>
<p>These results are all achievable using CSS animations, but some SVG properties can’t be animated using CSS. Luckily, we can do more &mdash; and have much more fun &mdash; using SMIL animations in SVG. We can combine complex animations, move objects along paths, and control when they start, stop, and everything in between.</p>
<p>Animations can be embedded within any SVG element, including <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorials/SVG_from_scratch/Basic_shapes">primitive shapes</a> like circles, ellipses, and rectangles. They can also be encapsulated into groups, paths, and polygons:</p>
<pre><code class="language-svg">&lt;circle ...&gt;
  &lt;animate&gt;...&lt;/animate&gt;
&lt;/circle&gt;
</code></pre>
<p>Animations can also be defined outside an element, elsewhere in an SVG, and connected to it using an <code>xlink</code> attribute:</p>
<pre><code class="language-svg">&lt;g id="yogi"&gt;...&lt;/g&gt;
  ...
&lt;animate xlink:href="#yogi"&gt;…&lt;/animate&gt;
</code></pre>
<h2 id="building-an-animation">Building An Animation</h2>
<p><code>&lt;animate&gt;</code> is just one of several animation elements in SVG. Together with an <code>attributeName</code> value, it enables animations based on one or more of an element’s attributes.</p>
<p>Most animation explanations start by moving a primitive shape, like this exciting circle:</p>
<pre><code class="language-svg">&lt;circle
  r="50"
  cx="50" 
  cy="50" 
  fill="#062326" 
  opacity="1"
/&gt;
</code></pre>
<p>Using this <code>attributeName</code> property, I can define which of this circle’s attributes I want to animate, which, in this example, is its <code>cx</code> (x-axis center point) position:</p>
<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate attributename="cx"&gt;&lt;/animate&gt;
&lt;/circle&gt;
</code></pre>
<p>On its own, this does precisely nothing until I define three more values. The <code>from</code> keyword specifies the circle’s initial position, <code>to</code>, its final position, and the <code>dur</code>-ation between those two positions:</p>
<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  from="50" 
  to="500"
  dur="1s"&gt;
  &lt;/animate&gt;
&lt;/circle&gt;
</code></pre>
<p>If I want more precise control, I can replace <code>from</code> and <code>to</code> with a set of <code>values</code> separated by semicolons:</p>
<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  values="50; 250; 500; 250;"
  dur="1s"&gt;
  &lt;/animate&gt;
&lt;/circle&gt;
</code></pre>
<p>Finally, I can define how many times the animation repeats (<code>repeatcount</code>) and even after what period that repeating should stop (<code>repeatdur</code>):</p>
<pre><code class="language-svg">&lt;circle ... &gt;
  &lt;animate 
  attributename="cx"
  values="50; 250; 500; 250;"
  dur="1s"
  repeatcount="indefinite"
  repeatdur="180s"&gt;
&lt;/circle&gt;
</code></pre>
<p>Most SVG elements have attributes that can be animated. This title card from 1959’s <a href="https://yogibear.fandom.com/wiki/Brainy_Bear">“Brainy Bear” episode</a> shows Yogi in a crazy scientist‘s brain experiment. Yogi’s head is under the dome, and energy radiates around him.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration where Yogi’s head is under the dome, and energy radiates around him" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/7-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To create the buzz around Yogi, my SVG includes three <code>path</code> elements, each with <code>opacity</code>, <code>stroke</code>, and <code>stroke-width</code> attributes, which can all be animated:</p>
<pre><code class="language-svg">&lt;path opacity="1" stroke="#fff" stroke-width="5" ... /&gt;
</code></pre>
<p>I animated each path’s <code>opacity</code>, changing its value from <code>1</code> to <code>.5</code> and back again:</p>
<pre><code class="language-svg">&lt;path opacity="1" ... &gt;
  &lt;animate 
    attributename="opacity"
    values="1; .25; 1;"
    dur="1s"
    repeatcount="indefinite"&gt;
  &lt;/animate&gt;
&lt;/path&gt;
</code></pre>
<p>Then, to radiate energy from Yogi, I specified when each animation should <code>begin</code>, using a different value for each <code>path</code>:</p>
<pre><code class="language-svg">&lt;path ... &gt;
  &lt;animate begin="0" … &gt;
&lt;/path&gt;

&lt;path ... &gt;
  &lt;animate begin=".5s" … &gt;
&lt;/path&gt;

&lt;path ... &gt;
  &lt;animate begin="1s" … &gt;
&lt;/path&gt;
</code></pre>
<p>I’ll explain more about the <code>begin</code> property and how to start animations after this short commercial break.</p>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="qEEzYgG" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Brainy Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/qEEzYgG) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/qEEzYgG">Brainy Bear SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>To make animations appear more natural, I can apply more than one <code>animate</code> element and give each one a different <code>attributename</code> value. Those paths also contain a <code>stroke-width</code> attribute, which I can also animate by changing the stroke widths between <code>5</code> and <code>7</code>:</p>
<pre><code class="language-svg">&lt;path ... &gt;
  &lt;animate attributename="opacity" ... &gt;&lt;/animate&gt;
  &lt;animate attributename="stroke-width" ... &gt;&lt;/animate&gt;
&lt;/path&gt;
</code></pre>
<p>Finally, I can animate the dome over Yogi’s head, changing its <code>fill</code> colour between two values over five seconds to create the impression that the crazy scientist’s machine is heating up:</p>
<pre><code class="language-svg">&lt;path fill="#50D9E0" ... &gt;
  &lt;animate
    attributename="fill"
    values="#50D9E0; #E18C50;"
    dur="5s"
    begin="2s"
  &gt;
&lt;/path&gt;
</code></pre>
<p>Implement that code, and you’ll soon notice that the dome returns to its original state after the animation is complete. To retain its colour at the end of the animation, I can add the &mdash; confusingly named &mdash; <code>fill</code> property and a value of <code>freeze.</code> This stops the animation in its final state and prevents it from returning to the original colour:</p>
<pre><code class="language-svg">&lt;path fill="#50D9E0" ... &gt;
  &lt;animate fill="freeze"&gt;
&lt;/path&gt;
</code></pre>
<p>Animating attributes brings these title card designs to life, whether by adjusting the position of a primitive shape, its opacity, and stroke width or by creating complex sequences with staggered timing. But there’s still more I can do, starting with the next animation element, <code>animateTransform</code>.</p>
<div class="partners__lead-place"></div>
<h2 id="animatetransform"><code>animateTransform</code></h2>
<p>If <code>&lt;animate&gt;</code> controls attributes, then <code>animateTransform</code> animates transformations, including rotations, scaling, skewing, and translations. It works by changing the values of a transform property, like this <code>translate</code>:</p>
<pre><code class="language-svg">&lt;path transform="translate(0,0)"/&gt;
</code></pre>
<p>Then, the animation works the same way as <code>&lt;animate&gt;</code>, adding an <code>attributename</code> and specifying the type of transform, in this example, <code>rotate:</code></p>
<pre><code class="language-svg">&lt;animatetransform 
  attributename="transform"
  type="rotate"&gt;
&lt;/animatetransform&gt;
</code></pre>
<p>I can use either <code>from</code> and <code>to</code> or the <code>values</code> attribute to define how an element is transformed.</p>
<ul>
<li><strong>Scale</strong> uses <code>x</code> and <code>y</code> values  (<code>.5</code>, <code>1</code>).</li>
<li><strong>Rotate</strong> uses degrees (<code>0</code>–<code>360</code>) plus optional <code>x</code> and <code>y</code> (<code>360</code>, <code>0</code>, <code>0</code>).</li>
<li><strong>Translate</strong> also uses <code>x</code> and <code>y</code> values  (<code>50</code>, <code>100</code>).</li>
<li><strong>Skew</strong> uses <code>x</code> and <code>y</code> values, too (<code>50</code>, <code>100</code>).</li>
</ul>
<p>What’s interesting about those values is that they can be added to an element’s existing values instead of replacing them. For example, when an attribute contains a <code>translate</code> value of <code>100, 0</code>:</p>
<pre><code class="language-svg">&lt;path transform="translate(100, 0)"/&gt;
</code></pre>
<p>And then I animate that translation horizontally by <code>100</code>:</p>
<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0, 0"
  to="100, 0"
  additive="sum"&gt;
&lt;/animatetransform&gt;
</code></pre>
<p>Using the <code>additive</code> property with a value of <code>sum</code>, the animation values are relative to the original, starting the animation at <code>100</code> and ending at <code>200</code> by adding <code>100</code> to <code>100</code>.</p>
<p>Similarly, if I give the <code>accumulate</code> property a value of <code>sum</code>, each instance of animation will build on the last. So, in an animation where an element is translated by <code>100</code> and repeats five times, each movement will be cumulative, moving the element by <code>500</code>:</p>
<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0, 0"
  to="100, 0"
  additive="sum"
  accumulate="sum" 
/&gt;
</code></pre>
<p>This title card from 1958’s Yogi Bear’s <a href="https://yogibear.fandom.com/wiki/Yogi_Bear%27s_Big_Break">“Big Break” episode</a> shows Yogi floating from the sky under a parachute.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration shows Yogi floating from the sky under a parachute" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/8-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>I needed two types of transform animations to generate the effect of Yogi drifting gently downwards: <code>translate</code>, and <code>rotate</code>. I first added an <code>animatetransform</code> element to the group, which contains Yogi and his chute. I defined his initial vertical position &mdash; <code>1200</code> off the top of the <code>viewBox</code> &mdash; then translated his descent to <code>1000</code> over a 15-second duration:</p>
<pre><code class="language-svg">&lt;g transform="translate(1200, -1200)"&gt;
  ...
  &lt;animateTransform
    attributeName="transform"
    type="translate"
    values="500,-1200; 500,1000"
    dur="15s"
    repeatCount="1" 
  /&gt;
&lt;/g&gt;
</code></pre>
<p>Yogi appears to fall from the sky, but the movement looks unrealistic. So, I added a second <code>animatetransform</code> element, this time with an indefinitely repeating +/- 5-degree rotation to swing Yogi from side to side during his descent:</p>
<pre><code class="language-svg">&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-5; 5; -5"
  dur="14s"
  repeatCount="indefinite"
  additive="sum" 
/&gt;
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="PwwraNm" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Big Break SVG animation [forked]](https://codepen.io/smashingmag/pen/PwwraNm) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/PwwraNm">Big Break SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="starting-and-stopping">Starting And Stopping</h2>
<p>So far, every animation begins as soon as the page has loaded. But there are ways to not only delay the start of animation but define precisely where it begins, using the begin <code>property</code>:</p>
<p>In this title card from 1959’s <a href="https://yogibear.fandom.com/wiki/Robin_Hood_Yogi">“Robin Hood Yogi”</a>, Yogi shoots an arrow into an apple on Boo-Boo’s head.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration where Yogi shoots an arrow into an apple on Boo-Boo’s head" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/9-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>By default, the arrow is set loose when the page loads. Blink, and you might miss it. To build some anticipation, I can <code>begin</code> the animation two seconds later:</p>
<pre><code class="language-svg">&lt;animatetransform
  attributename="transform"
  type="translate"
  from="0 0"
  to="750 0"
  dur=".25s"
  begin="2s"
  fill="freeze"
/&gt;
</code></pre>
<p>Or, I can let the viewer take the shot when they click the arrow:</p>
<pre><code class="language-svg">&lt;animatetransform
  ...
  begin="click"
/&gt;
</code></pre>
<p>And I can combine the click event and a delay, all with no JavaScript, just a smattering of SMIL:</p>
<pre><code class="language-svg">&lt;animatetransform
  ...
  begin="click + .5s"
/&gt;
</code></pre>
<p>Try this yourself by clicking the arrow:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="OPPeERj" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Robin Hood Yogi CSS animation [forked]](https://codepen.io/smashingmag/pen/OPPeERj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/OPPeERj">Robin Hood Yogi CSS animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="synchronising-animations">Synchronising Animations</h2>
<p>In his 1958 <a href="https://yogibear.fandom.com/wiki/Pie-Pirates">“Pie-Pirates” episode</a>, Yogi Bear tries to steal a pie and has to outwit a bulldog. The title card &mdash; designed by Lawrence Goble &mdash; shows the chase but, alas, (spoiler alert) no stolen pie.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration showing a bulldog chasing Yogi Bear" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/10-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To bring this title card to life, I needed two groups of paths: one for Yogi and the other for the dog. I translated them both off the left edge of the <code>viewBox</code>:</p>
<pre><code class="language-svg">&lt;g class="dog" transform="translate(-1000, 0)"&gt;
  ...
&lt;/g&gt;

&lt;g class="yogi" transform="translate(-1000, 0)"&gt;
  ...
&lt;/g&gt;
</code></pre>
<p>Then, I applied an <code>animatetransform</code> element to both groups, which moves them back into view:</p>
<pre><code class="language-svg">&lt;!-- yogi --&gt;
&lt;animateTransform
  attributeName="transform"
  type="translate"
  from="-1000,0"
  to="0,0"
  dur="2s"
  fill="freeze"
/&gt;

&lt;!-- dog --&gt;
&lt;animateTransform
  attributeName="transform"
  type="translate"
  from="-1000,0"
  to="0,0"
  dur=".5s"
  fill="freeze"
/&gt;
</code></pre>
<p>This sets up the action, but the effect feels flat, so I added another pair of animations that bounce both characters:</p>
<pre><code class="language-svg">&lt;!-- yogi --&gt;
&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-1,0,450; 1,0,450; -1,0,450"
  dur=".25s"
  repeatCount="indefinite"
/&gt;

&lt;!-- dog --&gt;
&lt;animateTransform
  attributeName="transform"
  type="rotate"
  values="-1,0,450; 1,0,450; -1,0,450"
  dur="0.5s"
  repeatCount="indefinite"
/&gt;
</code></pre>
<p>Animations can begin when a page loads, after a specified time, or when clicked. And by naming them, they can also synchronise with other animations.</p>
<p>I wanted Yogi to enter the frame first to build anticipation, with a short pause before other animations begin, synchronising to the moment he’s arrived. First, I added an ID to Yogi’s <code>translate</code> animation:</p>
<pre><code class="language-svg">&lt;animateTransform
  id="yogi"
  type="translate"
  ...
/&gt;
</code></pre>
<blockquote><p><strong>Watch out</strong>: For a reason, I can’t, for the life of me, explain why Firefox won’t begin animations with an ID when the ID contains a hyphen. This isn’t smarter than the average browser, but replacing hyphens with underscores fixes the problem.</p></blockquote>
<p>Then, I applied a <code>begin</code> to his <code>rotate</code> animation, which starts playing a half-second after the <code>#yogi</code> animation ends:</p>
<pre><code class="language-svg">&lt;animateTransform
  type="rotate"
  begin="yogi.end + .5s"
  ...
/&gt;
</code></pre>
<p>I can build sophisticated sets of synchronised animations using the <code>begin</code> property and whether a named animation begins or ends. The bulldog chasing Yogi enters the frame two seconds after Yogi begins his entrance:</p>
<pre><code class="language-svg">&lt;animateTransform
  id="dog"
  type="translate"
  begin="yogi.begin + 2s"
  fill="freeze"
  ...
/&gt;
</code></pre>
<p>One second after the dog has caught up with Yogi, a <code>rotate</code> transformation makes him bounce, too:</p>
<pre><code class="language-svg">&lt;animateTransform
  type="rotate"
  ...
  begin="dog.begin + 1s"
  repeatCount="indefinite" 
/&gt;
</code></pre>
<p>The background rectangles whizzing past are also synchronised, this time to one second before the bulldog ends his run:</p>
<pre><code class="language-svg">&lt;rect ...&gt;
  &lt;animateTransform
    begin="dog.end + -1s"
  /&gt;
&lt;/rect&gt;
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="LEEKryp" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Pie-Pirates SVG animation [forked]](https://codepen.io/smashingmag/pen/LEEKryp) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LEEKryp">Pie-Pirates SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>The timing of this background movement is synchronised with the dog arriving, which, in turn, is relative to Yogi’s arrival, building a sequence of animations that all feel connected.</p>
<div class="partners__lead-place"></div>
<h2 id="animating-along-motion-paths">Animating Along Motion Paths</h2>
<p>Until now, all the animations in these title cards have been up, down, left, right, or one combination or another. But there’s one more aspect of SMIL in SVG, which can add an extra dimension to animations: animating along motion paths using the <code>animatemotion</code> element.</p>
<p><code>animatemotion</code> accepts all the same properties and values as <code>animate</code> and <code>animateTransform</code>, but adds a few more for finer control over direction and timing. <code>animatemotion</code> uses the <code>path</code> property to enable elements to move along a motion path. It also uses the <code>d</code> value for coordinate data in the same way as any conventional path.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="540" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration of the Runaway Bear" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show © Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/11-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>In <a href="https://yogibear.fandom.com/wiki/The_Runaway_Bear">“The Runaway Bear”</a> from 1959, Yogi must avoid a hunter turning his head into a trophy. I wanted Yogi to leap in and out of the screen by making him follow a path. I also wanted to vary the speed of his dash: speeding up as he enters and exits, and slowing down as he passes the title text.</p>
<p>I first added a <code>path</code> property, using its coordinate data to give Yogi a route to follow, and specified a two-second duration for my animation:</p>
<pre><code class="language-svg">&lt;g&gt;
  &lt;animateMotion
    dur="2s"
    path="..."
  &gt;
  &lt;/animateMotion&gt;
&lt;/g&gt;
</code></pre>
<p>Alternatively, I could add a <code>path</code> element, leave it visible, or prevent it from being rendered by placing it inside a <code>defs</code> element:</p>
<pre><code class="language-svg">&lt;defs&gt;
  &lt;path id="yogi" d="..." /&gt;
&lt;/defs&gt;
</code></pre>
<p>I can then reference that by using a <code>mpath</code> element inside my <code>animateMotion</code>:</p>
<pre><code class="language-svg">&lt;animateMotion
  ...
  &lt;mpath href="#yogi" /&gt;
&lt;/animateMotion&gt;
</code></pre>
<p>I experimented with several paths before settling on the one that delivered the movement shape I was looking for:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Several variants of the Yogi Bear title card recreated by Andy Clarke" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Yogi Bear title card design by Lawrence Goble (1959.) Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-3-smil-not-dead/12-yogi-bear-title-card-recreations.png">Large preview</a>)<br />
    </figcaption></figure>
<p>One was too bouncy, one was too flat, but the third motion path was just right. Almost, as I also wanted to vary the speed of Yogi’s dash: speeding him up as he enters and exits and slowing him down as he passes the title text.</p>
<p>The <code>keyPoints</code> property enabled me to specify points along the motion path and then adjust the duration Yogi spends between them. To keep things simple, I defined five points between <code>0</code> and <code>1</code>:</p>
<pre><code class="language-svg">&lt;animateMotion
  ...
  keyPoints="0; .35; .5; .65; 1;"
&gt;
&lt;/animateMotion&gt;
</code></pre>
<p>Then I added the same number of <code>keyTimes</code> values, separated by semicolons, to control the pacing of this animation:</p>
<pre><code class="language-svg">&lt;animateMotion
  ...
  keyTimes="0; .1; .5; .95; 1;"
&gt;
&lt;/animateMotion&gt;
</code></pre>
<p>Now, Yogi rushes through the first three <code>keyPoints</code>, slows down as he passes the title text, then speeds up again as he exits the <code>viewBox</code>.</p>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="oggryox" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Runaway Bear SVG animation [forked]](https://codepen.io/smashingmag/pen/oggryox) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggryox">Runaway Bear SVG animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="smil-s-not-dead-baby-smil-s-not-dead">SMIL’s Not Dead, Baby. SMIL’s Not Dead</h2>
<p>With their ability to control transformations, animate complex motion paths, and synchronise multiple animations, SMIL animations in SVG are still powerful tools. They can bring design to life without needing a framework or relying on JavaScript. It’s compact, which makes it great for small SVG effects.</p>
<p>SMIL includes the <code>begin</code> attribute, which makes chaining animations far more intuitive than with CSS. Plus, SMIL lives inside the SVG file, making it perfect for animations that travel with an asset. So, while SMIL is not modern by today’s standards and may be a little bit niche, it can still be magical.</p>
<blockquote class="pull-quote">
<p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aDon%e2%80%99t%20let%20the%20misconception%20that%20SMIL%20is%20%e2%80%9cdead%e2%80%9d%20stop%20you%20from%20using%20this%20fantastic%20tool.%0a&amp;url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-3-smil-not-dead%2f"></p>
<p>Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool.</p>
<p>    </a>
  </p>
<div class="pull-quote__quotation">
<div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
</p></div>
</blockquote>
<p>Google reversed its decision to deprecate SMIL almost a decade ago, so it remains a terrific choice for designers and developers who want <strong>simple</strong>, <strong>semantic ways</strong> to add animations to their designs.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/05/21/smashing-animations-part-3-smils-not-dead-baby-smils-not-dead/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</title>
		<link>http://computercoursesonline.com/index.php/2025/05/14/smashing-animations-part-2-how-css-masking-can-add-an-extra-dimension/</link>
					<comments>http://computercoursesonline.com/index.php/2025/05/14/smashing-animations-part-2-how-css-masking-can-add-an-extra-dimension/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 14 May 2025 13:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=915</guid>

					<description><![CDATA[Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension Andy Clarke 2025-05-14T13:00:00+00:00 2025-05-15T20:32:37+00:00 Despite keyframes and scroll-driven events, CSS animations have remained relatively rudimentary. As I wrote in Part 1, they remind me of the 1960s Hanna-Barbera animated series I...]]></description>
										<content:encoded><![CDATA[<p>              <title>Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</title></p>
<article>
<header>
<h1>Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension</h1>
<address>Andy Clarke</address>
<p>                  2025-05-14T13:00:00+00:00<br />
                  2025-05-15T20:32:37+00:00<br />
                </header>
<p>Despite keyframes and scroll-driven events, CSS animations have remained relatively rudimentary. As I wrote in <a href="https://www.smashingmagazine.com/2025/05/smashing-animations-part-1-classic-cartoons-inspire-css/">Part 1</a>, they remind me of the 1960s <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> animated series I grew up watching on TV. Shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear">Yogi Bear</a>.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg"></p>
<p>    <img loading="lazy" width="800" height="640" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show poster" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/1-yogi-bear-show.jpg">Large preview</a>)<br />
    </figcaption></figure>
<p>Mike loves ’90s animation &mdash; especially <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)">Disney’s <em>Duck Tales</em></a>. So, that is the aesthetic applied throughout the design.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png"></p>
<p>    <img loading="lazy" width="800" height="550" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Design by Andy Clarke" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Design by <a href="https://stuffandnonsense.co.uk/">Andy Clarke, Stuff &amp; Nonsense</a>. Mike Worth’s website will launch in June 2025, but you can see <a href="https://codepen.io/collection/YwMKPb">examples from this article on CodePen</a>. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/2-yogi-bear-design-andy-clarke.png">Large preview</a>)<br />
    </figcaption></figure>
<p>I used animations throughout and have recently added an extra dimension to them using <strong>masking</strong>. So, to explain how this era of animation relates to masking in CSS, I’ve chosen an episode of <em>The Yogi Bear Show</em>, “Disguise and Gals,” first broadcast in May 1961. In this story, two bank robbers, disguised as little old ladies, hide their loot in a “pic-a-nic” basket in Yogi and Boo-Boo’s cave!</p>
<p>What could <em>possibly</em> go wrong?</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg"></p>
<p>    <img loading="lazy" width="800" height="640" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show, “Disguise and Gals” episode" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/3-yogi-bear-disguise-gals.jpg">Large preview</a>)<br />
    </figcaption></figure>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="what-s-a-mask">What’s A Mask?</h2>
<p>One simple masking example comes at the end of “Disguise and Gals” and countless other cartoons. Here, an animated vignette gradually hides more of Yogi’s face. The content behind the mask isn’t erased; it’s hidden.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg"></p>
<p>    <img loading="lazy" width="800" height="198" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A masking example of Yogi’s face" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/4-yogi-bear-masking-example.jpg">Large preview</a>)<br />
    </figcaption></figure>
<p>In CSS, <strong>masking controls visibility using a bitmap, vector, or gradient mask image</strong>. When a mask’s filled pixels cover an element, its content will be visible. When they are transparent, it will be hidden, which makes sense. Filled pixels can be any colour, but I always make mine hot pink so that it’s clear to me which areas will be visible.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg"></p>
<p>    <img loading="lazy" width="800" height="264" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear’s masking example by Andy Clarke" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/5-yogi-bear-andy-clarke-recreation.jpg">Large preview</a>)<br />
    </figcaption></figure>
<p>A <code>clip-path</code> functions similarly to a <code>mask</code> but uses paths to create hard-edged clipping areas. If you want to be picky, masks and clipping paths are technically different, but the goal for using them is usually the same. So, for this article, I’ll refer to them as two entrances to the same cave and call using either “masking.”</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="148" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A robber rushes the picnic basket into Yogi’s cave" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/6-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>In this sequence from “Disguise and Gals,” one of the robbers rushes the picnic basket containing their loot into Yogi’s cave. Masking defines the visible area, creating the illusion that the robber is entering the cave.</p>
<blockquote><p>How do I choose when to use <code>clip path</code> and when to choose <code>mask</code>?</p></blockquote>
<p>I’ll explain my reasons in each example.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="219" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Masking defines the visible area of the cave" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/7-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>When Mike Worth and I discussed working together, we knew we would neither have the budget nor the time to create a short animated cartoon for his website. However, we were keen to explore how animations could bring to life what would’ve otherwise been static images.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png"></p>
<p>    <img loading="lazy" width="800" height="549" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Illustration by Andy Clarke for Mike Worth’s website" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/8-mike-worth-website.png">Large preview</a>)<br />
    </figcaption></figure>
<h2 id="masking-using-a-clipping-path">Masking Using A Clipping Path</h2>
<p>On Mike’s biography page, his character also enters a cave. The SVG illustration I created contains two groups, one for the background and the other for the orangutan in the foreground:</p>
<pre><code class="language-html">&lt;figure&gt;
  &lt;svg viewBox="0 0 1400 960" id="cave"&gt;
    &lt;g class="background"&gt;…&lt;/g&gt;
    &lt;g class="foreground"&gt;…&lt;/g&gt;
  &lt;/svg&gt;
&lt;/figure&gt;
</code></pre>
<p>I defined a keyframe animation that moves the character from <code>2000px</code> on the right to its natural position in the center of the frame by altering its <code>translate</code> value:</p>
<pre><code class="language-css">@keyframes foreground {
  0% { 
    opacity: .75; 
    translate: 2000px 0;
  }
  60% { 
    opacity: 1;
    translate: 0 0;
  }
  80% {
    opacity: 1; 
    translate: 50px 0;
  }
  100% {
    opacity: 1;
    translate: 0 0;
  }
}
</code></pre>
<p>Then, I applied that animation to the foreground group:</p>
<pre><code class="language-css">.foreground {
  opacity: 0;
  animation: foreground 5s 2s ease-in-out infinite;
}
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="VYYgYRQ" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s about page (without clip-path) [forked]](https://codepen.io/smashingmag/pen/VYYgYRQ) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/VYYgYRQ">Mike Worth’s about page (without clip-path) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>While the <code>50px</code> bounce does add a touch of realism to his movement, I wasn’t happy with how the character’s entrance started at the edge of the viewport.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png"></p>
<p>    <img loading="lazy" width="800" height="549" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Character standing at the edge of the illustartion made with masking using a clipping path" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/9-masking-using-clipping-path.png">Large preview</a>)<br />
    </figcaption></figure>
<p>I wanted him to become visible at the edge of the illustration instead. As the edges of the cave walls are hard, I chose a <code>clip-path</code>.</p>
<p>There are several ways to define a <code>clip-path</code> in CSS. I could use a primitive shape like a rectangle, where each of the first four values specifies its corner positions. The <code>round</code> keyword and the value that follows define any rounded corners:</p>
<pre><code class="language-css">clip-path: rect(0px 150px 150px 0px round 5px);
</code></pre>
<p>Or <code>xywh</code> (x, y, width, height) values, which I find easier to read:</p>
<pre><code class="language-css">clip-path: xywh(0 0 150px 150px round 5px);
</code></pre>
<p>I could use a <code>circle</code>:</p>
<pre><code class="language-css">clip-path: circle(60px at center);
</code></pre>
<p>Or an <code>ellipse</code>:</p>
<pre><code class="language-css">clip-path: ellipse(50% 40% at 50% 50%);
</code></pre>
<p>I could use a <code>polygon</code> shape:</p>
<pre><code class="language-css">clip-path: polygon(...);
</code></pre>
<p>Or even the points from a path I created in a graphics app like Sketch:</p>
<pre><code class="language-css">clip-path: path("M ...");
</code></pre>
<p>Finally &mdash; and my choice for this example &mdash; I might use a mask that I defined using paths from an SVG file:</p>
<pre><code class="language-css">clip-path: url(#mask-cave);
</code></pre>
<p>To make the character visible from the edge of the illustration, I added a second SVG. To prevent a browser from displaying it, set both its dimensions to zero:</p>
<pre><code class="language-html">&lt;figure&gt;
  &lt;svg viewBox="0 0 1400 960" id="cave"&gt;...&lt;/svg&gt;
  &lt;svg height="0" width="0" id="mask"&gt;...&lt;/svg&gt;
&lt;/figure&gt;
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png"></p>
<p>    <img loading="lazy" width="800" height="549" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A single SVG clipPath" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/10-svg-clippath.png">Large preview</a>)<br />
    </figcaption></figure>
<p>This contains a single SVG <code>clipPath</code>. By placing this inside the <code>defs</code> element, this path isn’t rendered, but it will be available to create my CSS <code>clip-path</code>:</p>
<pre><code class="language-svg">&lt;svg height="0" width="0" id="mask"&gt;
  &lt;defs&gt;
    &lt;clipPath id="mask-cave"&gt;...&lt;/clipPath&gt;
  &lt;/defs&gt;
&lt;/svg&gt;
</code></pre>
<p>I applied the <code>clipPath</code> URL to my illustration, and now Mike’s mascot only becomes visible when he enters the cave:</p>
<pre><code class="language-css">#cave {
  clip-path: url(#mask-cave);
}
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="oggmgKp" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s about page (with clip-path) (without clip-path) [forked]](https://codepen.io/smashingmag/pen/oggmgKp) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggmgKp">Mike Worth’s about page (with clip-path) (without clip-path) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<blockquote><p><strong>Tip</strong>: Implement that code, and you’ll notice there’s a problem when resizing a browser window. While my cave illustration is flexible, the <code>clipPath</code> remains a fixed width.</p>
<p>To make <code>clipPath</code> responsive, add <code>clipPathUnits="objectBoundingBox"</code> to the opening tag, then apply two <code>scale</code> values to the <code>transform</code> property. To calculate these values, divide <code>1</code> first by the SVG’s <code>width</code> and then by its <code>height</code>. My SVG’s width is <code>1400px</code>, which produces the first <code>scale</code> value, <code>0.0007142857143</code>.</p>
<div class="break-out">
<pre><code class="language-svg">&lt;clipPath id="mask-cave"
  clipPathUnits="objectBoundingBox"
  transform="scale(0.0007142857143, 0.001041666667)"&gt;
  ...
&lt;/clipPath&gt;
</code></pre>
</div>
</blockquote>
<div class="partners__lead-place"></div>
<h2 id="clipping-irregular-shapes">Clipping Irregular Shapes</h2>
<p>I often need to change or alter illustrations &mdash; perhaps by overlaying colours or applying blending modes &mdash; but I want to retain their overall shape.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png"></p>
<p>    <img loading="lazy" width="800" height="181" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Altered illustrations with overlaying colours" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/11-altered-illustrations.png">Large preview</a>)<br />
    </figcaption></figure>
<p>While a <code>clipPath</code> will give me the result I’m looking for, the complexity and size of these paths can sometimes negatively affect performance. That’s when I choose a CSS <code>mask</code> as its properties have been baseline and highly usable since 2023.</p>
<p>The <code>mask</code> property is a shorthand and can include values for <code>mask-clip</code>, <code>mask-mode</code>, <code>mask-origin</code>, <code>mask-position</code>, <code>mask-repeat</code>, <code>mask-size</code>, and <code>mask-type</code>. I find it’s best to learn these properties individually to grasp the concept of masks more easily.</p>
<p>Masks control visibility using bitmap, vector, or gradient mask images. Again, when a mask’s filled pixels cover an element, its content will be visible. When they‘re transparent, the content will be hidden. And when parts of a mask are semi-transparent, some of the content will show through. I can use a bitmap format that includes an alpha channel, such as PNG or WebP:</p>
<pre><code class="language-css">mask-image: url(mask.webp);
</code></pre>
<p>I could apply a mask using a vector graphic:</p>
<pre><code class="language-css">mask-image: url(mask.svg);
</code></pre>
<p>Or generate an image using a conical, linear, or radial gradient:</p>
<pre><code class="language-css">mask-image: linear-gradient(#000, transparent); 
</code></pre>
<p>…or:</p>
<div class="break-out">
<pre><code class="language-css">mask-image: radial-gradient(circle, #ff104c 0%, transparent 100%);
</code></pre>
</div>
<p>I might apply more than one mask to an element and mix several image types using what should be a familiar syntax:</p>
<pre><code class="language-css">mask-image:
  image(url(mask.webp)),
  linear-gradient(#000, transparent);
</code></pre>
<p><code>mask</code> shares the same syntax as CSS backgrounds, which makes remembering its properties much easier. To apply a <code>background-image</code>, add its URL value:</p>
<pre><code class="language-css">background-image: url("background.webp");
</code></pre>
<p>To apply a mask, swap the <code>background-image</code> property for <code>mask-image</code>:</p>
<pre><code class="language-css">mask-image: url("mask.webp");
</code></pre>
<p>The <code>mask</code> property also shares the same browser styles as CSS backgrounds, so by default, a mask will repeat horizontally and vertically unless I specify otherwise:</p>
<div class="break-out">
<pre><code class="language-css">/&#042; Options: repeat, repeat-x, repeat-y, round, space, no-repeat &#042;/
mask-repeat: no-repeat;
</code></pre>
</div>
<p>It will be placed at the top-left corner unless I alter its position:</p>
<pre><code class="language-css">/&#042; Options: Keywords, units, percentages &#042;/
mask-position: center;
</code></pre>
<p>Plus, I can specify <code>mask-size</code> in the same way as <code>background-size</code>:</p>
<div class="break-out">
<pre><code class="language-css">/&#042; Options: Keywords (auto, contain, cover), units, percentages &#042;/
mask-size: cover;
</code></pre>
</div>
<p>Finally, I can define where a mask starts:</p>
<pre><code class="language-css">mask-origin: content-box;
mask-origin: padding-box;
mask-origin: border-box;
</code></pre>
<h2 id="using-a-mask-image">Using A Mask Image</h2>
<p>Mike’s FAQs page includes an animated illustration of his hero standing at a crossroads. My goal was to separate the shape from its content, allowing me to change the illustration throughout the hero’s journey. So, I created a scalable <code>mask-image</code> which defines the visible area and applied it to the figure element:</p>
<pre><code class="language-css">figure {
  mask-image: url(mask.svg);
}
</code></pre>
<p>To ensure the mask matched the illustration’s dimensions, I also set the <code>mask-size</code> to always <code>cover</code> its content:</p>
<pre><code class="language-css">figure {
  mask-size: cover;
}
</code></pre>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png"></p>
<p>    <img loading="lazy" width="800" height="181" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Mike Worth’s FAQs (mask-image)" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/12-mask-image.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="VYYgLed" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s FAQs (mask-image) [forked]](https://codepen.io/smashingmag/pen/VYYgLed) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/VYYgLed">Mike Worth’s FAQs (mask-image) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>Although “X“ never, ever marks the spot, Mike Worth’s review page illustration sees his orangutan mascot studying his treasure map. I wanted to focus someone’s attention on the center part of the image by using an elliptical shape.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png"></p>
<p>    <img loading="lazy" width="800" height="549" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A gorilla dressed as an explorer studies a treasure map at a desk with an elliptical shape effect" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/13-mike-worth-review-page-illustration.png">Large preview</a>)<br />
    </figcaption></figure>
<pre><code class="language-css">figure {
  clip-path: ellipse(45% 35% at 50% 50%);
}
</code></pre>
<p>However, the hard edges of a clip <code>clip-path</code> don’t create the effect I was aiming to achieve:</p>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="dPPaoXK" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s reviews page (ellipse) [forked]](https://codepen.io/smashingmag/pen/dPPaoXK) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/dPPaoXK">Mike Worth’s reviews page (ellipse) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>I experimented by combining a Gaussian blur filter with a mask defined in SVG, first by creating the filter, and then applying it to the mask’s ellipse:</p>
<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;filter id="mask-blur" x="-50%" y="-50%" width="200%" height="200%"&gt;
  &lt;feGaussianBlur in="SourceGraphic" stdDeviation="10" /&gt;&lt;/filter&gt;
&lt;/defs&gt;

&lt;defs&gt;
  &lt;mask id="mask"&gt;
    &lt;ellipse cx="700" cy="480" fill="#FFF" rx="450" ry="320" filter="url(#mask-blur)"/&gt;
  &lt;/mask&gt;
&lt;/defs&gt;
</code></pre>
</div>
<p>And, while this achieved the result I was looking for, the implementation felt over-engineered for what was, after all, a simple effect. By far, the most efficient, elegant, and responsive implementation used a <code>radial-gradient</code>, achieving the effect I was hoping for with no bitmap or vector image and just a single CSS property:</p>
<div class="break-out">
<pre><code class="language-css">figure {
  mask-image: radial-gradient(ellipse farthest-corner at center center, #000 0%, transparent 75%);
}
</code></pre>
</div>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="MYYLwjj" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s review page (radial gradient mask) [forked]](https://codepen.io/smashingmag/pen/MYYLwjj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/MYYLwjj">Mike Worth’s review page (radial gradient mask) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>This approach enables me to fine-tune my <code>mask-image</code> size and even animate its colour stops, position, and size when someone interacts with its content.</p>
<div class="partners__lead-place"></div>
<h2 id="layering-multiple-masks">Layering Multiple Masks</h2>
<p>Lighting is especially important on Mike Worth’s review page, where his orangutan hero needs it to study his treasure map. Like background images, I can apply multiple mask images to create the lighting I need.</p>
<p>I could combine two mask images: a circular, semi-transparent <code>radial-gradient</code> to provide the ambiance, plus a <code>linear-gradient</code> at a 45-degree angle for the light rays. I applied them both to the <code>figure</code> element:</p>
<div class="break-out">
<pre><code class="language-css">figure {
  mask-image:
  radial-gradient(circle, rgba(255,16,76,.5) 45%, transparent 55%),
  linear-gradient(45deg, transparent 40%, #ff104c 50%, #ff104c 50%, transparent 60%);
  mask-repeat: no-repeat;
}
</code></pre>
</div>
<p>I positioned the masks individually, set the <code>radial-gradient</code> size to <code>80%</code>, and the <code>linear-gradient</code> light rays to cover the entire image:</p>
<pre><code class="language-css">figure {
  mask-position: 50% 50%, 0 0;
  mask-size: 80%, cover;
}
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="myyvJra" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s reviews page (layering multiple masks) [forked]](https://codepen.io/smashingmag/pen/myyvJra) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/myyvJra">Mike Worth’s reviews page (layering multiple masks) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>But, I needed more precise control over the position of the light rays to create the effect that they’re coming from the desk lamp. So, I replaced the <code>linear-gradient</code> with a soft-edged bitmap image:</p>
<div class="break-out">
<pre><code class="language-css">figure {
  mask-image:
    radial-gradient(circle, rgba(255,16,76,.5) 45%, transparent 55%),
  url(mask.webp);
  mask-size: 90%, cover;
}
</code></pre>
</div>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png"></p>
<p>    <img loading="lazy" width="800" height="180" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A three-part image demonstrating the use of multiple masks in image editing." class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/14-layering-multiple-masks.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Finally, to add an extra touch of realism, I added a keyframe animation &mdash; which changes the <code>mask-size</code> and creates the effect that the lamp light is flickering &mdash; and applied it to the figure:</p>
<pre><code class="language-css">@keyframes lamp-flicker {
  0%, 19.9%, 22%, 62.9%, 64%, 64.9%, 70%, 100% { 
    mask-size: 90%, auto;
  }

  20%, 21.9%, 63%, 63.9%, 65%, 69.9% { 
    mask-size: 90%, 0px;
  }
}

figure {
  animation: lamp-flicker 3s 3s linear infinite;
}
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="yyyZNba" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s review page (multiple mask-images) [forked]](https://codepen.io/smashingmag/pen/yyyZNba) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/yyyZNba">Mike Worth’s review page (multiple mask-images) [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="animating-masks">Animating Masks</h2>
<p>Animating CSS masks creates exciting reveals and <strong>transitions between scenes</strong> and helps someone focus on critical content. It can also bring stories to life, making a person’s experience more engaging and immersive.</p>
<p>In this deleted scene from my design for his website, the orangutan adventurer mascot I created for Mike Worth can be seen driving across the landscape. To help tell the story that he’s being watched from afar by his archnemesis, I wanted to add a binocular-shaped mask.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png"></p>
<p>    <img loading="lazy" width="800" height="549" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An orangutan adventurer mascot driving across the landscape demonstrating the use of a binocular-shaped mask" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/15-binocular-shaped-mask.png">Large preview</a>)<br />
    </figcaption></figure>
<p>I started by creating the binocular shape, complete with some viewfinder markers.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png"></p>
<p>    <img loading="lazy" width="800" height="549" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A binocular shape" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/16-binocular-shape.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Then, I applied that image as a mask, setting its position, repeat, and size values to place it in the center of the figure element:</p>
<pre><code class="language-css">figure {
  mask-image: url(mask.svg);
  mask-position: 50% 50%;
  mask-repeat: no-repeat;
  mask-size: 85%;
}
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="jEEdPLq" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s journey animation with binocular-shaped mask [forked]](https://codepen.io/smashingmag/pen/jEEdPLq) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/jEEdPLq">Mike Worth’s journey animation with binocular-shaped mask [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>However, despite the infinitely scrolling background and the motion of the hero’s bumpy ride, the animation still felt static. So I added a subtle animation that shifts the <code>mask-position</code>, first by creating the keyframes:</p>
<div class="break-out">
<pre><code class="language-css">@keyframes pan-mask {
  0% { mask-position: 45% 45%; } /&#042; Start lower-left &#042;/
  25% { mask-position: 55% 55%; } /&#042; Move to upper-right &#042;/
  50% { mask-position: 43% 52%; } /&#042; Shift more dramatically &#042;/
  75% { mask-position: 57% 48%; } /&#042; More variation &#042;/
  100% { mask-position: 45% 45%; } /&#042; Loop back &#042;/
}
</code></pre>
</div>
<p>Then, I applied it, along with the scrolling background animation, to the figure element:</p>
<pre><code class="language-css">/&#042; Run both animations simultaneously &#042;/
figure {
  animation:
  background-scroll 5s linear infinite,
  pan-mask 6s ease-in-out infinite alternate;
}
</code></pre>
<p>After seeing these results, I wondered whether I could connect someone to the story by linking the <code>mask-position</code> to the movement of their cursor. I added a script that selects the <code>figure</code> element, gets the cursor position relative to the viewport, and dynamically changes the <code>mask-position</code>:</p>
<pre><code class="language-javascript">&lt;script&gt;
  // Select the figure element.
  const figure = document.querySelector('figure');
  document.addEventListener('mousemove', (event) =&gt; {
  
  // Get the cursor position.
  const mouseX = event.clientX;
  const mouseY = event.clientY;
  
  // Normalise the mask-position.
  const maskX = (mouseX / window.innerWidth) &#042; 100;
  const maskY = (mouseY / window.innerHeight) &#042; 100;`
  
  // Dynamically set the mask-position.
  figure.style.maskPosition =${maskX}% ${maskY}%;
  });
&lt;/script&gt;
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="oggmXeM" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s journey animation with binoculars following the cursor [forked]](https://codepen.io/smashingmag/pen/oggmXeM) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/oggmXeM">Mike Worth’s journey animation with binoculars following the cursor [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>With that, there was only one challenge left to complete the effect. Focusing binoculars on a distant target is rarely easy, and when the hero’s archnemesis has hands the size of a silverback gorilla, the task is even more challenging. I extended my script to blur the visible content seen through the binocular-shaped mask and then removed the filter when someone presses their keyboard’s spacebar or presses a mouse button:</p>
<pre><code class="language-javascript">&lt;script&gt;
  // When mouse button pressed, remove blur
  document.addEventListener('mousedown', () =&gt; {
    figure.style.filter = 'blur(0)';
  });
  
  // When mouse button released, reapply blur
  document.addEventListener('mouseup', () =&gt; {
    figure.style.filter = 'blur(5px)';
  });
  
  // When spacebar pressed, remove blur
  document.addEventListener('keydown', (event) =&gt; {
    if (event.key === ' ') {
      figure.style.filter = 'blur(0)';
    }
  });

  // When spacebar released, reapply blur
  document.addEventListener('keyup', (event) =&gt; {
    if (event.key === ' ') {
      figure.style.filter = 'blur(5px)';
    }
  });
&lt;/script&gt;
</code></pre>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="MYYLwEJ" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s journey animation with focussing blur [forked]](https://codepen.io/smashingmag/pen/MYYLwEJ) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/MYYLwEJ">Mike Worth’s journey animation with focussing blur [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="more-masking-animation">More Masking Animation</h2>
<p>When someone using Mike Worth’s website follows the wrong path or takes a wrong turn, he ends up sinking into hot lava.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png"></p>
<p>    <img loading="lazy" width="800" height="182" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A cartoon gorilla wearing a fedora and leather jacket is shown inside a circular cutout at the center of a brown background demonstrating a masking effect" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-2-css-masking-add-extra-dimension/17-masking-animation.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To let someone know they might’ve reached the end of their adventure, I wanted to ape the zooming-in effect I started this article with:</p>
<pre><code class="language-html">&lt;figure&gt;
  &lt;svg&gt;…&lt;/svg&gt;
&lt;/figure&gt;
</code></pre>
<p>I created a circular <code>clip-path</code> and set its default size to <code>75%</code>. Then, I defined the animation keyframes to resize the circle from 75% to 15% before attaching it to my figure with a one-second duration and a three-second delay:</p>
<pre><code class="language-css">@keyframes error {
  0% { clip-path: circle(75%); }
  100% { clip-path: circle(15%); }
}

figure {
  clip-path: circle(75%);
  animation: error 1s 3s ease-in forwards;
}
</code></pre>
<p>The animation now focuses someone’s attention on the hapless hero, before he sinks lower and lower into the bubblingly hot lava.</p>
<p>Try this yourself:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="qEEgdxy" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s error page [forked]](https://codepen.io/smashingmag/pen/qEEgdxy) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/qEEgdxy">Mike Worth’s error page [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="bringing-it-all-to-life">Bringing It All To Life</h2>
<p>Masking adds an <strong>extra dimension to web animation</strong> and makes stories more engaging and someone’s experience more compelling &mdash; all while keeping animations efficiently lightweight. Whether you’re revealing content, guiding focus, or adding more depth to a design, masks offer endless creative possibilities. So why not experiment with them in your next project? You might uncover a whole new way to bring your animations to life.</p>
<p>The end. Or is it? …</p>
<p>Mike Worth’s website will launch in June 2025, but you can <a href="https://codepen.io/collection/YwMKPb">see examples from this article on CodePen</a> now.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/05/14/smashing-animations-part-2-how-css-masking-can-add-an-extra-dimension/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</title>
		<link>http://computercoursesonline.com/index.php/2025/05/07/smashing-animations-part-1-how-classic-cartoons-inspire-modern-css/</link>
					<comments>http://computercoursesonline.com/index.php/2025/05/07/smashing-animations-part-1-how-classic-cartoons-inspire-modern-css/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 07 May 2025 08:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=866</guid>

					<description><![CDATA[Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS Andy Clarke 2025-05-07T08:00:00+00:00 2025-05-08T20:32:35+00:00 Browser makers didn’t take long to add the movement capabilities to CSS. The simple :hover pseudo-class came first, and a bit later, the transitions between two states. Then came the ability...]]></description>
										<content:encoded><![CDATA[<p>              <title>Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</title></p>
<article>
<header>
<h1>Smashing Animations Part 1: How Classic Cartoons Inspire Modern CSS</h1>
<address>Andy Clarke</address>
<p>                  2025-05-07T08:00:00+00:00<br />
                  2025-05-08T20:32:35+00:00<br />
                </header>
<p>Browser makers didn’t take long to add the movement capabilities to CSS. The simple <code>:hover</code> pseudo-class came first, and a bit later, the <code>transition</code>s between two states. Then came the ability to change states across a set of <code>@keyframes</code> and, most recently, scroll-driven animations that link keyframes to the scroll position.</p>
<p>Even with these added capabilities, <strong>CSS animations have remained relatively rudimentary</strong>. They remind me of the <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> animated series I grew up watching on TV.</p>
<p>These animated shorts lacked the budgets given to live-action or animated movies. They were also far lower than those available when William Hanna and Joseph Barbera made Tom and Jerry shorts while working for MGM Cartoons. This meant the animators needed to develop techniques to work around their cost restrictions and the technical limitations of the time.</p>
<p>They used fewer frames per second and far fewer cells. Instead of using a different image for each frame, they repeated each one several times. They reused cells as frequently as possible by zooming and overlaying additional elements to construct a new scene. They kept bodies mainly static and overlayed eyes, mouths, and legs to create the illusion of talking and walking. Instead of reducing the quality of these cartoons, these constraints created a charm often lacking in more recent, bigger-budget, and technically advanced productions.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show illustration" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/3-yogi-bear-show.png">Large preview</a>)<br />
    </figcaption></figure>
<p>The simple and efficient techniques developed by Hanna-Barbera’s animators can be implemented using CSS. Modern layout tools allow web developers to layer elements. Scaleable Vector Graphics (SVG) can contain several frames, and developers needn’t resort to JavaScript; they can use CSS to change an element’s <code>opacity</code>, <code>position</code>, and <code>visibility</code>. But what are some reasons for doing this?</p>
<p>Animations bring static experiences to life. They can <strong>improve usability</strong> by guiding people’s actions and delighting or surprising them when interacting with a design. When carefully considered, animations can reinforce branding and help tell stories about a brand.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png"></p>
<p>    <img loading="lazy" width="800" height="550" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Design by Andy Clarke of Mike Worth’s website" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Design by Andy Clarke, Stuff &amp; Nonsense. Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/4-andy-clarke-design.png">Large preview</a>)<br />
    </figcaption></figure>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="introducing-mike-worth">Introducing Mike Worth</h2>
<p>I’ve recently been working on a new website for Emmy-award-winning game composer Mike Worth. He hired me to create a bold, retro-style design that showcases his work. I used CSS animations throughout to delight and surprise his audience as they move through his website.</p>
<p>Mike loves ’80s and ’90s animation &mdash; especially Disney’s <a href="https://en.wikipedia.org/wiki/DuckTales_(1987_TV_series)"><em>Duck Tales</em></a>. Unsurprisingly, my taste in cartoons stretches back a little further to the 1960s <a href="https://en.wikipedia.org/wiki/Hanna-Barbera">Hanna-Barbera</a> shows like Dastardly and Muttley in <em>Their Flying Machines</em>, <em>Scooby-Doo</em>, <em>The Perils of Penelope Pitstop</em>, <em>Wacky Races</em>, and, of course, <a href="https://en.wikipedia.org/wiki/Yogi_Bear">Yogi Bear</a>.</p>
<p>So, to explain how this era of animation relates to CSS, I’ve chosen an episode of <em>The Yogi Bear Show</em>, “<a href="https://www.youtube.com/watch?v=CPnmzcmKgA0">Home Sweet Jellystone</a>,” first broadcast in 1961. In this story, Ranger Smith inherits a mansion and (spoiler alert) leaves Jellystone.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show. An illustration from “Home Sweet Jellystone episode”" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/5-yogi-bear-show-home-sweet-jellystone.png">Large preview</a>)<br />
    </figcaption></figure>
<h2 id="dissecting-movement">Dissecting Movement</h2>
<p>In this episode, Hanna-Barbera’s techniques become apparent as soon as a postman arrives with a telegram for Ranger Smith. The camera pans sideways across a landscape painting by background artist Robert Gentle to create the illusion that the postman is moving.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show, author’s recreation of dissecting movement" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (Author’s recreation) (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/6-yogi-bear-show-author-recreation.png">Large preview</a>)<br />
    </figcaption></figure>
<p>The background loops when a scene lasts longer than a single pan of Robert Gentle’s landscape painting, with bushes and trees appearing repeatedly.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png"></p>
<p>    <img loading="lazy" width="800" height="80" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A closer look at the landscape painting, with bushes and trees appearing repeatedly" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/7-yogi-bear-dissecting-movement.png">Large preview</a>)<br />
    </figcaption></figure>
<p>This can be recreated using a single element and an animation that changes the position of its background image:</p>
<pre><code class="language-css">@keyframes background-scroll {
  0% { background-position: 2750px 0; }
  100% { background-position: 0 0; }
}

div {
  overflow: hidden;
  width: 100vw;
  height: 540px;
  background-image: url("…");
  background-size: 2750px 540px;
  background-repeat: repeat-x;
  animation: background-scroll 5s linear infinite;
}
</code></pre>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="NPPzgyq" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Yogi Bear background image scroll [forked]](https://codepen.io/smashingmag/pen/NPPzgyq) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/NPPzgyq">Yogi Bear background image scroll [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>Although beautifully executed, Robert Gentle’s background paintings were often remarkably simple. The mansion’s interior background rushes past to create the illusion of Ranger Smith dashing across it, so it needed very few details.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png"></p>
<p>    <img loading="lazy" width="800" height="314" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The mansion’s interior background" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/8-robert-gentle-background-painting.png">Large preview</a>)<br />
    </figcaption></figure>
<p><strong>The economy of movement</strong> was essential for producing these animated shorts cheaply and efficiently. The postman’s motorcycle bounces, and only his head position and facial expressions change, which adds a subtle hint of realism.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png"></p>
<p>    <img loading="lazy" width="800" height="131" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An example of several drawings where only head position and facial expressions change to add movement on a motorcycle" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Sequence shortened. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/9-yogi-bear-sequence-shortened.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Likewise, only Ranger Smith’s facial expression and leg positions change throughout his walk cycle as he dashes through his mansion. The rest of his body stays static.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png"></p>
<p>    <img loading="lazy" width="800" height="131" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An example of several drawings where only Ranger Smith’s facial expression and leg positions change during a walk" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Sequence shortened. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/10-yogi-bear-sequence-shortened.png">Large preview</a>)<br />
    </figcaption></figure>
<p>In a discarded scene from my design for his website, the orangutan adventurer mascot I created for Mike Worth can be seen driving across the landscape.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png"></p>
<p>    <img loading="lazy" width="800" height="400" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="An illustration from Mike Worth’s website" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/11-mike-worth-website.png">Large preview</a>)<br />
    </figcaption></figure>
<p>I drew directly from <strong>Hanna-Barbera’s bouncing and scrolling technique</strong> for this scene by using two keyframe animations: <code>background-scroll</code> and <code>bumpy-ride</code>. The infinitely scrolling background works just like before:</p>
<pre><code class="language-css">@keyframes background-scroll {
  0% { background-position: 960px 0; }
  100% { background-position: 0 0; }
}
</code></pre>
<p>I created the appearance of his bumpy ride by animating changes to the keyframes’ <code>translate</code> values:</p>
<pre><code class="language-css">@keyframes bumpy-ride {
  0% { translate: 0 0; }
  10% { translate: 0 -5px; }
  20% { translate: 0 3px; }
  30% { translate: 0 -3px; }
  40% { translate: 0 5px; }
  50% { translate: 0 -10px; }
  60% { translate: 0 4px; }
  70% { translate: 0 -2px; }
  80% { translate: 0 7px; }
  90% { translate: 0 -4px; }
  100% { translate: 0 0; }
}

figure {
  /&#042; ... &#042;/
  animation: background-scroll 5s linear infinite;
}

img {
  /&#042; ... &#042;/
  animation: bumpy-ride 1.5s infinite ease-in-out;
}
</code></pre>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="ByyVZrB" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth background image scroll [forked]](https://codepen.io/smashingmag/pen/ByyVZrB) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ByyVZrB">Mike Worth background image scroll [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>As Michelle Barker wrote about <a href="https://www.smashingmagazine.com/2021/10/respecting-users-motion-preferences/">here at Smashing Magazine</a> back in 2021:</p>
<blockquote><p>“When working with motion on the web, it’s important to consider that not everyone experiences it the same way. What might feel smooth and slick to some might be annoying or distracting to others &mdash; or worse, induce feelings of sickness or even cause seizures.”</p>
<p>&mdash; Michelle Barker</p></blockquote>
<p>You can prevent that from happening by turning off animations when someone has chosen reduced motion in their browser by using the <code>prefers-reduced-motion</code> media query:</p>
<pre><code class="language-css">@media (prefers-reduced-motion: reduce) {
  &#042; { animation: none !important; }
}
</code></pre>
<div class="partners__lead-place"></div>
<h2 id="reusing-elements">Reusing Elements</h2>
<p>Since each episode’s budget and production time were limited, William Hanna and Joseph Barbera created a streamlined process for producing their animations. They used as few as 2,000 individual drawings and just a few background paintings per episode, often reusing them on several episodes.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png"></p>
<p>    <img loading="lazy" width="800" height="322" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="A background painting with highlighted trees from the Yogi Bear show" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/12-yogi-bear-background-painting.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Watch the episode and you’ll see these trees appear over and over again throughout “Home Sweet Jellystone.” Behind Yogi and Boo-Boo on the track, in the bushes, and scaled up in this close-up of Boo-Boo:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png"></p>
<p>    <img loading="lazy" width="800" height="196" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show paintings with the same trees" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/1-yogi-bear-show-reusing-elements.png">Large preview</a>)<br />
    </figcaption></figure>
<p>The animators also frequently <strong>layered foreground elements onto these background paintings</strong> to create a variety of new scenes:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png"></p>
<p>    <img loading="lazy" width="800" height="293" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The Yogi Bear Show paintings with layered foreground elements" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      On the left: The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. On the right: Author’s edit.(<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/2-layered-foreground-elements.png">Large preview</a>)<br />
    </figcaption></figure>
<p>In my deleted scene from Mike Worth’s website, I introduced these rocks into the foreground to add depth to the animation:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png"></p>
<p>    <img loading="lazy" width="800" height="400" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Painting from Mike Worth’s website with rocks on the background" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/13-layered-foreground-elements.png">Large preview</a>)<br />
    </figcaption></figure>
<p>If I were using bitmap images, this would require just one additional image:</p>
<pre><code class="language-html">&lt;figure&gt;
  &lt;img id="bumpy-ride" src="..." alt="" /&gt;
  &lt;img id="apes-rock" src="..." alt="" /&gt;
&lt;/figure&gt;
</code></pre>
<pre><code class="language-css">figure {
  position: relative; 

  #bumpy-ride { ... }

  #apes-rock {
    position: absolute;
    width: 960px;
    left: calc(50% - 480px);
    bottom: 0;
  }
}
</code></pre>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="xbbzraj" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth layered animation [forked]](https://codepen.io/smashingmag/pen/xbbzraj) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/xbbzraj">Mike Worth layered animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>Had I continued developing this scene, I might’ve added a slower scrolling animation to those rocks to introduce a parallax effect for even greater realism.</p>
<h2 id="looping-frames-create-movement">Looping Frames Create Movement</h2>
<p>To meet their limited budget and production schedules, Hanna Barbera’s animators carefully planned their animations and cleverly <strong>only animated specific elements</strong>. While heads and facial expressions make characters talk and their legs change to make them walk, most characters’ bodies remain relatively static. So, throughout this entire scene of Ranger Smith walking and talking his way across his cabin, only his face and legs are animated:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png"></p>
<p>    <img loading="lazy" width="800" height="157" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Looping Frames of Ranger Smith walking, where only his face and legs are animated" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/14-looping-frames.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Likewise, when the ranger reads his telegram, only his eyes and mouth move:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png"></p>
<p>    <img loading="lazy" width="800" height="400" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Looping Frames of Ranger Smith reading a telegram" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/15-looping-frames-movement.png">Large preview</a>)<br />
    </figcaption></figure>
<p>If you’ve wondered why both Ranger Smith and Yogi Bear wear collars and neckties, it’s so the line between their animated heads and faces and static bodies is obscured:</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png"></p>
<p>    <img loading="lazy" width="800" height="600" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear wearing a collar" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. Author’s recreation. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/16-yogi-bear.png">Large preview</a>)<br />
    </figcaption></figure>
<blockquote class="pull-quote">
<p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aSVG%20delivers%20incredible%20performance%20and%20also%20offers%20fantastic%20flexibility%20when%20animating%20elements.%20The%20ability%20to%20embed%20one%20SVG%20inside%20another%20and%20to%20manipulate%20groups%20and%20other%20elements%20using%20CSS%20makes%20it%20ideal%20for%20animations.%0a&amp;url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-1-classic-cartoons-inspire-css%2f"></p>
<p>SVG delivers incredible performance and also offers fantastic flexibility when animating elements. The ability to embed one SVG inside another and to manipulate groups and other elements using CSS makes it ideal for animations.</p>
<p>    </a>
  </p>
<div class="pull-quote__quotation">
<div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
</p></div>
</blockquote>
<p>I replicated how Hanna-Barbera made Ranger Smith and other characters’ mouths move by first including a group that contains the ranger’s body and head, which remain static throughout. Then, I added six more groups, each containing one frame of his mouth moving:</p>
<pre><code class="language-svg">&lt;svg&gt;
  &lt;!-- static elements --&gt;
  &lt;g&gt;...&lt;/g&gt;

  &lt;!-- animation frames --&gt;
  &lt;g class="frame-1"&gt;...&lt;/g&gt;
  &lt;g class="frame-2"&gt;...&lt;/g&gt;
  &lt;g class="frame-3"&gt;...&lt;/g&gt;
  &lt;g class="frame-4"&gt;...&lt;/g&gt;
  &lt;g class="frame-5"&gt;...&lt;/g&gt;
  &lt;g class="frame-6"&gt;...&lt;/g&gt;
&lt;/svg&gt;
</code></pre>
<p>I used CSS custom properties to define the speed at which characters’ mouths move and how many frames are in the animation:</p>
<pre><code class="language-css">:root {
  --animation-duration: 1s;
  --frame-count: 6;
}
</code></pre>
<p>Then, I applied a keyframe animation to show and hide each frame:</p>
<pre><code class="language-css">@keyframes ranger-talking {
  0% { visibility: visible; }
  16.67% { visibility: hidden; }
  100% { visibility: hidden; }
}

[class&#042;="frame"] {
  visibility: hidden;
  animation: ranger-talking var(--animation-duration) infinite;
}
</code></pre>
<p>Before finally setting a delay, which makes each frame visible at the correct time:</p>
<div class="break-out">
<pre><code class="language-css">.frame-1 {
  animation-delay: calc(var(--animation-duration) &#042; 0 / var(--frame-count));
}

/&#042; ... &#042;/

.frame-6 {
  animation-delay: calc(var(--animation-duration) &#042; 5 / var(--frame-count));
}
</code></pre>
</div>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="QwwxgJE" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Ranger Smith talking [forked]](https://codepen.io/smashingmag/pen/QwwxgJE) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/QwwxgJE">Ranger Smith talking [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<div class="partners__lead-place"></div>
<h2 id="working-with-mike-worth">Working With Mike Worth</h2>
<p>When Mike Worth and I sat down to discuss working together, we both understood that there would be neither the budget nor the time to create a short animated cartoon for his website. We also knew that video would be the correct format for a fully animated production, but we were keen to explore how CSS could bring what would’ve otherwise been static images to life. So, this begs the question of <em>why</em> and <em>when</em> to use CSS animations.</p>
<h3 id="ambient-animations">Ambient Animations</h3>
<blockquote><p>Subtle ambient animations contribute to a website’s atmosphere and help with storytelling without distracting from its content or functionality.</p></blockquote>
<p>For Mike Worth’s about-page illustration, I shone shafts of light onto a stone tablet to add depth to an otherwise flat image. Inside my SVG, I added a <code>path</code> for the light and reduced its <code>opacity</code> to <code>.25</code>:</p>
<div class="break-out">
<pre><code class="language-svg">&lt;svg&gt;
  &lt;!-- ... --&gt;
  &lt;path class="light-shaft" fill="#F1DCA9" opacity=".25" d=""/&gt;
&lt;/svg&gt;
</code></pre>
</div>
<p>I then defined an SVG filter to blur the edges of my light shafts and linked it to my <code>path</code>:</p>
<div class="break-out">
<pre><code class="language-svg">&lt;defs&gt;
  &lt;filter id="light-shaft" width="100%" height="100%" x="0" y="0" filterUnits="objectBoundingBox"&gt;
  &lt;feGaussianBlur in="SourceGraphic" stdDeviation="20"/&gt;
  &lt;/filter&gt;
&lt;/defs&gt;

&lt;svg&gt;
  &lt;!-- ... --&gt;
  &lt;path class="light-shaft" filter="url(#light-shaft)" … /&gt;
&lt;/svg&gt;
</code></pre>
</div>
<p>Finally, I added a subtle ambient animation that rotates the light shafts and creates a more natural feel:</p>
<pre><code class="language-css">@keyframes shaft-rotate {
  0% { rotate: 2deg; }
  50% { rotate: -2deg; }
  100% { rotate: 2deg; }
}

.light-shaft {
  animation: shaft-rotate 20s infinite;
  transform-origin: 100% 0;
}
</code></pre>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="bNNKROE" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s about page animation [forked]](https://codepen.io/smashingmag/pen/bNNKROE) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNNKROE">Mike Worth’s about page animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<p>Can you throw more light on Mike’s navigation?</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="ZYYRyVJ" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Light up Mike Worth about page [forked]](https://codepen.io/smashingmag/pen/ZYYRyVJ) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ZYYRyVJ">Light up Mike Worth about page [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h3 id="animations-on-interactions">Animations On Interactions</h3>
<blockquote><p>In the same way that a <code>:hover</code> pseudo-class provides valuable visual feedback when someone interacts with an element, CSS animations can create a deeper connection between people and a design.</p></blockquote>
<p>I included an Easter egg interaction on Mike Worth’s review page illustration. The big red button turns the desk lamp on and off, much to the consternation of Mike’s orangutan mascot, who’s trying to study his map. To implement this, I applied a <code>data-</code> attribute to the SVG illustration:</p>
<pre><code class="language-svg">&lt;svg … data-lights="lights-on"&gt;
  &lt;!-- ... --&gt;
&lt;/svg&gt;
</code></pre>
<p>And added a red button for any curious visitor to press:</p>
<div class="break-out">
<pre><code class="language-html">&lt;a href="javascript:void(0);" id="light-switch" title="Lights on/off"&gt;
  &lt;path fill="#0a0908" d="..."/&gt;
  &lt;ellipse fill="#9c1621" … /&gt;
  &lt;path fill="#fff" d="..."/&gt;
&lt;/a&gt;
</code></pre>
</div>
<p>When someone presses that red button, the light goes out, which is made possible by changing the value of the SVG’s <code>data-</code> attribute from <code>lights-on</code> to <code>lights-off</code>.</p>
<p>Several elements within the illustration light up when the desk lamp is on. To make this happen, I applied a class value to those specific items:</p>
<pre><code class="language-svg">&lt;path class="lamp-glow" /&gt;
</code></pre>
<p>And used the data-attribute value to toggle the glow on and off when someone presses the lamp’s button:</p>
<pre><code class="language-css">[data-lights="lights-on"] .lamp-glow {
  opacity: 1;
  transition: opacity .25s linear;
}

[data-lights="lights-off"] .lamp-glow {
  opacity: .25;
  transition: opacity .25s linear;
}
</code></pre>
<p>When someone turns the lamp on, it flickers at what appears to be random intervals. I first applied a class value to the flickering elements:</p>
<pre><code class="language-svg">&lt;path class="lamp-flicker" /&gt;
</code></pre>
<p>Then, I hid them when the lamp was turned off:</p>
<pre><code class="language-css">[data-lights="lights-off"] .lamp-flicker {
  visibility: hidden;
}
</code></pre>
<p>Finally, I created a keyframe animation that flickers the lamp light’s <code>opacity</code> at seemingly random intervals:</p>
<div class="break-out">
<pre><code class="language-css">@keyframes lamp-flicker {
  0%, 19.9%, 22%, 62.9%, 64%, 64.9%, 70%, 100% { opacity: 1; }
  20%, 21.9%, 63%, 63.9%, 65%, 69.9% { opacity: .5; }
}

[data-lights="lights-on"] .lamp-flicker {
  animation: lamp-flicker 3s 3s linear infinite;
}
</code></pre>
</div>
<p>Animations can also tempt people to venture deeper into a design, so I made the crystal skull on the desk vibrate to hint at something more to discover:</p>
<pre><code class="language-html">&lt;a href="/easter-egg"&gt;
  &lt;g id="crystal-skull"&gt;...&lt;/g&gt;
&lt;/a&gt;
</code></pre>
<div class="break-out">
<pre><code class="language-css">@keyframes crystal-skull-vibrate {
  0% { translate: 0 0; }
  20% { translate: -2px 2px; }
  40% { translate: -2px -2px; }
  60% { translate: 2px 2px; }
  80% { translate: 2px -2px; }
  100% { translate: 0 0; }
}

&#035;crystal-skull:hover {
  animation: crystal-skull-vibrate .5s ease 0s infinite normal forwards;
}
</code></pre>
</div>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="YPPvQBg" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s review page animation [forked]](https://codepen.io/smashingmag/pen/YPPvQBg) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/YPPvQBg">Mike Worth’s review page animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h3 id="animations-tell-stories">Animations Tell Stories</h3>
<blockquote><p>When carefully considered, animations can reflect a brand’s identity and help tell its story.</p></blockquote>
<p>Mike Worth’s brand is high-energy &mdash; just like his personality &mdash; and the story he tells about his work as a video game composer is engaging and playful. Mike wanted every interaction with his website to bring his personality to the screen.</p>
<p>Should someone get lost along their journey, they’ll end up on Mike’s 404 page, where his hero has a sinking feeling. While Mike’s orangutan adventurer slips deeper and deeper into the quicksand, animated bubbles rise:</p>
<pre><code class="language-svg">&lt;g&gt;
  &lt;circle class="four-oh-dear-bubble" ... /&gt;
  &lt;circle class="four-oh-dear-bubble" ... /&gt;
  &lt;circle class="four-oh-dear-bubble" ... /&gt;
  &lt;!-- ... --&gt;
&lt;/g&gt;
</code></pre>
<div class="break-out">
<pre><code class="language-css">@keyframes four-oh-dear-bubbles {
  0% { 
    animation-timing-function: ease-in; 
    opacity: 1; 
    transform: translateY(45px);
  }
  24% { 
    opacity: 1; 
  }
  40% { 
    animation-timing-function: ease-in; 
    translate: 0 24px;
  }
  65% { 
    animation-timing-function: ease-in;
    translate: 0 12px;
  }
  82% { 
    animation-timing-function: ease-in;
    translate: 0 6px;
  }
  93% { 
    animation-timing-function: ease-in;
    translate: 0 4px;
  }
  25%, 55%, 75%, 87% { 
    animation-timing-function: ease-out;
    translate: 0 0;
  }
  100% { 
    animation-timing-function: ease-out;
    opacity: 1; 
    translate: 0 0;
  }
}

.four-oh-dear-bubble {
  animation: four-oh-dear-bubbles 2s ease 0s infinite alternate forwards; }
</code></pre>
</div>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="ZYYRyPX" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Mike Worth’s 404 page animation [forked]](https://codepen.io/smashingmag/pen/ZYYRyPX) by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ZYYRyPX">Mike Worth’s 404 page animation [forked]</a> by <a href="https://codepen.io/malarkey">Andy Clarke</a>.</figcaption></figure>
<h2 id="bringing-it-all-to-life">Bringing It All To Life</h2>
<p>Just as the animators at Hanna-Barbera turned technical limitations into their signature style, CSS animations enable web professionals to craft characterful experiences. By layering elements, looping frames, and applying subtle movement, you can inject personality into a design while improving someone’s experience.</p>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Yogi Bear’s painting" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The Yogi Bear Show, copyright Warner Bros. Entertainment Inc. (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/17-yogi-bear.png">Large preview</a>)<br />
    </figcaption></figure>
<p>In my design for Mike Worth’s website, <strong>animation isn’t just for decoration; it tells a compelling story about him and his work</strong>. Every movement reflects his brand identity and makes his website an extension of his creative world.</p>
<blockquote class="pull-quote">
<p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aThink%20beyond%20movement%20the%20next%20time%20you%20reach%20for%20a%20CSS%20animation.%20Consider%20emotions,%20identity,%20and%20mood,%20too.%20After%20all,%20a%20well-considered%20animation%20can%20do%20more%20than%20catch%20someone%e2%80%99s%20eye.%20It%20can%20capture%20their%20imagination.%0a&amp;url=https://smashingmagazine.com%2f2025%2f05%2fsmashing-animations-part-1-classic-cartoons-inspire-css%2f"></p>
<p>Think beyond movement the next time you reach for a CSS animation. Consider emotions, identity, and mood, too. After all, a well-considered animation can do more than catch someone’s eye. It can capture their imagination.</p>
<p>    </a>
  </p>
<div class="pull-quote__quotation">
<div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
</p></div>
</blockquote>
<figure class="
  
    break-out article__image
  
  
  "></p>
<p>    <a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png"></p>
<p>    <img loading="lazy" width="800" height="450" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="The end painting" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      (<a href="https://files.smashing.media/articles/smashing-animations-part-1-classic-cartoons-inspire-css/18-the-end-painting.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Mike Worth’s website will launch in June 2025, but you can <a href="https://codepen.io/collection/YwMKPb">see examples from this article on CodePen</a> now.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/05/07/smashing-animations-part-1-how-classic-cartoons-inspire-modern-css/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module?</title>
		<link>http://computercoursesonline.com/index.php/2025/05/06/masonry-in-css-should-grid-evolve-or-stand-aside-for-a-new-module/</link>
					<comments>http://computercoursesonline.com/index.php/2025/05/06/masonry-in-css-should-grid-evolve-or-stand-aside-for-a-new-module/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Tue, 06 May 2025 13:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=868</guid>

					<description><![CDATA[Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module? Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module? Gabriel Shoyombo 2025-05-06T13:00:00+00:00 2025-05-08T20:32:35+00:00 You’ve got a Pinterest-style layout to build, but you’re tired of JavaScript. Could CSS finally have the answer? Well, for a beginner, taking a look at the pins on your Pinterest page, you...]]></description>
										<content:encoded><![CDATA[<p>              <title>Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module?</title></p>
<article>
<header>
<h1>Masonry In CSS: Should Grid Evolve Or Stand Aside For A New Module?</h1>
<address>Gabriel Shoyombo</address>
<p>                  2025-05-06T13:00:00+00:00<br />
                  2025-05-08T20:32:35+00:00<br />
                </header>
<p>You’ve got a Pinterest-style layout to build, but you’re tired of JavaScript. Could CSS finally have the answer? Well, for a beginner, taking a look at the pins on your Pinterest page, you might be convinced that the CSS grid layout is enough, but not until you begin to build do you realise <code>display: grid</code> with additional tweaks is less than enough. In fact, Pinterest built its layout with JavaScript, but how cool would it be if it were just CSS? If there were a CSS display property that gave such a layout without any additional JavaScript, how awesome would that be?</p>
<p>Maybe there is. The CSS grid layout has an <strong>experimental masonry value</strong> for <code>grid-template-rows</code>. The masonry layout is an irregular, flowing grid. Irregular in the sense that, instead of following a rigid grid pattern with spaces left after shorter pieces, the items in the next row of a masonry layout rise to fill the spaces on the masonry axis. It’s the dream for portfolios, image galleries, and social feeds &mdash; designs that thrive on organic flow. But here’s the catch: while this experimental feature exists (think Firefox Nightly with a flag enabled), it’s not the seamless solution you might expect, thanks to limited browser support and some rough edges in its current form.</p>
<p>Maybe there isn’t. CSS lacks native masonry support, forcing developers to use hacks or JavaScript libraries like <a href="https://masonry.desandro.com">Masonry.js</a>. Developers with a good design background have expressed their criticism about the CSS grid form of masonry, with <a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/">Rachel</a> highlighting that <strong>masonry’s organic flow contrasts with Grid’s strict two-dimensional structure</strong>, potentially confusing developers expecting Grid-like behaviour or <a href="https://ishadeed.com/article/should-masonry-be-part-of-css-grid/">Ahmad Shadeed</a> fussing about how it makes the grid layout more complex than it should be, potentially overwhelming developers who value Grid’s clarity for structured layouts. <a href="https://css-tricks.com/css-masonry-css-grid/">Geoff</a> also echoes Rachel Andrew’s concern that <em>“teaching and learning grid to get to understand masonry behaviour unnecessarily lumps two different formatting contexts into one,”</em> complicating education for designers and developers who rely on clear mental models.</p>
<p>Perhaps there might be hope. The Apple WebKit team just sprung up a new contender, which claims not only to merge the pros of grid and masonry into a unified system shorthand but also includes flexbox concepts. Imagine the best of three CSS layout systems in one.</p>
<p>Given these complaints and criticisms &mdash; and a new guy in the game &mdash; the question is:</p>
<blockquote><p>Should CSS Grid expand to handle Masonry, or should a new, dedicated module take over, or should <code>item-flow</code> just take the reins?</p></blockquote>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="the-state-of-masonry-in-css-today">The State Of Masonry In CSS Today</h2>
<p>Several developers have attempted to create workarounds to achieve a masonry layout in their web applications using CSS Grid with manual row-span hacks, CSS Columns, and JavaScript libraries. Without native masonry, developers often turn to Grid hacks like this: a <code>grid-auto-rows</code> trick paired with JavaScript to fake the flow. It works &mdash; sort of &mdash; but the cracks show fast.</p>
<p>For instance, the example below relies on JavaScript to measure each item’s height after rendering, calculate the number of 10px rows (plus gaps) the item should span while setting <code>grid-row-end</code> dynamically, and use event listeners to adjust the layout upon page load and window resize.</p>
<div class="break-out">
<pre><code class="language-html">/&#042; HTML &#042;/
&lt;div class="masonry-grid"&gt;
  &lt;div class="masonry-item"&gt;&lt;img src="image1.jpg" alt="Image 1"&gt;&lt;/div&gt;
  &lt;div class="masonry-item"&gt;&lt;p&gt;Short text content here.&lt;/p&gt;&lt;/div&gt;
  &lt;div class="masonry-item"&gt;&lt;img src="image2.jpg" alt="Image 2"&gt;&lt;/div&gt;
  &lt;div class="masonry-item"&gt;&lt;p&gt;Longer text content that spans multiple lines to show height variation.&lt;/p&gt;&lt;/div&gt;
&lt;/div&gt;
</code></pre>
</div>
<div class="break-out">
<pre><code class="language-css">/&#042; CSS &#042;/
.masonry-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); /&#042; Responsive columns &#042;/
  grid-auto-rows: 10px; /&#042; Small row height for precise spanning &#042;/
  grid-auto-flow: column; /&#042; Fills columns left-to-right &#042;/
  gap: 10px; /&#042; Spacing between items &#042;/
}

.masonry-item {
  /&#042; Ensure content doesn’t overflow &#042;/
  overflow: hidden;
}

.masonry-item img {
  width: 100%;
  height: auto;
  display: block;
}

.masonry-item p {
  margin: 0;
  padding: 10px;
}
</code></pre>
</div>
<div class="break-out">
<pre><code class="language-javascript">// JavaScript

function applyMasonry() {
  const grid = document.querySelector('.masonry-grid');
  const items = grid.querySelectorAll('.masonry-item');

  items.forEach(item =&gt; {
    // Reset any previous spans
    item.style.gridRowEnd = 'auto';

    // Calculate the number of rows to span based on item height
    const rowHeight = 10; 
    const gap = 10; 
    const itemHeight = item.getBoundingClientRect().height;
    const rowSpan = Math.ceil((itemHeight + gap) / (rowHeight + gap));

    // Apply the span
    item.style.gridRowEnd = `span ${rowSpan}`;
  });
}

// Run on load and resize
window.addEventListener('load', applyMasonry);
window.addEventListener('resize', applyMasonry);
</code></pre>
</div>
<p>This Grid hack gets us close to a masonry layout &mdash; items stack, gaps fill, and it looks decent enough. But let’s be real: it’s not there yet. The code sample above, unlike native <code>grid-template-rows: masonry</code> (which is experimental and only exists on Firefox Nightly), relies on JavaScript to calculate spans, defeating the “no JavaScript” dream. The JavaScript logic works by recalculating spans on resize or content change. As <a href="https://css-tricks.com/native-css-masonry-layout-in-css-grid/">Chris Coyier</a> noted in his critique of similar hacks, this can lead to lag on complex pages.</p>
<p>Also, the logical DOM order might not match the visual flow, a concern <a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/">Rachel Andrew</a> raised about masonry layouts generally. Finally, if images load slowly or content shifts (e.g., lazy-loaded media), the spans need recalculation, risking layout jumps. It’s not really the ideal hack; I’m sure you’d agree.</p>
<blockquote class="pull-quote">
<p>
    <a class="pull-quote__link" aria-label="Share on Twitter" href="https://twitter.com/share?text=%0aDevelopers%20need%20a%20smooth%20experience,%20and%20ergonomically%20speaking,%20hacking%20Grid%20with%20scripts%20is%20a%20mental%20juggling%20act.%20It%20forces%20you%20to%20switch%20between%20CSS%20and%20JavaScript%20to%20tweak%20a%20layout.%20A%20native%20solution,%20whether%20Grid-powered%20or%20a%20new%20module,%20has%20to%20nail%20effortless%20responsiveness,%20neat%20rendering,%20and%20a%20workflow%20that%20does%20not%20make%20you%20break%20your%20tools.%0a&amp;url=https://smashingmagazine.com%2f2025%2f05%2fmasonry-css-should-grid-evolve-stand-aside-new-module%2f"></p>
<p>Developers need a smooth experience, and ergonomically speaking, hacking Grid with scripts is a mental juggling act. It forces you to switch between CSS and JavaScript to tweak a layout. A native solution, whether Grid-powered or a new module, has to nail effortless responsiveness, neat rendering, and a workflow that does not make you break your tools.</p>
<p>    </a>
  </p>
<div class="pull-quote__quotation">
<div class="pull-quote__bg">
      <span class="pull-quote__symbol">“</span></div>
</p></div>
</blockquote>
<p>That’s why this debate matters &mdash; our daily grind demands it.</p>
<div class="partners__lead-place"></div>
<h2 id="option-1-extending-css-grid-for-masonry">Option 1: Extending CSS Grid For Masonry</h2>
<p>One way forward is to strengthen the CSS Grid with masonry powers. As of this writing, CSS grids have been extended to accommodate masonry. <code>grid-template-rows: masonry</code> is a draft of CSS Grid Level 3 that is currently experimental in Firefox Nightly. The columns of this layout will remain as a grid axis while the row takes on masonry. The child elements are then laid out item by item along the rows, as with the grid layout’s automatic placement. With this layout, items flow vertically, respecting column tracks but not row constraints.</p>
<p>This option leaves Grid as your go-to layout system but allows it to handle the flowing, gap-filling stacks we crave.</p>
<div class="break-out">
<pre><code class="language-css">.masonry-grid {
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-template-rows: masonry;
}
</code></pre>
</div>
<p>First off, the grid-masonry style builds on CSS Grid’s familiarity and robust tooling (e.g., DevTools support). As a front-end developer, there’s a chance you’ve played with <code>grid-template-columns</code> or <code>grid-area</code>, so you’re halfway up the learning matrix. Masonry only extends the existing capabilities, eliminating the need to learn a whole new syntax from scratch. Also, Grid’s robust tooling comes along with Chrome DevTools’ grid overlay or Firefox’s layout inspector, removing the need for JavaScript hacks.</p>
<p>Not so fast: there are limitations. Grid’s specifications already include properties like <code>align-content</code> and <code>grid-auto-flow</code>. Stacking masonry on the list risks turning it into a labyrinth.</p>
<p>Then there are <strong>the edge cases</strong>. What happens when you want an item to span multiple columns and flow masonry-style? Or when gaps between items don’t align across columns? The specs are still foggy here, and early tests hint at bugs like items jumping unpredictably if content loads dynamically. This issue could break layouts, especially on responsive designs. <strong>The browser compatibility issue</strong> also exists. It’s still experimental, and even with polyfills, it does not work on other browsers except Firefox Nightly. Not something you’d want to try in your next client’s project, right?</p>
<h2 id="option-2-a-standalone-masonry-module">Option 2: A Standalone Masonry Module</h2>
<p>What if we had a <code>display: masonry</code> approach instead? Indulge me for a few minutes. This isn’t just wishful thinking. Early CSS Working Group chats have floated the idea, and it’s worth picturing how it could improve layouts. Let’s dive into the vision, <em>how</em> it might work, and <em>what</em> it gains or loses in the process.</p>
<p>Imagine a layout system that doesn’t lean on Grid’s rigid tracks or Flexbox’s linear flow but instead thrives on vertical stacking with a horizontal twist. The goal? A clean slate for masonry’s signature look: items cascading down columns, filling gaps naturally, no hacks required. Inspired by murmurs in CSSWG discussions and the Chrome team’s alternative proposal, this module would <strong>prioritise fluidity over structure</strong>, giving designers a tool that feels as intuitive as the layouts they’re chasing. Think Pinterest but without JavaScript scaffolding.</p>
<p>Here’s the pitch: a <code>display</code> value named <code>masonry</code> kicks off a flow-based system where items stack vertically by default, adjusting horizontally to fit the container. You’d control the direction and spacing with simple properties like the following:</p>
<pre><code class="language-css">.masonry {
  display: masonry;
  masonry-direction: column;
  gap: 1rem;
}
</code></pre>
<p>Want more control? Hypothetical extras like <code>masonry-columns: auto</code> could mimic Grid’s <code>repeat(auto-fill, minmax())</code>, while <code>masonry-align: balance</code> might even out column lengths for a polished look. It’s less about precise placement (Grid’s strength) and more about letting content breathe and flow, adapting to whatever screen size is thrown at it. The big win here is a clean break from Grid’s rigid order. A standalone module keeps them distinct: <strong>Grid for order, Masonry for flow</strong>. No more wrestling with Grid properties that don’t quite fit; you get a system tailored to the job.</p>
<p>Of course, it’s not all smooth sailing. A brand-new spec means starting from zero. Browser vendors would need to rally behind it, which can be slow. Also, it might lead to <strong>confusion of choice</strong>, with developers asking questions like: <em>“Do I use Grid or Masonry for this gallery?”</em> But hear me out: This proposed module might muddy the waters before it clears them, but after the water is clear, it’s safe for use by all and sundry.</p>
<h2 id="item-flow-a-unified-layout-resolution">Item Flow: A Unified Layout Resolution</h2>
<p>In March 2025, <a href="https://webkit.org/blog/16587/item-flow-part-1-a-new-unified-concept-for-layout/">Apple’s WebKit team proposed Item Flow</a>, a new system that unifies concepts from Flexbox, Grid, and masonry into a single set of properties. Rather than choosing between enhancing Grid or creating a new masonry module, Item Flow merges their strengths, replacing <code>flex-flow</code> and <code>grid-auto-flow</code> with a shorthand called <code>item-flow</code>. This system introduces four longhand properties:</p>
<ul>
<li><code>item-direction</code><br />
Controls flow direction (e.g., <code>row</code>, <code>column</code>, <code>row-reverse</code>).</li>
<li><code>item-wrap</code><br />
Manages wrapping behaviour (e.g., <code>wrap</code>, <code>nowrap</code>, <code>wrap-reverse</code>).</li>
<li><code>item-pack</code><br />
Determines packing density (e.g., <code>sparse</code>, <code>dense</code>, <code>balance</code>).</li>
<li><code>item-slack</code><br />
Adjusts tolerance for layout adjustments, allowing items to shrink or shift to fit.</li>
</ul>
<p>Item Flow aims to make masonry a natural outcome of these properties, not a separate feature. For example, a masonry layout could be achieved with:</p>
<pre><code class="language-css">.container {
  display: grid; /&#042; or flex &#042;/
  item-flow: column wrap dense;

  /&#042; long hand version &#042;/
  item-direction: column;
  item-wrap: wrap;
  item-pack: dense;

  gap: 1rem;
}
</code></pre>
<p>This setup allows items to flow vertically, wrap into columns, and pack tightly, mimicking masonry’s organic arrangement. The dense packing option, inspired by Grid’s <code>auto-flow: dense</code>, reorders items to minimise gaps, while <code>item-slack</code> could fine-tune spacing for visual balance.</p>
<p>Item Flow’s promise lies in its <strong>wide use case</strong>. It enhances Grid and Flexbox with features like <code>nowrap</code> for Grid or <code>balance</code> packing for Flexbox, addressing long-standing developer wishlists. However, the proposal is still in discussion, and <a href="https://grok.com/chat/caeffa67-49d2-478e-834d-611d9a7bf204">properties like <code>item-slack</code> face naming debates due to clarity issues for non-native English speakers</a>.</p>
<p>The downside? Item Flow is a <strong>future-facing concept</strong>, and it has not yet been implemented in browsers as of April 2025. Developers must wait for standardisation and adoption, and the CSS Working Group is still gathering feedback.</p>
<div class="partners__lead-place"></div>
<h2 id="what-s-the-right-path">What’s The Right Path?</h2>
<p>While there is no direct answer to that question, the masonry debate hinges on balancing simplicity, performance, and flexibility. Extending the Grid with masonry is tempting but risks <strong>overcomplicating</strong> an already robust system. A standalone <code>display: masonry</code> module offers clarity but <strong>adds to CSS’s learning curve.</strong> Item Flow, the newest contender, proposes a unified system that could make masonry a natural extension of Grid and Flexbox, potentially putting the debate to rest at last.</p>
<p>Each approach has trade-offs:</p>
<ul>
<li><strong>Grid with Masonry</strong>: Familiar but potentially clunky, with accessibility and spec concerns.</li>
<li><strong>New Module</strong>: Clean and purpose-built, but requires learning new syntax.</li>
<li><strong>Item Flow</strong>: Elegant and versatile but not yet available, with ongoing debates over naming and implementation.</li>
</ul>
<p>Item Flow’s ability to enhance existing layouts while supporting masonry makes it a compelling option, but its success depends on browser adoption and community support.</p>
<h2 id="conclusion">Conclusion</h2>
<p>So, where do we land after all this? The masonry showdown boils down to three paths: the extension of masonry into CSS Grid, a standalone module for masonry, or Item Flow. Now, the question is, <strong>will CSS finally free us from JavaScript for masonry</strong>, or are we still dreaming?</p>
<p>Grid’s teasing us with a taste, and a standalone module’s whispering promises &mdash; but the finish line’s unclear, and WebKit swoops in with a killer merge shorthand, Item Flow. Browser buy-in, community push, and a few more spec revisions might tell us. For now, it’s your move &mdash; test, tweak, and weigh in. The answer’s coming, one layout at a time.</p>
<h3 id="references">References</h3>
<ul>
<li>“<a href="https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/">Native CSS Masonry Layout in CSS Grid</a>” by Rachel Andrew</li>
<li>“<a href="https://ishadeed.com/article/css-grid-masonry/">Should Masonry be part of CSS Grid?</a>” by Ahmad Shadeed</li>
<li>“<a href="https://css-tricks.com/css-masonry-css-grid/">CSS Masonry &amp; CSS Grid</a>” by Geoff Graham</li>
<li>“<a href="https://css-irl.info/masonry-in-css/">Masonry? In CSS?!</a>” by Michelle Barker</li>
<li>“<a href="https://css-tricks.com/native-css-masonry-layout-in-css-grid/">Native CSS Masonry Layout in CSS Grids</a>” by Chris Coyier</li>
<li>“<a href="https://webkit.org/blog/16587/item-flow-part-1-a-new-unified-concept-for-layout/">Item Flow Part 1: A Unified Concept for Layout</a>” by WebKit</li>
</ul>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/05/06/masonry-in-css-should-grid-evolve-or-stand-aside-for-a-new-module/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Transitioning Top-Layer Entries And The Display Property In CSS</title>
		<link>http://computercoursesonline.com/index.php/2025/01/29/transitioning-top-layer-entries-and-the-display-property-in-css/</link>
					<comments>http://computercoursesonline.com/index.php/2025/01/29/transitioning-top-layer-entries-and-the-display-property-in-css/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 29 Jan 2025 10:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=256</guid>

					<description><![CDATA[Transitioning Top-Layer Entries And The Display Property In CSS Transitioning Top-Layer Entries And The Display Property In CSS Brecht De Ruyte 2025-01-29T10:00:00+00:00 2025-05-01T20:32:48+00:00 Animating from and to display: none; was something we could only achieve with JavaScript to change classes or create other hacks. The reason why we couldn’t do this in CSS is explained...]]></description>
										<content:encoded><![CDATA[<p>              <title>Transitioning Top-Layer Entries And The Display Property In CSS</title></p>
<article>
<header>
<h1>Transitioning Top-Layer Entries And The Display Property In CSS</h1>
<address>Brecht De Ruyte</address>
<p>                  2025-01-29T10:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>Animating from and to <code>display: none;</code> was something we could only achieve with JavaScript to change classes or create other hacks. The reason why we couldn’t do this in CSS is explained in the new <a href="https://www.w3.org/TR/css-transitions-2/#defining-before-change-style">CSS Transitions Level 2 specification</a>:</p>
<blockquote><p>“In Level 1 of this specification, transitions can only start during a style change event for elements that have a defined before-change style established by the previous style change event. That means a transition could not be started on an element that was not being rendered for the previous style change event.”</p></blockquote>
<p>In simple terms, this means that we couldn’t start a transition on an element that is hidden or that has just been created.</p>
<h3 id="what-does-transition-behavior-allow-discrete-do">What Does <code>transition-behavior: allow-discrete</code> Do?</h3>
<p><code>allow-discrete</code> is a bit of a strange name for a CSS property value, right? We are going on about transitioning <code>display: none</code>, so why isn’t this named <code>transition-behavior: allow-display</code> instead? The reason is that this does a bit more than handling the CSS <code>display</code> property, as there are other “discrete” properties in CSS. A simple rule of thumb is that discrete properties do not transition but usually flip right away between two states. Other examples of discrete properties are <code>visibility</code> and <code>mix-blend-mode</code>. I’ll include an example of these at the end of this article.</p>
<p>To summarise, setting the <code>transition-behavior</code> property to <code>allow-discrete</code> allows us to tell the browser it can swap the values of a discrete property (e.g., <code>display</code>, <code>visibility</code>, and <code>mix-blend-mode</code>) at the 50% mark instead of the 0% mark of a transition.</p>
<h3 id="what-does-starting-style-do">What Does <code>@starting-style</code> Do?</h3>
<p>The <code>@starting-style</code> rule defines the styles of an element right before it is rendered to the page. This is highly needed in combination with <code>transition-behavior</code> and this is why:</p>
<p>When an item is added to the DOM or is initially set to <code>display: none</code>, it needs some sort of “starting style” from which it needs to transition. To take the example further, <a href="https://css-tricks.com/clarifying-the-relationship-between-popovers-and-dialogs/">popovers and dialog elements</a> are added to a top layer which is a layer that is outside of your document flow, you can kind of look at it as a sibling of the <code>&lt;html&gt;</code> element in your page’s structure. Now, when opening this dialog or popover, they get created inside that top layer, so they don’t have any styles to start transitioning from, which is why we set <code>@starting-style</code>. Don’t worry if all of this sounds a bit confusing. The demos might make it more clearly. The important thing to know is that we can give the browser something to start the animation with since it otherwise has nothing to animate from.</p>
<h3 id="a-note-on-browser-support">A Note On Browser Support</h3>
<p>At the moment of writing, the <code>transition-behavior</code> is available in Chrome, Edge, Safari, and Firefox. It’s the same for <code>@starting-style</code>, but Firefox currently does not support animating from <code>display: none</code>. But remember that everything in this article can be perfectly used as a progressive enhancement.</p>
<p>Now that we have the theory of all this behind us, let’s get practical. I’ll be covering three use cases in this article:</p>
<ul>
<li>Animating from and to <code>display: none</code> in the DOM.</li>
<li>Animating dialogs and popovers entering and exiting the top layer.</li>
<li>More “discrete properties” we can handle.
<p></li>
</ul>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="animating-from-and-to-display-none-in-the-dom">Animating From And To <code>display: none</code> In The DOM</h2>
<p>For the first example, let’s take a look at <code>@starting-style</code> alone. I created this demo purely to explain the magic. Imagine you want two buttons on a page to add or remove list items inside of an unordered list.</p>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>This could be your starting HTML:</p>
<pre><code class="language-html">&lt;button type="button" class="btn-add"&gt;
  Add item
&lt;/button&gt;
&lt;button type="button" class="btn-remove"&gt;
  Remove item
&lt;/button&gt;
&lt;ul role="list"&gt;&lt;/ul&gt;
</code></pre>
<p>Next, we add actions that add or remove those list items. This can be any method of your choosing, but for demo purposes, I quickly wrote a bit of JavaScript for it:</p>
<pre><code class="language-javascript">document.addEventListener("DOMContentLoaded", () =&gt; {
  const addButton = document.querySelector(".btn-add");
  const removeButton = document.querySelector(".btn-remove");
  const list = document.querySelector('ul[role="list"]');

  addButton.addEventListener("click", () =&gt; {
    const newItem = document.createElement("li");
    list.appendChild(newItem);
  });

  removeButton.addEventListener("click", () =&gt; {
    if (list.lastElementChild) {
      list.lastElementChild.classList.add("removing");
      setTimeout(() =&gt; {
        list.removeChild(list.lastElementChild);
      }, 200);
    }
  });
});
</code></pre>
<p>When clicking the <code>addButton</code>, an empty list item gets created inside of the unordered list. When clicking the <code>removeButton</code>, the last item gets a new <code>.removing</code> class and finally gets taken out of the DOM after 200ms.</p>
<p>With this in place, we can write some CSS for our items to animate the removing part:</p>
<pre><code class="language-css">ul {
    li {
      transition: opacity 0.2s, transform 0.2s;

      &amp;.removing {
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>This is great! Our <code>.removing</code> animation is already looking perfect, but what we were looking for here was a way to animate the entry of items coming inside of our DOM. For this, we will need to define those starting styles, as well as the final state of our list items.</p>
<p>First, let’s update the CSS to have the final state inside of that list item:</p>
<pre><code class="language-css">ul {
    li {
      opacity: 1;
      transform: translate(0, 0);
      transition: opacity 0.2s, transform 0.2s;

      &amp;.removing {
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>
<p>Not much has changed, but now it’s up to us to let the browser know what the starting styles should be. We could set this the same way we did the <code>.removing</code> styles like so:</p>
<pre><code class="language-css">ul {
    li {
      opacity: 1;
      transform: translate(0, 0);
      transition: opacity 0.2s, transform 0.2s;

      @starting-style {
        opacity: 0;
        transform: translate(0, 50%);
      }

      &amp;.removing {
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>
<p>Now we’ve let the browser know that the <code>@starting-style</code> should include zero opacity and be slightly nudged to the bottom using a <code>transform</code>. The final result is something like this:</p>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>But we don’t need to stop there! We could use different animations for entering and exiting. We could, for example, update our starting style to the following:</p>
<pre><code class="language-css">@starting-style {
  opacity: 0;
  transform: translate(0, -50%);
}
</code></pre>
<p>Doing this, the items will enter from the top and exit to the bottom. See the full example in this CodePen:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="XJroPgg" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [@starting-style demo &#8211; up-in, down-out [forked]](https://codepen.io/smashingmag/pen/XJroPgg) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/XJroPgg">@starting-style demo &#8211; up-in, down-out [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption></figure>
<h3 id="when-to-use-transition-behavior-allow-discrete">When To Use <code>transition-behavior: allow-discrete</code></h3>
<p>In the previous example, we added and removed items from our DOM. In the next demo, we will show and hide items using the CSS <code>display</code> property. The basic setup is pretty much the same, except we will add eight list items to our DOM with the <code>.hidden</code> class attached to it:</p>
<pre><code class="language-html">  &lt;button type="button" class="btn-add"&gt;
    Show item
  &lt;/button&gt;
  &lt;button type="button" class="btn-remove"&gt;
    Hide item
  &lt;/button&gt;

&lt;ul role="list"&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
  &lt;li class="hidden"&gt;&lt;/li&gt;
&lt;/ul&gt;
</code></pre>
<p>Once again, for demo purposes, I added a bit of JavaScript that, this time, removes the <code>.hidden</code> class of the next item when clicking the <code>addButton</code> and adds the <code>hidden</code> class back when clicking the <code>removeButton</code>:</p>
<div class="break-out">
<pre><code class="language-javascript">document.addEventListener("DOMContentLoaded", () =&gt; {
  const addButton = document.querySelector(".btn-add");
  const removeButton = document.querySelector(".btn-remove");
  const listItems = document.querySelectorAll('ul[role="list"] li');

  let activeCount = 0;

  addButton.addEventListener("click", () =&gt; {
    if (activeCount &lt; listItems.length) {
      listItems[activeCount].classList.remove("hidden");
      activeCount++;
    }
  });

  removeButton.addEventListener("click", () =&gt; {
    if (activeCount &gt; 0) {
      activeCount--;
      listItems[activeCount].classList.add("hidden");
    }
  });
});
</code></pre>
</div>
<p>Let’s put together everything we learned so far, add a <code>@starting-style</code> to our items, and do the basic setup in CSS:</p>
<pre><code class="language-css">ul {
    li {
      display: block;
      opacity: 1;
      transform: translate(0, 0);
      transition: opacity 0.2s, transform 0.2s;

      @starting-style {
        opacity: 0;
        transform: translate(0, -50%);
      }

      &amp;.hidden {
        display: none;
        opacity: 0;
        transform: translate(0, 50%);
      }
    }
  }
</code></pre>
<p>This time, we have added the <code>.hidden</code> class, set it to <code>display: none</code>, and added the same <code>opacity</code> and <code>transform</code> declarations as we previously did with the <code>.removing</code> class in the last example. As you might expect, we get a nice fade-in for our items, but removing them is still very abrupt as we set our items directly to <code>display: none</code>.</p>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>This is where the <code>transition-behavior</code> property comes into play. To break it down a bit more, let’s remove the <code>transition</code> property shorthand of our previous CSS and open it up a bit:</p>
<pre><code class="language-css">ul {
    li {
      display: block;
      opacity: 1;
      transform: translate(0, 0);
      transition-property: opacity, transform;
      transition-duration: 0.2s;
    }
  }
</code></pre>
<p>All that is left to do is transition the <code>display</code> property and set the <code>transition-behavior</code> property to <code>allow-discrete</code>:</p>
<pre><code class="language-css">ul {
    li {
      display: block;
      opacity: 1;
      transform: translate(0, 0);
      transition-property: opacity, transform, display;
      transition-duration: 0.2s;
      transition-behavior: allow-discrete;
      /&#042; etc. &#042;/
    }
  }
</code></pre>
<p>We are now animating the element from <code>display: none</code>, and the result is exactly as we wanted it:</p>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>We can use the <code>transition</code> shorthand property to make our code a little less verbose:</p>
<div class="break-out">
<pre><code class="language-css">transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete;
</code></pre>
</div>
<p>You can add <code>allow-discrete</code> in there. But if you do, take note that if you declare a shorthand transition after <code>transition-behavior</code>, it will be overruled. So, instead of this:</p>
<pre><code class="language-css">transition-behavior: allow-discrete;
transition: opacity 0.2s, transform 0.2s, display 0.2s;
</code></pre>
<p>…we want to declare <code>transition-behavior</code> <em>after</em> the <code>transition</code> shorthand:</p>
<pre><code class="language-css">transition: opacity 0.2s, transform 0.2s, display 0.2s;
transition-behavior: allow-discrete;
</code></pre>
<p>Otherwise, the <code>transition</code> shorthand property overrides <code>transition-behavior</code>.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="GgKPXda" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [@starting-style and transition-behavior: allow-discrete [forked]](https://codepen.io/smashingmag/pen/GgKPXda) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/GgKPXda">@starting-style and transition-behavior: allow-discrete [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption></figure>
<div class="partners__lead-place"></div>
<h2 id="animating-dialogs-and-popovers-entering-and-exiting-the-top-layer">Animating Dialogs And Popovers Entering And Exiting The Top Layer</h2>
<p>Let’s add a few use cases with dialogs and popovers. Dialogs and popovers are good examples because they get added to the top layer when opening them.</p>
<h3 id="what-is-that-top-layer">What Is That Top Layer?</h3>
<p>We’ve already likened the “top layer” to a sibling of the <code>&lt;html&gt;</code> element, but you might also think of it as a special layer that sits above everything else on a web page. It&rsquo;s like a transparent sheet that you can place over a drawing. Anything you draw on that sheet will be visible on top of the original drawing.</p>
<p>The original drawing, in this example, is the DOM. This means that the top layer is out of the document flow, which provides us with a few benefits. For example, as I stated before, dialogs and popovers are added to this top layer, and that makes perfect sense because they should always be on top of everything else. No more <code>z-index: 9999</code>!</p>
<p>But it’s more than that:</p>
<ul>
<li><strong><code>z-index</code> is irrelevant</strong>: Elements on the top layer are always on top, regardless of their <code>z-index</code> value.</li>
<li><strong>DOM hierarchy doesn’t matter</strong>: An element’s position in the DOM doesn’t affect its stacking order on the top layer.</li>
<li><strong>Backdrops</strong>: We get access to a new <code>::backdrop</code> pseudo-element that lets us style the area between the top layer and the DOM beneath it.</li>
</ul>
<p>Hopefully, you are starting to understand the importance of the top layer and how we can transition elements in and out of it as we would with popovers and dialogues.</p>
<h3 id="transitioning-the-dialog-element-in-the-top-layer">Transitioning The Dialog Element In The Top Layer</h3>
<p>The following HTML contains a button that opens a <code>&lt;dialog&gt;</code> element, and that <code>&lt;dialog&gt;</code> element contains another button that closes the <code>&lt;dialog&gt;</code>. So, we have one button that opens the <code>&lt;dialog&gt;</code> and one that closes it.</p>
<div class="break-out">
<pre><code class="language-html">&lt;button class="open-dialog" data-target="my-modal"&gt;Show dialog&lt;/button&gt;

&lt;dialog id="my-modal"&gt;
  &lt;p&gt;Hi, there!&lt;/p&gt;
  &lt;button class="outline close-dialog" data-target="my-modal"&gt;
    close
  &lt;/button&gt;
&lt;/dialog&gt;
</code></pre>
</div>
<p>A lot is happening in HTML with <a href="https://utilitybend.com/blog/an-update-on-invokers-invoker-commands-in-html">invoker commands</a> that will make the following step a bit easier, but for now, let’s add a bit of JavaScript to make this modal actually work:</p>
<pre><code class="language-javascript">// Get all open dialog buttons.
const openButtons = document.querySelectorAll(".open-dialog");
// Get all close dialog buttons.
const closeButtons = document.querySelectorAll(".close-dialog");

// Add click event listeners to open buttons.
openButtons.forEach((button) =&lt; {
  button.addEventListener("click", () =&lt; {
    const targetId = button.getAttribute("data-target");
    const dialog = document.getElementById(targetId);
    if (dialog) {
      dialog.showModal();
    }
  });
});

// Add click event listeners to close buttons.
closeButtons.forEach((button) =&lt; {
  button.addEventListener("click", () =&lt; {
    const targetId = button.getAttribute("data-target");
    const dialog = document.getElementById(targetId);
    if (dialog) {
      dialog.close();
    }
  });
});
</code></pre>
<p>I’m using the following styles as a starting point. Notice how I’m styling the <code>::backdrop</code> as an added bonus!</p>
<pre><code class="language-css">dialog {
  padding: 30px;
  width: 100%;
  max-width: 600px;
  background: #fff;
  border-radius: 8px;
  border: 0;
  box-shadow: 
    rgba(0, 0, 0, 0.3) 0px 19px 38px,
    rgba(0, 0, 0, 0.22) 0px 15px 12px;
    
  &amp;::backdrop {
    background-image: linear-gradient(
      45deg in oklab,
      oklch(80% 0.4 222) 0%,
      oklch(35% 0.5 313) 100%
    );
  }
}
</code></pre>
<p>This results in a pretty hard transition for the entry, meaning it’s not very smooth:</p>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>Let’s add transitions to this dialog element and the backdrop. I’m going a bit faster this time because by now, you likely see the pattern and know what’s happening:</p>
<pre><code class="language-css">dialog {
  opacity: 0;
  translate: 0 30%;
  transition-property: opacity, translate, display;
  transition-duration: 0.8s;

  transition-behavior: allow-discrete;
  
  &amp;[open] {
    opacity: 1;
    translate: 0 0;

    @starting-style {
      opacity: 0;
      translate: 0 -30%;
    }
  }
}
</code></pre>
<p>When a dialog is open, the browser slaps an <code>open</code> attribute on it:</p>
<pre><code class="language-html">&lt;dialog open&gt; ... &lt;/dialog&gt;
</code></pre>
<p>And that’s something else we can target with CSS, like <code>dialog[open]</code>. So, in this case, we need to set a <code>@starting-style</code> for when the dialog is in an <code>open</code> state.</p>
<p>Let’s add a transition for our backdrop while we’re at it:</p>
<pre><code class="language-css">dialog {
  /&#042; etc. &#042;/
  &amp;::backdrop {
    opacity: 0;
    transition-property: opacity;
    transition-duration: 1s;
  }

  &amp;[open] {
    /&#042; etc. &#042;/
    &amp;::backdrop {
      opacity: 0.8;

      @starting-style {
        opacity: 0;
      }
    }
  }
}
</code></pre>
<figure class="video-embed-container">
<div class="video-embed-container--wrapper"></div>
</figure>
<p>Now you’re probably thinking: <em>A-ha! But you should have added the <code>display</code> property and the <code>transition-behavior: allow-discrete</code> on the backdrop!</em></p>
<p>But no, that is not the case. Even if I would change my backdrop pseudo-element to the following CSS, the result would stay the same:</p>
<pre><code class="language-css"> &amp;::backdrop {
    opacity: 0;
    transition-property: opacity, display;
    transition-duration: 1s;
    transition-behavior: allow-discrete;
  }
</code></pre>
<p>It turns out that we are working with a <code>::backdrop</code> and when working with a <code>::backdrop</code>, we’re implicitly also working with the CSS <code>overlay</code> property, which specifies whether an element appearing in the top layer is currently rendered in the top layer.</p>
<p>And <code>overlay</code> just so happens to be another discrete property that we need to include in the <code>transition-property</code> declaration:</p>
<pre><code class="language-css">dialog {
  /&#042; etc. &#042;/

&amp;::backdrop {
  transition-property: opacity, display, overlay;
  /&#042; etc. &#042;/
}
</code></pre>
<p>Unfortunately, this is currently only supported in Chromium browsers, but it can be perfectly used as a progressive enhancement.</p>
<p>And, yes, we need to add it to the <code>dialog</code> styles as well:</p>
<pre><code class="language-css">dialog {
  transition-property: opacity, translate, display, overlay;
  /&#042; etc. &#042;/

&amp;::backdrop {
  transition-property: opacity, display, overlay;
  /&#042; etc. &#042;/
}
</code></pre>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="pvzqOGe" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Dialog: starting-style, transition-behavior, overlay [forked]](https://codepen.io/smashingmag/pen/pvzqOGe) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvzqOGe">Dialog: starting-style, transition-behavior, overlay [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption></figure>
<p>It’s pretty much the same thing for a popover instead of a dialog. I’m using the same technique, only working with popovers this time:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="emObLxe" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Popover transition with @starting-style [forked]](https://codepen.io/smashingmag/pen/emObLxe) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/emObLxe">Popover transition with @starting-style [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption></figure>
<div class="partners__lead-place"></div>
<h2 id="other-discrete-properties">Other Discrete Properties</h2>
<p>There are a few other discrete properties besides the ones we covered here. If you remember the second demo, where we transitioned some items from and to <code>display: none</code>, the same can be achieved with the <code>visibility</code> property instead. This can be handy for those cases where you want items to preserve space for the element’s box, even though it is invisible.</p>
<p>So, here’s the same example, only using <code>visibility</code> instead of <code>display</code>.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="LEPMJqX" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Transitioning the visibility property [forked]](https://codepen.io/smashingmag/pen/LEPMJqX) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LEPMJqX">Transitioning the visibility property [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption></figure>
<p>The CSS <strong><code>mix-blend-mode</code></strong> property is another one that is considered discrete. To be completely honest, I can’t find a good use case for a demo. But I went ahead and created a somewhat trite example where two <code>mix-blend-mode</code>s switch right in the middle of the transition instead of right away.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="bNbOxZp" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Transitioning mix-blend-mode [forked]](https://codepen.io/smashingmag/pen/bNbOxZp) by <a href="https://codepen.io/utilitybend">utilitybend</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/bNbOxZp">Transitioning mix-blend-mode [forked]</a> by <a href="https://codepen.io/utilitybend">utilitybend</a>.</figcaption></figure>
<h2 id="wrapping-up">Wrapping Up</h2>
<p>That’s an overview of how we can transition elements in and out of the top layer! In an ideal world, we could get away without needing a completely new property like <code>transition-behavior</code> just to transition otherwise “un-transitionable” properties, but here we are, and I’m glad we have it.</p>
<p>But we also got to learn about <code>@starting-style</code> and how it provides browsers with a set of styles that we can apply to the start of a transition for an element that’s in the top layer. Otherwise, the element has nothing to transition from at first render, and we’d have no way to transition them smoothly in and out of the top layer.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2025/01/29/transitioning-top-layer-entries-and-the-display-property-in-css/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>New Front-End Features For Designers In 2025</title>
		<link>http://computercoursesonline.com/index.php/2024/12/31/new-front-end-features-for-designers-in-2025/</link>
					<comments>http://computercoursesonline.com/index.php/2024/12/31/new-front-end-features-for-designers-in-2025/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Tue, 31 Dec 2024 12:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=258</guid>

					<description><![CDATA[New Front-End Features For Designers In 2025 New Front-End Features For Designers In 2025 Cosima Mielke 2024-12-31T12:00:00+00:00 2025-05-01T20:32:48+00:00 Component-specific styling, styling parents based on their children, relative colors — the web platform is going through exciting times, and many things that required JavaScript in the past can today be achieved with one simple line of...]]></description>
										<content:encoded><![CDATA[<p>              <title>New Front-End Features For Designers In 2025</title></p>
<article>
<header>
<h1>New Front-End Features For Designers In 2025</h1>
<address>Cosima Mielke</address>
<p>                  2024-12-31T12:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>Component-specific styling, styling parents based on their children, relative colors — the web platform is going through <strong>exciting times</strong>, and many things that required JavaScript in the past can today be achieved with <strong>one simple line of HTML and CSS</strong>.</p>
<p>As we are moving towards 2025, it’s a good time to revisit some of the <strong>incredible new technologies</strong> that are broadly available and supported in modern browsers today. Let’s dive right in and explore how they can simplify your day-to-day work and help you build modern UI components.</p>
<h3 id="table-of-contents">Table of Contents</h3>
<p>Below you’ll find quick jumps to topics you may be interested in, or <a href="#css-container-queries-and-style-queries">skip the table of contents</a>.</p>
<ul class="toc-components">
<li><a href="#anchor-positioning-for-tooltips-and-popovers">anchor-positioning</a></li>
<li><a href="#auto-field-sizing-for-forms">auto field-sizing</a></li>
<li><a href="#css-container-queries-and-style-queries">container queries</a></li>
<li><a href="#reliable-dialog-and-popover"><code>&lt;dialog&gt;</code></a></li>
<li><a href="#exclusive-accordions">exclusive accordions</a></li>
<li><a href="#making-focus-visible"><code>:focus-visible</code></a></li>
<li><a href="#styling-parents-based-on-children"><code>:has</code></a></li>
<li><a href="#making-hidden-content-searchable"><code>hidden=until-found</code></a></li>
<li><a href="#high-definition-colors-with-oklch-and-oklab">high-definition colors</a></li>
<li><a href="#styling-groups-within-select-menus"><code>&lt;hr&gt;</code> in select</a></li>
<li><a href="#the-right-virtual-keyboard-on-mobile"><code>inputmode</code></a></li>
<li><a href="#interpolate-between-values-for-type-and-spacing"><code>min()</code>, <code>max()</code>, <code>clamp()</code></a></li>
<li><a href="#relative-colors-in-css">relative colors</a></li>
<li><a href="#responsive-html-video-and-audio">responsive videos</a></li>
<li><a href="#smooth-scrolling-behavior">scroll behavior</a></li>
<li><a href="#simpler-snapping-for-scrollable-containers">scroll snap</a></li>
<li><a href="#no-more-typographic-orphans-and-widows"><code>text-wrap: balance</code></a></li>
<li><a href="#live-and-late-validation"><code>:user-valid</code> and <code>:user-invalid</code></a></li>
<li><a href="#smooth-transitions-with-the-view-transitions-api">View Transitions API</a></li>
</ul>
<h2 id="css-container-queries-and-style-queries">CSS Container Queries And Style Queries</h2>
<p><strong>Component-specific styling</strong>? What has long sounded like a dream to any developer, is slowly but surely becoming reality. Thanks to container queries, we can now query the width and style of the container in which components live.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/docs/css-ui/style-queries"></p>
<p>    <img loading="lazy" width="800" height="521" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="CSS Container Queries And Style Queries" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://developer.chrome.com/docs/css-ui/style-queries">Style queries</a> give us more logical control of styles in CSS. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/style-queries-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>As Una Kravets points out in her <a href="https://developer.chrome.com/docs/css-ui/style-queries">introduction to style queries</a>, this currently only works with CSS custom property values, but there are already some real-world use cases where style queries shine: They come in particularly handy when you have a <strong>reusable component with multiple variations</strong> or when you don’t have control over all of your styles but need to apply changes in certain cases.</p>
<p>If you want to dive deeper into what’s possible with container style queries and the things we can — maybe — look forward to in the future, also be sure to take a look at <a href="https://css-tricks.com/digging-deeper-into-container-style-queries/">Geoff Graham’s post</a>. He dug deep into the <strong>more nuanced aspects of style queries</strong> and summarized the things that stood out to him.</p>
<h2 id="no-more-typographic-orphans-and-widows">No More Typographic Orphans And Widows</h2>
<p>We all know those headlines where the last word breaks onto a new line and stands there alone, breaking the visual and looking, well, odd. Of course, there’s the good ol’ <code>&lt;br&gt;</code> to break the text manually or a <code>&lt;span&gt;</code> to <strong>divide the content into different parts</strong>. But have you heard of <code>text-wrap: balance</code> already?</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://ishadeed.com/article/css-text-wrap-balance/"></p>
<p>    <img loading="lazy" width="800" height="369" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="No More Typographic Orphans And Widows" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      No more odd line breaks, thanks to <a href="https://ishadeed.com/article/css-text-wrap-balance/"><code>text-wrap: balance</code></a>. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/text-wrap-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>By applying the <code>text-wrap: balance</code> property, the browser will automatically <strong>calculate the number of words</strong> and divide them equally between two lines — perfect for <strong>page titles</strong>, card titles, tooltips, modals, and FAQs, for example. Ahmad Shadeed wrote a helpful <a href="https://ishadeed.com/article/css-text-wrap-balance/">guide to <code>text-wrap: balance</code></a> in which he takes a detailed look at the property and how it can help you make your headlines look more consistent.</p>
<p>When dealing with large blocks of text, such as <strong>paragraphs</strong>, you might want to look into <code>text-wrap: pretty</code> to <a href="https://developer.chrome.com/blog/css-text-wrap-pretty">prevent orphans on the last line</a>.</p>
<h2 id="auto-field-sizing-for-forms">Auto Field-Sizing For Forms</h2>
<p>Finding just the right size for an input field usually involves a lot of guesswork — or JavaScript — to count characters and increase the field’s height or width as a user enters text. CSS <code>field-sizing</code> is here to change that. With field-sizing, we can <strong>auto-grow inputs and text areas</strong>, but also auto-shrink short select menus, so the form always fits content size perfectly. All we need to make it happen is one line of CSS.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/docs/css-ui/css-field-sizing"></p>
<p>    <img loading="lazy" width="800" height="496" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Auto Field-Sizing For Forms" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://developer.chrome.com/docs/css-ui/css-field-sizing">Auto field-sizing</a> allows us to automatically grow or shrink inputs and text areas depending on the content size. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/auto-field-sizing-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Adam Argyle summarized <a href="https://developer.chrome.com/docs/css-ui/css-field-sizing">everything you need to know about field-sizing</a>, exploring in detail how <code>field-sizing</code> affects different <code>&lt;form&gt;</code> elements. To prevent your input fields from becoming too small or too large, it is also a good idea to insert some <strong>additional styles</strong> that keep them in shape. Adam shares a code snippet that you can copy-and-paste right away.</p>
<h2 id="making-hidden-content-searchable">Making Hidden Content Searchable</h2>
<p>Accordions are a popular UI pattern, but they come with a caveat: The content inside the collapsed sections is impossible to search with <strong>find-in-page search</strong>. By using the <code>hidden=until-found</code> attribute and the <code>beforematch</code> event, we can solve the problem and even make the content accessible to search engines.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/docs/css-ui/hidden-until-found"></p>
<p>    <img loading="lazy" width="800" height="475" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Making Hidden Content Searchable" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://developer.chrome.com/docs/css-ui/hidden-until-found"><code>hidden=until-found</code></a> makes hidden content in accordions searchable. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/hidden-until-found-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>As Joey Arhar explains in his <a href="https://developer.chrome.com/docs/css-ui/hidden-until-found">guide to making collapsed content searchable</a>, you can <strong>replace the styles that hide the section</strong> with the <code>hidden=until-found</code> attribute. If your page also has another state that needs to be kept in sync with whether or not your section is revealed, he recommends adding a <code>beforematch</code> event listener. It will be fired on the <code>hidden=until-found</code> element right before the element is revealed by the browser.</p>
<h2 id="styling-groups-within-select-menus">Styling Groups Within Select Menus</h2>
<p>It’s a small upgrade for the <code>&lt;select&gt;</code> element, but a mighty one: We can now add <code>&lt;hr&gt;</code> into the list of select options, and they will <a href="https://developer.chrome.com/blog/hr-in-select">appear as separators</a> to help <strong>visually break up the options</strong> in the list.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/blog/hr-in-select"></p>
<p>    <img loading="lazy" width="800" height="565" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Styling Groups Within Select Menus" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Perfect when your select menu has a lot of options: It’s now possible to <a href="https://developer.chrome.com/blog/hr-in-select">group content</a>. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/select-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>If you want to refine things further, also be sure to take a look at <code>&lt;optgroup&gt;</code>. The HTML element lets you <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup">group options within a <code>&lt;select&gt;</code> element</a> by adding a <strong>subheading</strong> for each group.</p>
<h2 id="simpler-snapping-for-scrollable-containers">Simpler Snapping For Scrollable Containers</h2>
<p>Sometimes, you need a quick and easy way to make an element a scrollable container. CSS scroll snap makes it possible. The CSS feature enables us to create a <strong>well-controlled scrolling experience</strong> that lets users precisely swipe left and right and snap to a specific item in the container. No JavaScript required.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://ishadeed.com/article/css-scroll-snap/"></p>
<p>    <img loading="lazy" width="800" height="546" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Simpler Snapping For Scalable Containers" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      Have you ever wished there was a CSS feature that makes it easy to create a scrollable container? <a href="https://ishadeed.com/article/css-scroll-snap/">CSS scroll snap</a> is here to help. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/snapping-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Ahmad Shadeed wrote a <a href="https://ishadeed.com/article/css-scroll-snap/">practical guide</a> that walks you step by step through the process of setting up a container with scroll snap. You can use it to create <strong>image galleries</strong>, avatar lists, or other components where you want a user to scroll and snap through the content, whether it’s horizontally or vertically.</p>
<h2 id="anchor-positioning-for-tooltips-and-popovers">Anchor Positioning For Tooltips And Popovers</h2>
<p>Whether you use it for footnotes, tooltips, connector lines, visual cross-referencing, or dynamic labels in charts, the CSS Anchor Positioning API enables us to <strong>natively position elements relative to other elements</strong>, known as <em>anchors</em>.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/blog/anchor-positioning-api"></p>
<p>    <img loading="lazy" width="800" height="449" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Anchor Positioning For Tooltips And Popovers" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The <a href="https://developer.chrome.com/blog/anchor-positioning-api">CSS Anchor Positioning API</a> helps us create layered interfaces without any third-party libraries. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/anchor-positioning-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>In her <a href="https://developer.chrome.com/blog/anchor-positioning-api">introduction to the CSS Anchor Positioning API</a>, Una Kravets summarized in detail how anchor positioning works. She takes a closer look at the mechanism behind anchor positioning, how to tether to one and <strong>multiple anchors</strong>, and how to size and position an anchor-positioned element based on the size of its anchor. Browser support is still limited, so you might want to use the API with some precautions. Una’s guide includes what to watch out for.</p>
<div class="partners__lead-place"></div>
<h2 id="high-definition-colors-with-oklch-and-oklab">High-Definition Colors With OKLCH And OKLAB</h2>
<p>With high-definition colors with LCH, okLCH, LAB, and okLAB that give us access to <strong>50% more colors</strong>, the times of RGB/HSL might be over soon. To get you familiar with the new color spaces, Vitaly wrote a quick <a href="https://www.linkedin.com/posts/vitalyfriedman_colors-design-css-activity-7062428890362699776-LdLe/">overview of what you need to know</a>.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://www.linkedin.com/posts/vitalyfriedman_colors-design-css-activity-7062428890362699776-LdLe/"></p>
<p>    <img loading="lazy" width="800" height="631" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="High-Definition Colors With OKLCH And OKLAB" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The times of RGB/HSL might be over soon. Say hello to <a href="https://www.linkedin.com/posts/vitalyfriedman_colors-design-css-activity-7062428890362699776-LdLe/">high-definition colors</a>. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/color-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Both OKLCH and OKLAB are based on human perception and can specify any color the human eye can see. While OKLAB works best for rich gradients, OKLCH is a fantastic fit for color palettes in <strong>design systems</strong>. OKLCH/OKLAB colors are fully supported in Chrome, Edge, Safari, Firefox, and Opera. Figma doesn’t support them yet.</p>
<h2 id="relative-colors-in-css">Relative Colors In CSS</h2>
<p>Let’s say you have a background color and want to reduce its <strong>luminosity</strong> by 25%, or you want to use a complementary color without having to calculate it yourself. The relative color syntax (RCS) makes it possible to create a new color based on a given color.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://smashing-freiburg-2024.netlify.app/24-relative-color/"></p>
<p>    <img loading="lazy" width="800" height="483" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Relative Colors In CSS" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://smashing-freiburg-2024.netlify.app/24-relative-color/">Relative colors</a> allow us to automatically calculate a new color based on an existing color. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/relative-color.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To derive and compute a new color, we can use the <code>from</code> keyword for color functions (<code>color()</code>, <code>hsl()</code>, <code>oklch()</code>, etc.) to <strong>modify the values of the input color</strong>. Adam Argyle shares some <a href="https://smashing-freiburg-2024.netlify.app/24-relative-color/">code snippets</a> of what this looks like in practice, or check the <a href="https://drafts.csswg.org/css-color-5/#relative-colors">spec</a> for more details.</p>
<h2 id="smooth-transitions-with-the-view-transitions-api">Smooth Transitions With The View Transitions API</h2>
<p>There are a number of use cases where a smooth visual transition can make the user experience more engaging. When a thumbnail image on a product listing page transitions into a full-size image on the product detail page, for example, or when you have a <strong>fixed navigation bar</strong> that stays in place as you navigate from one page to another. The View Transitions API helps us create seamless visual transitions between different views on a site.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/docs/web-platform/view-transitions"></p>
<p>    <img loading="lazy" width="800" height="506" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smooth Transitions With The View Transitions API" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The <a href="https://developer.chrome.com/docs/web-platform/view-transitions">View Transitions API</a> creates seamless visual transitions between different views. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/view-transitions-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>View transitions can be triggered not only on a single document but also <strong>between two different documents</strong>. Both rely on the same principle: The browser takes snapshots of the old and new states, the DOM gets updated while rendering is suppressed, and the transitions are powered by CSS Animations. The only difference lies in how you trigger them, as Bramus Van Damme explains in his <a href="https://developer.chrome.com/docs/web-platform/view-transitions">guide to the View Transitions API</a>. A good <a href="https://www.debugbear.com/blog/view-transitions-spa-without-framework">alternative to single page apps</a> that often rely on heavy JavaScript frameworks.</p>
<h2 id="exclusive-accordions">Exclusive Accordions</h2>
<p>The ‘exclusive accordion’ is a variation of the accordion component. It only allows one disclosure widget to be open at the same time, so when a user opens a new one, the one that is already open will be <strong>closed automatically</strong> to save space. Thanks to CSS, we can now create the effect without a single line of JavaScript.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.chrome.com/docs/css-ui/exclusive-accordion"></p>
<p>    <img loading="lazy" width="800" height="495" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Exclusive Accordions" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      An <a href="https://developer.chrome.com/docs/css-ui/exclusive-accordion">exclusive accordion</a> automatically closes a disclosure widget when a new one is opened. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/exclusive-accordion-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To build an exclusive accordion, we need to add a <code>name</code> attribute to the <code>&lt;details&gt;</code> elements. When this attribute is used, all <code>&lt;details&gt;</code> elements that have the same <code>name</code> value form a <strong>semantic group</strong> and behave as an exclusive accordion. Bramus Van Damme <a href="https://developer.chrome.com/docs/css-ui/exclusive-accordion">summarized in detail how it works</a>.</p>
<h2 id="live-and-late-validation">Live And Late Validation</h2>
<p>When we use <code>:valid</code> and <code>:invalid</code> to apply styling based on a user’s input, there’s a downside: a form control that is <strong>required and empty</strong> will match <code>:invalid</code> even if a user hasn’t started interacting with it yet. To prevent this from happening, we usually had to write stateful code that keeps track of input a user has changed. But not anymore.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes"></p>
<p>    <img loading="lazy" width="800" height="461" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Live And Late Validation" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes"><code>:user-valid</code> and <code>:user-invalid</code></a> improve the user experience of input validation. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/validation-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>With <code>:user-valid</code> and <code>:user-invalid</code>, we now have a <a href="https://web.dev/articles/user-valid-and-user-invalid-pseudo-classes">native CSS solution that handles all of this automatically</a>. Contrary to <code>:valid</code> and <code>:invalid</code>, the <code>:user-valid</code> and <code>:user-invalid</code> pseudo-classes give users feedback about mistakes only <strong>after they have changed the input</strong>. <code>:user-valid</code> and <code>:user-invalid</code> work with input, select, and textarea controls.</p>
<h2 id="smooth-scrolling-behavior">Smooth Scrolling Behavior</h2>
<p>Imagine you have a scrolling box and a series of links that <strong>target an anchored position</strong> inside the box. When a user clicks on one of the links, it will take them to the content section inside the scrolling box — with a rather abrupt jump. The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior"><code>scroll-behavior</code> property</a> makes the scrolling transition a lot smoother, only with CSS.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior"></p>
<p>    <img loading="lazy" width="800" height="419" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smooth Scrolling Behavior" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior"><code>scroll-behavior</code></a> sets the behavior for a scrolling box when scrolling is triggered by the navigation. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/scroll-behavior-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>When setting the <code>scroll-behavior</code> value to <code>smooth</code>, the scrolling box will scroll in a <strong>smooth fashion</strong> using a user-agent-defined easing function over a user-agent-defined period of time. Of course, you can also use <code>scroll-behavior: auto</code>, and the scrolling box will scroll instantly.</p>
<h2 id="making-focus-visible">Making Focus Visible</h2>
<p>Focus styles are essential to help keyboard users navigate a page. However, for mouse users, it can be irritating when a focus ring appears around a button or link as they click on it. <code>:focus-visible</code> is here to help us create the best experience for both user groups: It displays <strong>focus styles for keyboard users</strong> and hides them for mouse users.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible"></p>
<p>    <img loading="lazy" width="800" height="379" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Making Focus Visible" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible"><code>:focus-visible</code></a> shows focus styles only when necessary. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/focus-visible-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p><code>:focus-visible</code> applies while an element matches the <code>:focus</code> pseudo-class and the User Agent determines via <strong>heuristics</strong> that the focus should be made visible on the element. Curious how it works in practice? MDN Web Docs highlights the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible">differences between <code>:focus</code> and <code>:focus-visible</code></a>, what you need to consider accessibility-wise, and how to provide a fallback for old browser versions that don’t support <code>:focus-visible</code>.</p>
<h2 id="styling-parents-based-on-children">Styling Parents Based On Children</h2>
<p>Historically, CSS selectors have worked in a top-down fashion, allowing us to style a child based on its parent. The new CSS pseudo-class <code>:has</code> works the other way round: We can now <strong>style a parent based on its children</strong>. But that’s not all yet. Josh W. Comeau wrote a fantastic <a href="https://www.joshwcomeau.com/css/has/">introduction to <code>:has</code></a> in which he explores real-world use cases that show what the pseudo-class is capable of.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://www.joshwcomeau.com/css/has/"></p>
<p>    <img loading="lazy" width="800" height="546" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Styling Parents Based On Children" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://www.joshwcomeau.com/css/has/"><code>:has</code></a> makes it possible to style one element based on the property or status of any other element. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/has-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p><code>:has</code> is not limited to parent-child relationships or direct siblings. Instead, it lets us style one element based on the properties or status of any other element in a totally <strong>different container</strong>. And it can be used as a sort of global event listener, as Josh shows — to disable scrolling on a page when a modal is open or to create a JavaScript-free dark mode toggle, for example.</p>
<div class="partners__lead-place"></div>
<h2 id="interpolate-between-values-for-type-and-spacing">Interpolate Between Values For Type And Spacing</h2>
<p>CSS comparison functions <code>min()</code>, <code>max()</code>, and <code>clamp()</code> are today supported in all major browsers, providing us with an effective way to create dynamic layouts with <strong>fluid type scales</strong>, grids, and spacing systems.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://ishadeed.com/article/css-min-max-clamp/"></p>
<p>    <img loading="lazy" width="800" height="384" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Interpolate Between Values For Type And Spacing" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The future of design is <a href="https://ishadeed.com/article/css-min-max-clamp/">fluid</a>. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/comparison-functions-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To get you fit for using the functions in your projects right away, Ahmad Shadeed wrote a <a href="https://ishadeed.com/article/css-min-max-clamp/">comprehensive guide</a> in which he explains everything you need to know about <code>min()</code>, <code>max()</code>, and <code>clamp()</code>, with <strong>practical examples</strong> and use cases and including all the points of confusion you might encounter.</p>
<p>If you’re looking for a quick and easy way to create fluid scales, the <a href="https://utopia.fyi/type/calculator/">Fluid Type Scale Calculator</a> by Utopia has got your back. All you need to do is define min and max viewport widths and the number of scale steps, and the <strong>calculator</strong> provides you with a responsive preview of the scale and the CSS code snippet.</p>
<h2 id="reliable-dialog-and-popover">Reliable Dialog And Popover</h2>
<p>If you’re looking for a quick way to create a modal or popup, the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"><code>&lt;dialog&gt;</code> HTML element</a> finally offers a native (and accessible!) solution to help you get the job done. It represents a <strong>modal or non-modal dialog box</strong> or other interactive component, such as a confirmation prompt or a subwindow used to enter data.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://smashing-freiburg-2024.netlify.app/27-dialog/"></p>
<p>    <img loading="lazy" width="800" height="425" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Reliable dialog And Popover" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      We now have accessible <a href="https://smashing-freiburg-2024.netlify.app/27-dialog/"><code>&lt;dialog&gt;</code> menus</a> for blocking pop-ups and popovers for non-blocking menus. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/dialog.png">Large preview</a>)<br />
    </figcaption></figure>
<p>While modal dialog boxes interrupt interaction with a page, non-modal dialog boxes <strong>allow interaction</strong> with the page while the dialog is open. Adam Argyle published some <a href="https://smashing-freiburg-2024.netlify.app/27-dialog/">code snippets</a> that show how <code>&lt;dialog&gt;</code> can block pop-ups and popovers for non-blocking menus, out of the box.</p>
<h2 id="responsive-html-video-and-audio">Responsive HTML Video And Audio</h2>
<p>In 2014, media attribute support for HTML video sources was deleted from the HTML standard. Last year, it made a comeback, which means that we can use <strong>media queries</strong> for delivering responsive HTML videos.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://scottjehl.com/posts/using-responsive-video/"></p>
<p>    <img loading="lazy" width="800" height="468" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Responsive HTML Video And Audio" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      <a href="https://scottjehl.com/posts/using-responsive-video/">Adjusting video and audio files based on the browser’s viewport</a> reduces page payload. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/video-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>Scott Jehl summarized <a href="https://scottjehl.com/posts/using-responsive-video/">how responsive HTML video — and even audio — works</a>, what you need to consider when writing the markup, and what other types of media queries can be used in combination with HTML video.</p>
<h2 id="the-right-virtual-keyboard-on-mobile">The Right Virtual Keyboard On Mobile</h2>
<p>It’s a small detail, but one that adds to a well-considered user experience: displaying the <strong>most comfortable touchscreen keyboard</strong> to help a user enter their information without having to switch back and forth to insert numbers, punctuation, or special characters like an <code>@</code> symbol.</p>
<figure class="
  
  
  "></p>
<p>    <a href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/"></p>
<p>    <img loading="lazy" width="800" height="271" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Right Virtual Keyboards On Mobile" class="lazyload" data-src="https://res.cloudinary.com/indysigner/image/fetch/f_auto,q_80/w_400/https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png"></p>
<p>    </a><figcaption class="op-vertical-bottom">
      The <a href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/">right virtual keyboard</a> improves the user experience for mobile users. (<a href="https://files.smashing.media/articles/new-front-end-features-for-designers-in-2025/mobile-keyboard-opt.png">Large preview</a>)<br />
    </figcaption></figure>
<p>To show the right keyboard layout, we can use <code>inputmode</code>. It <a href="https://css-tricks.com/everything-you-ever-wanted-to-know-about-inputmode/">instructs the browser which keyboard to display</a> and supports values for <strong>numeric</strong>, telephone, decimal, <strong>email</strong>, <strong>URL</strong>, and search keyboards. To further improve the UX, we can add the <code>enterkeyhint</code> attribute: it <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint">adjusts the text on the Enter key</a>. If no <code>enterkeyhint</code> is used, the user agent might use contextual information from the <code>inputmode</code> attribute.</p>
<h2 id="a-look-into-the-future">A Look Into The Future</h2>
<p>As we are starting to adopt all of these shiny new front-end features in our projects, the web platform is, of course, constantly evolving — and there are some exciting things on the horizon already! For example, we are very close to getting <strong>masonry layout</strong>, fully <strong>customizable drop-downs</strong> with <code>&lt;selectmenu&gt;</code>, and <strong>text-box trimming</strong> for adjusting fonts to be perfectly aligned within the grid. Kudos to all the wonderful people who are working tirelessly to push the web forward! <img src="https://s.w.org/images/core/emoji/13.1.0/72x72/1f44f.png" alt="👏" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>In the meantime, we hope you found something helpful in this post that you can apply to your product or application right away. Happy tinkering!</p>
<h2 id="smashing-weekly-newsletter">Smashing Weekly Newsletter</h2>
<p><a href="https://www.smashingmagazine.com/the-smashing-newsletter/"><img loading="lazy" style="float:right;margin-top:1em;margin-left:1.5em;margin-bottom:1em;border-radius:11px;max-width:50%" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" width="200" alt="The weekly Smashing Newsletter" class="lazyload" data-src="https://www.smashingmagazine.com/images/smashing-cat/cat-with-slippers.svg"></a>You want to stay on top of what’s happening in the world of front-end and UX? With our <a href="https://www.smashingmagazine.com/the-smashing-newsletter/">weekly newsletter</a>, we aim to bring you <strong>useful, practical tidbits</strong> and share some of the helpful things that folks are working on in the web industry. Every issue is curated, written, and edited with love and care. No third-party mailings or hidden advertising.</p>
<p>Also, when you <a href="https://www.smashingmagazine.com/the-smashing-newsletter/">subscribe</a>, you really help us pay the bills. Thank you for your kind support!</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(vf, il)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2024/12/31/new-front-end-features-for-designers-in-2025/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines</title>
		<link>http://computercoursesonline.com/index.php/2024/12/11/an-introduction-to-css-scroll-driven-animations-scroll-and-view-progress-timelines/</link>
					<comments>http://computercoursesonline.com/index.php/2024/12/11/an-introduction-to-css-scroll-driven-animations-scroll-and-view-progress-timelines/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Wed, 11 Dec 2024 15:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=261</guid>

					<description><![CDATA[An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines Mariana Beldi 2024-12-11T15:00:00+00:00 2025-05-01T20:32:48+00:00 You can safely use scroll-driven animations in Chrome as of December 2024. Firefox supports them, too, though you’ll need to enable a flag. Safari? Not yet, but don’t worry &#8212; you can still offer a...]]></description>
										<content:encoded><![CDATA[<p>              <title>An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines</title></p>
<article>
<header>
<h1>An Introduction To CSS Scroll-Driven Animations: Scroll And View Progress Timelines</h1>
<address>Mariana Beldi</address>
<p>                  2024-12-11T15:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>You can safely use scroll-driven animations in Chrome as of December 2024. Firefox supports them, too, though you’ll need to enable a flag. Safari? Not yet, but don’t worry &mdash; you can still offer a seamless experience across all browsers with a <a href="https://github.com/flackr/scroll-timeline">polyfill</a>. Just keep in mind that adding a polyfill involves a JavaScript library, so you won’t get the same performance boost.</p>
<p>There are plenty of valuable resources to dive into scroll-driven animations, which I’ll be linking throughout the article. My starting point was <a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICM3ttukz9x5LCNOHfWBVnn">Bramus’ video tutorial</a>, which pairs nicely with <a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/">Geoff’s in-depth notes</a> <a href="https://css-tricks.com/unleash-the-power-of-scroll-driven-animations/">Graham</a> that build on the tutorial.</p>
<p>In this article, we’ll walk through the <a href="https://www.w3.org/TR/scroll-animations-1/">latest published version by the W3C</a> and explore the two types of scroll-driven timelines &mdash; <strong>scroll progress timelines</strong> and <strong>view progress timelines</strong>. By the end, I hope that you are familiar with both timelines, not only being able to tell them apart but also feeling confident enough to use them in your work.</p>
<p><strong>Note</strong>: <em>All demos in this article only work in Chrome 116 or later at the time of writing.</em></p>
<h2 id="scroll-progress-timelines">Scroll Progress Timelines</h2>
<p>The scroll progress timeline links an animation’s timeline to the scroll position of a scroll container along a specific axis. So, the animation is tied directly to scrolling. As you scroll forward, so does the animation. You’ll see me refer to them as <code>scroll-timeline</code> animations in addition to calling them scroll progress timelines.</p>
<p>Just as we have two types of scroll-driven animations, we have two types of <code>scroll-timeline</code> animations: <strong>anonymous timelines</strong> and <strong>named timelines</strong>.</p>
<h3 id="anonymous-scroll-timeline">Anonymous <code>scroll-timeline</code></h3>
<p>Let’s start with a classic example: creating a scroll progress bar at the top of a blog post to track your reading progress.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="RNbRqoj" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Scroll Progress Timeline example &#8211; before animation-timeline scroll() [forked]](https://codepen.io/smashingmag/pen/RNbRqoj) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/RNbRqoj">Scroll Progress Timeline example &#8211; before animation-timeline scroll() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>In this example, there’s a <code>&lt;div&gt;</code> with the ID “progress.” At the end of the CSS file, you’ll see it has a background color, a defined width and height, and it’s fixed at the top of the page. There’s also an animation that scales it from <code>0</code> to <code>1</code> along the x-axis &mdash; pretty standard if you’re familiar with CSS animations!</p>
<p>Here’s the relevant part of the styles:</p>
<pre><code class="language-css">&#035;progress {
  /&#042; ... &#042;/
  animation: progressBar 1s linear;
}


@keyframes progressBar {
  from { transform: scaleX(0); }
}
</code></pre>
<p>The <code>progressBar</code> animation runs once and lasts one second with a linear timing function. Linking this animation scrolling is just a single line in CSS:</p>
<pre><code class="language-css">animation-timeline: scroll();
</code></pre>
<p>No need to specify seconds for the duration &mdash; the scrolling behavior itself will dictate the timing. And that’s it! You’ve just created your first scroll-driven animation! Notice how the animation’s direction is directly tied to the scrolling direction &mdash; scroll down, and the progress indicator grows wider; scroll up, and it becomes narrower.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="ByBzGpO" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Scroll Progress Timeline example &#8211; animation-timeline scroll() [forked]](https://codepen.io/smashingmag/pen/ByBzGpO) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ByBzGpO">Scroll Progress Timeline example &#8211; animation-timeline scroll() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<h3 id="scroll-timeline-property-parameters"><code>scroll-timeline</code> Property Parameters</h3>
<p>In a <code>scroll-timeline</code> animation, the <code>scroll()</code> function is used inside the <code>animation-timeline</code> property. It only takes two parameters: <code>&lt;scroller&gt;</code> and <code>&lt;axis&gt;</code>.</p>
<ul>
<li><strong><code>&lt;scroller&gt;</code></strong> refers to the scroll container, which can be set as <code>nearest</code> (the default), <code>root</code>, or <code>self</code>.</li>
<li><strong><code>&lt;axis&gt;</code></strong> refers to the scroll axis, which can be <code>block</code> (the default), <code>inline</code>, <code>x</code>, or <code>y</code>.</li>
</ul>
<p>In the reading progress example above, we didn’t declare any of these because we used the defaults. But we could achieve the same result with:</p>
<pre><code class="language-css">animation-timeline: scroll(nearest block);
</code></pre>
<p>Here, the <code>nearest</code> scroll container is the root scroll of the HTML element. So, we could also write it this way instead:</p>
<pre><code class="language-css">animation-timeline: scroll(root block);
</code></pre>
<p>The <code>block</code> axis confirms that the scroll moves top to bottom in a left-to-right writing mode. If the page has a wide horizontal scroll, and we want to animate along that axis, we could use the <code>inline</code> or <code>x</code>  values (depending on whether we want the scrolling direction to always be left-to-right or adapt based on the writing mode).</p>
<p>We’ll dive into <code>self</code> and <code>inline</code> in more examples later, but the best way to learn is to play around with all the combinations, and <a href="https://scroll-driven-animations.style/tools/scroll-timeline/params/">this tool by Bramus</a> lets you do exactly that. Spend a few minutes before we jump into the next property associated with scroll timelines.</p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h3 id="the-animation-range-property">The <code>animation-range</code> Property</h3>
<p>The <code>animation-range</code> for <code>scroll-timeline</code> defines which part of the scrollable content controls the start and end of an animation’s progress based on the scroll position. It allows you to decide when the animation starts or ends while scrolling through the container.</p>
<p>By default, the <code>animation-range</code> is set to <code>normal</code>, which is shorthand for the following:</p>
<pre><code class="language-css">animation-range-start: normal;
animation-range-end: normal;
</code></pre>
<p>This translates to <code>0%</code> (<code>start</code>) and <code>100%</code> (<code>end</code>) in a <code>scroll-timeline</code> animation:</p>
<pre><code class="language-css">animation-range: normal normal;
</code></pre>
<p>…which is the same as:</p>
<pre><code class="language-css">animation-range: 0% 100%;
</code></pre>
<p>You can declare any <a href="https://css-tricks.com/css-length-units/">CSS length units</a> or even <a href="https://www.smashingmagazine.com/2015/12/getting-started-css-calc-techniques/">calculations</a>. For example, let’s say I have a footer that’s <code>500px</code> tall. It’s filled with banners, ads, and related posts. I don’t want the scroll progress bar to include any of that as part of the reading progress. What I want is for the animation to start at the top and end <code>500px</code> before the bottom. Here we go:</p>
<pre><code class="language-css">animation-range: 0% calc(100% - 500px);
</code></pre>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="azoZQym" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Scroll Progress Timeline example &#8211; animation-timeline, animation-range [forked]](https://codepen.io/smashingmag/pen/azoZQym) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/azoZQym">Scroll Progress Timeline example &#8211; animation-timeline, animation-range [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>Just like that, we’ve covered the key properties of <code>scroll-timeline</code> animations. Ready to take it a step further?</p>
<h3 id="named-scroll-timeline">Named <code>scroll-timeline</code></h3>
<p>Let’s say I want to use the scroll position of a different scroll container for the same animation. The <code>scroll-timeline-name</code> property allows you to specify which scroll container the scroll animation should be linked to. You give it a name (a dashed-ident, e.g., <code>--my-scroll-timeline</code>) that maps to the scroll container you want to use. This container will then control the animation’s progress as the user scrolls through it.</p>
<p>Next, we need to define the scroll axis for this new container by using the <code>scroll-timeline-axis</code>, which tells the animation which axis will trigger the motion. Here’s how it looks in the code:</p>
<pre><code class="language-css">.my-class { 
  /&#042; This is my new scroll-container &#042;/
  scroll-timeline-name: --my-custom-name;
  scroll-timeline-axis: inline;
}
</code></pre>
<p>If you omit the axis, then the default <code>block</code> value will be used. However, you can also use the shorthand <code>scroll-timeline</code> property to combine both the name and axis in a single declaration:</p>
<pre><code class="language-css">.my-class { 
  /&#042; Shorthand for scroll-container with axis &#042;/
  scroll-timeline: --my-custom-name inline;
}
</code></pre>
<p>I think it’s easier to understand all this with a practical example. Here’s the same progress indicator we’ve been working with, but with inline scrolling (i.e., along the x-axis):</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="pvzbQrM" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Named Scroll Progress Timeline [forked]](https://codepen.io/smashingmag/pen/pvzbQrM) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/pvzbQrM">Named Scroll Progress Timeline [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>We have two animations running:</p>
<ol>
<li>A progress bar grows wider when scrolling in an inline direction.</li>
<li>The container’s background color changes the further you scroll.</li>
</ol>
<p>The HTML structure looks like the following:</p>
<div class="break-out">
<pre><code class="language-html">&lt;div class="gallery"&gt;
  &lt;div class="gallery-scroll-container"&gt;
    &lt;div class="gallery-progress" role="progressbar" aria-label="progress"&gt;&lt;/div&gt;
    &lt;img src="image1.svg" alt="Alt text" draggable="false" width="500"&gt;
    &lt;img src="image2.svg" alt="Alt text" draggable="false" width="500"&gt;
    &lt;img src="image3.svg" alt="Alt text" draggable="false" width="500"&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
</div>
<p>In this case, the <code>gallery-scroll-container</code> has horizontal scrolling and changes its background color as you scroll. Normally, we could just use <code>animation-timeline: scroll(self inline)</code> to achieve this. However, we also want the <code>gallery-progress</code> element to use the same scroll for its animation.</p>
<p>The <code>gallery-progress</code> element is the first inside <code>gallery-scroll-container</code>, and we will lose it when scrolling unless it’s absolutely positioned. But when we do this, the element no longer occupies space in the normal document flow, and that affects how the element behaves with its parent and siblings. We need to specify which scroll container we want it to listen to.</p>
<p>That’s where naming the scroll container comes in handy. By giving <code>gallery-scroll-container</code> a <code>scroll-timeline-name</code> and <code>scroll-timeline-axis</code>, we can ensure both animations sync to the same scroll:</p>
<pre><code class="language-css">.gallery-scroll-container {
  /&#042; ... &#042;/
  animation: bg steps(1);
  scroll-timeline: --scroller inline;
}
</code></pre>
<p>And is using that scrolling to define its own <code>animation-timeline</code>:</p>
<pre><code class="language-css">.gallery-scroll-container {
  /&#042; ... &#042;/
  animation: bg steps(1);
  scroll-timeline: --scroller inline;
  animation-timeline: --scroller;
}
</code></pre>
<p>Now we can scale this name to the progress bar that is using a different animation but listening to the same scroll:</p>
<pre><code class="language-css">.gallery-progress {
  /&#042; ... &#042;/
  animation: progressBar linear;
  animation-timeline: --scroller;
}
</code></pre>
<p>This allows both animations (the growing progress bar and changing background color) to follow the same scroll behavior, even though they are separate elements and animations.</p>
<div class="partners__lead-place"></div>
<h3 id="the-timeline-scope-property">The <code>timeline-scope</code> Property</h3>
<p>What happens if we want to animate something based on the scroll position of a sibling or even a higher ancestor? This is where the <code>timeline-scope</code> property comes into play. It allows us to extend the scope of a <code>scroll-timeline</code> beyond the current element’s subtree. The value of <code>timeline-scope</code> must be a custom identifier, which again is a dashed-ident.</p>
<p>Let’s illustrate this with a new example. This time, scrolling in one container runs an animation inside another container:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="jENrQGo" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Scroll Driven Animations &#8211; timeline-scope [forked]](https://codepen.io/smashingmag/pen/jENrQGo) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/jENrQGo">Scroll Driven Animations &#8211; timeline-scope [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>We can play the animation on the image when scrolling the text container because they are siblings in the HTML structure:</p>
<pre><code class="language-html">&lt;div class="main-container"&gt;
  &lt;div class="sardinas-container"&gt;
    &lt;img ...&gt;
  &lt;/div&gt;

  &lt;div class="scroll-container"&gt;
    &lt;p&gt;Long text...&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Here, only the <code>.scroll-container</code> has scrollable content, so let’s start by naming this:</p>
<pre><code class="language-css">.scroll-container {
  /&#042; ... &#042;/
  overflow-y: scroll;
  scroll-timeline: --containerText;
}
</code></pre>
<p>Notice that I haven’t specified the scroll axis, as it defaults to <code>block</code> (vertical scrolling), and that’s the value I want.</p>
<p>Let’s move on to the image inside the <code>sardinas-container</code>. We want this image to animate as we scroll through the <code>scroll-container</code>. I’ve added a <code>scroll-timeline-name</code> to its <code>animation-timeline</code> property:</p>
<pre><code class="language-css">.sardinas-container img {
  /&#042; ... &#042;/
  animation: moveUp steps(6) both;
  animation-timeline: --containerText;
}
</code></pre>
<p>At this point, however, the animation still won’t work because the <code>scroll-container</code> is not directly related to the images. To make this work, we need to extend the <code>scroll-timeline-name</code> so it becomes reachable. This is done by adding the <code>timeline-scope</code> to the parent element (or a higher ancestor) shared by both elements:</p>
<pre><code class="language-css">.main-container {
  /&#042; ... &#042;/
  timeline-scope: --containerText;
}
</code></pre>
<p>With this setup, the scroll of the <code>scroll-container</code> will now control the animation of the image inside the <code>sardinas-container</code>!</p>
<p>Now that we’ve covered how to use <code>timeline-scope</code>, we’re ready to move on to the next type of scroll-driven animations, where the same properties will apply but with slight differences in how they behave.</p>
<h2 id="view-progress-timelines">View Progress Timelines</h2>
<p>We just looked at <strong>scroll progress animations</strong>. That’s the first type of scroll-driven animation of the two. Next, we’re turning our attention to <strong>view progress animations</strong>. There’s a lot of similarities between the two! But they’re different enough to warrant their own section for us to explore how they work. You’ll see me refer to these as <code>view-timeline</code> animations in addition to calling them view progress animations, as they revolve around a <code>view()</code> function.</p>
<p>The <strong>view progress timeline</strong> is the second type of type of scroll-driven animation that we’re looking at. It tracks an element as it enters or exits the scrollport (the visible area of the scrollable content). This behavior is quite similar to <a href="https://css-tricks.com/an-explanation-of-how-the-intersection-observer-watches/?ref=csslayout.news">how an <code>IntersectionObserver</code> works in JavaScript</a> but can be done entirely in CSS.</p>
<p>We have anonymous and named view progress timelines, just as we have anonymous and named scroll progress animations. Let’s unpack those.</p>
<h3 id="anonymous-view-timeline">Anonymous View Timeline</h3>
<p>Here’s a simple example to help us see the basic idea of anonymous view timelines. Notice how the image fades into view when you scroll down to a certain point on the page:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="KwPMrQO" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [View Timeline Animation &#8211; view() [forked]](https://codepen.io/smashingmag/pen/KwPMrQO) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/KwPMrQO">View Timeline Animation &#8211; view() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>Let’s say we want to animate an image that fades in as it appears in the scrollport. The image’s opacity will go from <code>0</code> to <code>1</code>. This is how you might write that same animation in classic CSS using <code>@keyframes</code>:</p>
<pre><code class="language-css">img {
  /&#042; ... &#042;/
  animation: fadeIn 1s;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
</code></pre>
<p>That’s great, but we want the image to <code>fadeIn</code> when it’s in view. Otherwise, the animation is sort of like a tree that falls in a forest with no one there to witness it… did the animation ever happen? We’ll never know!</p>
<p>We have a <code>view()</code> function that makes this a view progress animation with a single line of CSS:</p>
<pre><code class="language-css">img {
  /&#042; ... &#042;/
  animation: fadeIn;
  animation-timeline: view();
}
</code></pre>
<p>And notice how we no longer need to declare an <code>animation-duration</code> like we did in classic CSS. The animation is no longer tied by time but by space. The animation is triggered as the image becomes visible in the scrollport.</p>
<h3 id="view-timeline-parameters">View Timeline Parameters</h3>
<p>Just like the <code>scroll-timeline</code> property, the <strong><code>view-timeline</code></strong> property accepts parameters that allow for more customization:</p>
<pre><code class="language-css">animation-timeline: view( );
</code></pre>
<ul>
<li><strong><code>&lt;inset&gt;</code></strong><br />
Controls when the animation starts and ends relative to the element’s visibility within the scrollport. It defines the margin between the edges of the scrollport and the element being tracked. The default value is <code>auto</code>, but it can also take length percentages as well as start and end values.</li>
<li><strong><code>&lt;axis&gt;</code></strong><br />
This is similar to the scroll-timeline’s axis parameter. It defines which axis (horizontal or vertical) the animation is tied to. The default is <code>block</code>, which means it tracks the vertical movement. You can also use <code>inline</code> to track horizontal movement or simple <code>x</code> or <code>y</code>.</li>
</ul>
<p>Here’s an example that uses both <code>inset</code> and <code>axis</code> to customize when and how the animation starts:</p>
<pre><code class="language-css">img {
  animation-timeline: view(20% block);
}
</code></pre>
<p>In this case:</p>
<ol>
<li>The animation starts when the image is 20% visible in the scrollport.</li>
<li>The animation is triggered by vertical scrolling (<code>block</code> axis).</li>
</ol>
<h3 id="parallax-effect">Parallax Effect</h3>
<p>With the <code>view()</code> function, it’s also easy to create parallax effects by simply adjusting the animation properties. For example, you can have an element move or scale as it enters the scrollport without any JavaScript:</p>
<pre><code class="language-css">img {
  animation: parallaxMove 1s;
  animation-timeline: view();
}

@keyframes parallaxMove {
  to { transform: translateY(-50px); }
}
</code></pre>
<p>This makes it incredibly simple to create dynamic and engaging scroll animations with just a few lines of CSS.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="mybEQLK" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Parallax effect with CSS Scroll driven animations &#8211; view() [forked]](https://codepen.io/smashingmag/pen/mybEQLK) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/mybEQLK">Parallax effect with CSS Scroll driven animations &#8211; view() [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<h3 id="the-animation-range-property-1">The <code>animation-range</code> Property</h3>
<p>Using the CSS <strong><code>animation-range</code></strong> property with view timelines defines how much of an element’s visibility within the scrollport controls the start and end points of the animation’s progress. This can be used to fine-tune when the animation begins and ends based on the element’s visibility in the viewport.</p>
<p>While the default value is <code>normal</code>, in view timelines, it translates to tracking the full visibility of the element from the moment it starts entering the scrollport until it fully leaves. This is represented by the following:</p>
<pre><code class="language-css">animation-range: normal normal;
/&#042; Equivalent to &#042;/
animation-range: cover 0% cover 100%;
</code></pre>
<p>Or, more simply:</p>
<pre><code class="language-css">animation-range: cover;
</code></pre>
<p>There are six possible values or <code>timeline-range-names</code>:</p>
<ol>
<li><strong><code>cover</code></strong><br />
Tracks the full visibility of the element, from when it starts entering the scrollport to when it completely leaves it.</li>
<li><strong><code>contain</code></strong><br />
Tracks when the element is fully visible inside the scrollport, from the moment it’s fully contained until it no longer is.</li>
<li><strong><code>entry</code></strong><br />
Tracks the element from the point it starts entering the scrollport until it’s fully inside.</li>
<li><strong><code>exit</code></strong><br />
Tracks the element from the point it starts, leaving the scrollport until it’s fully outside.</li>
<li><strong><code>entry-crossing</code></strong><br />
Tracks the element as it crosses the starting edge of the scrollport, from start to full crossing.</li>
<li><strong><code>exit-crossing</code></strong><br />
Tracks the element as it crosses the end edge of the scrollport, from start to full crossing.</li>
</ol>
<p>You can mix different <code>timeline-range-names</code> to control the start and end points of the animation range. For example, you could make the animation start when the element enters the scrollport and end when it exits:</p>
<pre><code class="language-css">animation-range: entry exit;
</code></pre>
<p>You can also combine these values with percentages to define more custom behavior, such as starting the animation halfway through the element’s entry and ending it halfway through its exit:</p>
<pre><code class="language-css">animation-range: entry 50% exit 50%;
</code></pre>
<p>Exploring all these values and combinations is best done interactively. Tools like Bramus’ <a href="https://scroll-driven-animations.style/tools/view-timeline/ranges/">view-timeline range visualizer</a> make it easier to understand.</p>
<div class="partners__lead-place"></div>
<h3 id="target-range-inside-keyframes">Target Range Inside <code>@keyframes</code></h3>
<p>One of the powerful features of <code>timeline-range-names</code> is their ability to be used inside <code>@keyframes</code>:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="zxOBMaK" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [target range inside @keyframes &#8211; view-timeline, timeline-range-name [forked]](https://codepen.io/smashingmag/pen/zxOBMaK) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/zxOBMaK">target range inside @keyframes &#8211; view-timeline, timeline-range-name [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>Two different animations are happening in that demo:</p>
<ol>
<li><strong><code>slideIn</code></strong><br />
When the element enters the scrollport, it scales up and becomes visible.</li>
<li><strong><code>slideOut</code></strong><br />
When the element leaves, it scales down and fades out.</li>
</ol>
<pre><code class="language-css">@keyframes slideIn {
  from {
    transform: scale(.8) translateY(100px); 
    opacity: 0;
  }
  to { 
    transform: scale(1) translateY(0); 
    opacity: 1;
  }
}

@keyframes slideOut {
  from {
    transform: scale(1) translateY(0); 
    opacity: 1;    
  }
  to { 
    transform: scale(.8) translateY(-100px); 
    opacity: 0 
  }
}
</code></pre>
<p>The new thing is that now we can merge these two animations using the <code>entry</code> and <code>exit</code> <code>timeline-range-names</code>, simplifying it into one animation that handles both cases:</p>
<pre><code class="language-css">@keyframes slideInOut {
  /&#042; Animation for when the element enters the scrollport &#042;/
  entry 0% {
    transform: scale(.8) translateY(100px); 
    opacity: 0;
  }
  entry 100% { 
    transform: scale(1) translateY(0); 
    opacity: 1;
  }
  /&#042; Animation for when the element exits the scrollport &#042;/
  exit 0% {
    transform: scale(1) translateY(0); 
    opacity: 1;    
  }
  exit 100% { 
    transform: scale(.8) translateY(-100px); 
    opacity: 0;
  }
}
</code></pre>
<ul>
<li><strong><code>entry 0%</code></strong><br />
Defines the state of the element at the beginning of its entry into the scrollport (scaled down and transparent).</li>
<li><strong><code>entry 100%</code></strong><br />
Defines the state when the element has fully entered the scrollport (fully visible and scaled up).</li>
<li><strong><code>exit 0%</code></strong><br />
Starts tracking the element as it begins to leave the scrollport (visible and scaled up).</li>
<li><strong><code>exit 100%</code></strong><br />
Defines the state when the element has fully left the scrollport (scaled down and transparent).</li>
</ul>
<p>This approach allows us to animate the element’s behavior smoothly as it both enters and leaves the scrollport, all within a single <code>@keyframes</code> block.</p>
<h3 id="named-view-timeline-and-timeline-scope">Named <code>view-timeline</code> And <code>timeline-scope</code></h3>
<p>The concept of using <code>view-timeline</code> with named timelines and linking them across different elements can truly expand the possibilities for scroll-driven animations. In this case, we are linking the scroll-driven animation of images with the animations of unrelated paragraphs in the DOM structure by using <strong>a named <code>view-timeline</code> and <code>timeline-scope</code></strong>.</p>
<p>The <code>view-timeline</code> property works similarly to the <code>scroll-timeline</code> property. It’s the shorthand for declaring the <code>view-timeline-name</code> and <code>view-timeline-axis</code> properties in one line. However, the difference from <code>scroll-timeline</code> is that we can link the animation of an element when the linked elements enter the scrollport. I took the previous demo and added an animation to the paragraphs so you can see how the opacity of the text is animated when scrolling the images on the left:</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="KwPMrBP" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [View-timeline, timeline-scope [forked]](https://codepen.io/smashingmag/pen/KwPMrBP) by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/KwPMrBP">View-timeline, timeline-scope [forked]</a> by <a href="https://codepen.io/marianab">Mariana Beldi</a>.</figcaption></figure>
<p>This one looks a bit verbose, but I found it hard to come up with a better example to show the power of it. Each image in the vertical scroll container is assigned a named <code>view-timeline</code> with a unique identifier:</p>
<div class="break-out">
<pre><code class="language-css">.vertical-scroll-container img:nth-of-type(1) { view-timeline: --one; }
.vertical-scroll-container img:nth-of-type(2) { view-timeline: --two; }
.vertical-scroll-container img:nth-of-type(3) { view-timeline: --three; }
.vertical-scroll-container img:nth-of-type(4) { view-timeline: --four; }
</code></pre>
</div>
<p>This makes the scroll timeline of each image have its own custom name, such as <code>--one</code> for the first image, <code>--two</code> for the second, and so on.</p>
<p>Next, we connect the animations of the paragraphs to the named timelines of the images. The corresponding paragraph should animate when the images enter the scrollport:</p>
<div class="break-out">
<pre><code class="language-css">.vertical-text p:nth-of-type(1) { animation-timeline: --one; }
.vertical-text p:nth-of-type(2) { animation-timeline: --two; }
.vertical-text p:nth-of-type(3) { animation-timeline: --three; }
.vertical-text p:nth-of-type(4) { animation-timeline: --four; }
</code></pre>
</div>
<p>However, since the images and paragraphs are not directly related in the DOM, we need to declare a <code>timeline-scope</code> on their common ancestor. This ensures that the named timelines (<code>--one</code>, <code>--two</code>, and so on) can be referenced and shared between the elements:</p>
<pre><code class="language-css">.porto {
  /&#042; ... &#042;/
  timeline-scope: --one, --two, --three, --four;
}
</code></pre>
<p>By declaring the <code>timeline-scope</code> with all the named timelines (<code>--one</code>, <code>—two</code>, <code>--three</code>, <code>--four</code>), both the images and the paragraphs can participate in the same scroll-timeline logic, despite being in separate parts of the DOM tree.</p>
<h2 id="final-notes">Final Notes</h2>
<p>We’ve covered the vast majority of what’s currently defined in the <a href="https://drafts.csswg.org/scroll-animations-1/">CSS Scroll-Driven Animations Module Leve 1 specification</a> today in December 2024. But I want to highlight a few key takeaways that helped me better understand these new rules that you may not get directly from the spec:</p>
<ul>
<li><strong>Scroll container essentials</strong><br />
It may seem obvious, but a scroll container is necessary for scroll-driven animations to work. Issues often arise when elements like text or containers are resized or when animations are tested on larger screens, causing the scrollable area to disappear.</li>
<li><strong>Impact of <code>position: absolute</code></strong><br />
Using absolute positioning can sometimes interfere with the intended behavior of scroll-driven animations. The relationship between elements and their parent elements gets tricky when <code>position: absolute</code> is applied.</li>
<li><strong>Tracking an element’s initial state</strong><br />
The browser evaluates the element’s state <em>before</em> any transformations (like <code>translate</code>) are applied. This affects when animations, particularly view timelines, begin. Your animation might trigger earlier or later than expected due to the initial state.</li>
<li><strong>Avoid hiding overflow</strong><br />
Using <code>overflow: hidden</code> can disrupt the scroll-seeking mechanism in scroll-driven animations. The recommended solution is to switch to <code>overflow: clip</code>. Bramus has <a href="https://www.bram.us/2024/02/14/scroll-driven-animations-you-want-overflow-clip-not-overflow-hidden/">a great article about this</a> and <a href="https://www.youtube.com/watch?v=72pUm4tQesw">a video from Kevin Powell</a> also suggests that we may no longer need <code>overflow: hidden</code>.</li>
<li><strong>Performance</strong><br />
For the best results, stick to animating GPU-friendly properties like transforms, opacity, and some filters. These skip the heavy lifting of recalculating layout and repainting. On the other hand, animating things like <code>width</code>, <code>height</code>, or <code>box-shadow</code> can slow things down since they require re-rendering. Bramus <a href="https://www.bram.us/2024/05/30/scroll-driven-animations-with-css-webexpo/">mentioned</a> that soon, more properties &mdash; like <code>background-color</code>, <code>clip-path</code>, <code>width</code>, and <code>height</code> &mdash; will be animatable on the compositor, making the performance even better.</li>
<li><strong>Use <code>will-change</code> wisely</strong><br />
Leverage this property to promote elements to the GPU, but use it sparingly. Overusing <code>will-change</code> can lead to excessive memory usage since the browser reserves resources even if the animations don’t frequently change.</li>
<li><strong>The order matters</strong><br />
If you are using the <code>animation</code> shorthand, always place the <code>animation-timeline</code> after it.</li>
<li><strong>Progressive enhancement and accessibility</strong><br />
Combine media queries for reduced motion preferences with the <code>@supports</code> rule to ensure animations only apply when the user has no motion restrictions, and the browser supports them.</li>
</ul>
<p>For example:</p>
<div class="break-out">
<pre><code class="language-css">@media screen and (prefers-reduce-motion: no-preference) {
  @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) { 
    .my-class {
      animation: moveCard linear both;    
      animation-timeline: view(); 
    }
  } 
}
</code></pre>
</div>
<p>My main struggle while trying to build the demos was more about CSS itself than the scroll animations. Sometimes, building the layout and generating the scroll was more difficult than applying the scroll animation. Also, some things that confused me at the beginning as the spec keeps evolving, and some of these are not there anymore (remember, it has been under development for more than five years now!):</p>
<ul>
<li><strong>x and y axes</strong><br />
These used to be called the “horizontal” and “vertical” axes, and while Firefox may still support the old terminology, it has been updated.</li>
<li><strong>Old <code>@scroll-timeline</code> syntax</strong><br />
In the past, <code>@scroll-timeline</code> was used to declare scroll timelines, but this has changed in the most recent version of the spec.</li>
<li><strong>Scroll-driven vs. scroll-linked animations</strong><br />
Scroll-<em>driven</em> animations were originally called scroll-<em>linked</em> animations. If you come across this older term in articles, double-check whether the content has been updated to reflect the latest spec, particularly with features like <code>timeline-scope</code>.</li>
</ul>
<h3 id="resources">Resources</h3>
<ul>
<li>All demos from this article can be found <a href="https://codepen.io/collection/WvVpQR">in this collection</a>, and I might include <a href="https://codepen.io/marianab/pen/bGXdEoB">more</a> as I experiment further.</li>
<li>A collection of <a href="https://codepen.io/collection/aMgBZp">demos from CodePen</a> that I find interesting (send me yours, and I’ll include it!)</li>
<li>This <a href="https://github.com/w3c/csswg-drafts/labels/scroll-animations-1">GitHub repo</a> is where you can report issues or join discussions about scroll-driven animations.</li>
<li><a href="https://scroll-driven-animations.style/">Demos, tools, videos</a>, and (even) more information from Bramus</li>
<li>Google Chrome <a href="https://www.youtube.com/playlist?list=PLNYkxOF6rcICM3ttukz9x5LCNOHfWBVnn">video tutorial</a></li>
</ul>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2024/12/11/an-introduction-to-css-scroll-driven-animations-scroll-and-view-progress-timelines/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CSS min() All The Things</title>
		<link>http://computercoursesonline.com/index.php/2024/10/17/css-min-all-the-things/</link>
					<comments>http://computercoursesonline.com/index.php/2024/10/17/css-min-all-the-things/#respond</comments>
		
		<dc:creator><![CDATA[.]]></dc:creator>
		<pubDate>Thu, 17 Oct 2024 10:00:00 +0000</pubDate>
				<category><![CDATA[Css]]></category>
		<guid isPermaLink="false">http://computercoursesonline.com/?p=263</guid>

					<description><![CDATA[CSS min() All The Things CSS min() All The Things Victor Ayomipo 2024-10-17T10:00:00+00:00 2025-05-01T20:32:48+00:00 Did you see this post that Chris Coyier published back in August? He experimented with CSS container query units, going all in and using them for every single numeric value in a demo he put together. And the result was… not...]]></description>
										<content:encoded><![CDATA[<p>              <title>CSS min() All The Things</title></p>
<article>
<header>
<h1>CSS min() All The Things</h1>
<address>Victor Ayomipo</address>
<p>                  2024-10-17T10:00:00+00:00<br />
                  2025-05-01T20:32:48+00:00<br />
                </header>
<p>Did you see <a href="https://frontendmasters.com/blog/what-if-you-used-container-units-for-everything/">this post</a> that Chris Coyier published back in August? He experimented with CSS container query units, going all in and using them for every single numeric value in a demo he put together. And <a href="https://codepen.io/chriscoyier/pen/OJYKLXz">the result</a> was… not too bad, actually.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="ExqWXOQ" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [Container Units for All Units [forked]](https://codepen.io/smashingmag/pen/ExqWXOQ) by <a href="https://codepen.io/chriscoyier">Chris Coyier</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/ExqWXOQ">Container Units for All Units [forked]</a> by <a href="https://codepen.io/chriscoyier">Chris Coyier</a>.</figcaption></figure>
<p>What I found interesting about this is how it demonstrates the complexity of sizing things. We’re constrained to absolute and relative units in CSS, so we’re either stuck at a specific size (e.g., <code>px</code>) or computing the size based on sizing declared on another element (e.g., <code>%</code>, <code>em</code>, <code>rem</code>, <code>vw</code>, <code>vh</code>, and so on). Both come with compromises, so it’s not like there is a “correct” way to go about things &mdash; it’s about the element’s context &mdash; and leaning heavily in any one direction doesn’t remedy that.</p>
<p>I thought I’d try my own experiment but with the CSS <code>min()</code> function instead of container query units. Why? Well, first off, we can supply the function with <strong>any type of length unit we want</strong>, which makes the approach a little more flexible than working with one type of unit. But the real reason I wanted to do this is personal interest more than anything else.</p>
<h2 id="the-demo">The Demo</h2>
<p>I won’t make you wait for the end to see how my <code>min()</code> experiment went:</p>
</p>
<blockquote class="twitter-tweet" data-media-max-width="560">
<p lang="en" dir="ltr">Taking website responsiveness to a whole new level <img src="https://s.w.org/images/core/emoji/13.1.0/72x72/1f310.png" alt="🌐" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://t.co/pKmHl5d0Dy">pic.twitter.com/pKmHl5d0Dy</a></p>
<p>&mdash; Vayo (@vayospot) <a href="https://twitter.com/vayospot/status/1630863145014112257?ref_src=twsrc%5Etfw">March 1, 2023</a></p></blockquote>
<p></p>
<p>We’ll talk about that more after we walk through the details.</p>
<div data-audience="non-subscriber" data-remove="true" class="feature-panel-container">
<aside class="feature-panel">
<div class="feature-panel-left-col">
<div class="feature-panel-description">
<p>Meet <strong><a data-instant href="https://www.smashingconf.com/online-workshops/">Smashing Workshops</a></strong> on <strong>front-end, design &amp; UX</strong>, with practical takeaways, live sessions, <strong>video recordings</strong> and a friendly Q&amp;A. With Brad Frost, Stéph Walter and <a href="https://smashingconf.com/online-workshops/workshops">so many others</a>.</p>
<p><a data-instant href="smashing-workshops" class="btn btn--green btn--large">Jump to the workshops&nbsp;↬</a></div>
</div>
<div class="feature-panel-right-col"><a data-instant href="smashing-workshops" class="feature-panel-image-link"></p>
<div class="feature-panel-image">
<img loading="lazy" class="feature-panel-image-img lazyload" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Feature Panel" width="257" height="355" data-src="/images/smashing-cat/cat-scubadiving-panel.svg"></p>
</div>
<p></a>
</div>
</aside>
</div>
<h2 id="a-little-about-min">A Little About <code>min()</code></h2>
<p>The <code>min()</code> function takes two values and applies the smallest one, whichever one happens to be in the element’s context. For example, we can say we want an element to be as wide as <code>50%</code> of whatever container it is in. And if <code>50%</code> is <em>greater</em> than, say <code>200px</code>, cap the width there instead.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="LYwWLMg" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [[forked]](https://codepen.io/smashingmag/pen/LYwWLMg) by <a href="https://codepen.io/geoffgraham">Geoff Graham</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/LYwWLMg">[forked]</a> by <a href="https://codepen.io/geoffgraham">Geoff Graham</a>.</figcaption></figure>
<p>So, <code>min()</code> is sort of like container query units in the sense that it is aware of how much available space it has in its container. But it’s different in that <code>min()</code> isn’t querying its container dimensions to compute the final value. We supply it with two acceptable lengths, and it determines which is best given the context. That makes <code>min()</code> (and <code>max()</code> for that matter) <strong>a useful tool for responsive layouts that adapt to the viewport’s size</strong>. It uses conditional logic to determine the “best” match, which means it can help adapt layouts without needing to reach for CSS media queries.</p>
<pre><code class="language-css">.element {
  width: min(200px, 50%);
}

/&#042; Close to this: &#042;/
.element {
  width: 200px;

  @media (min-width: 600px) {
    width: 50%;
  }
}
</code></pre>
<p>The difference between <code>min()</code> and <code>@media</code> in that example is that we’re telling the browser to set the element’s width to <code>50%</code> at a specific <em>breakpoint</em> of <code>600px</code>. With <code>min()</code>, it switches things up automatically as the amount of available space changes, whatever viewport size that happens to be.</p>
<p>When I use the <code>min()</code>, I think of it as having the ability to make smart decisions based on context. We don’t have to do the thinking or calculations to determine which value is used. However, using <code>min()</code> coupled with just any CSS unit isn’t enough. For instance, relative units work better for responsiveness than absolute units. You might even think of <code>min()</code> as <a href="https://css-tricks.com/min-max-and-clamp-are-css-magic/">setting a <em>maximum</em> value</a> in that it never goes below the first value but also caps itself at the second value.</p>
<p>I mentioned earlier that we could use any type of unit in <code>min()</code>. Let’s take the same approach that Chris did and lean heavily into a type of unit to see how <code>min()</code> behaves when it is used exclusively for a responsive layout. Specifically, we’ll use <strong>viewport units</strong> as they are directly relative to the size of the viewport.</p>
<p>Now, there are different flavors of viewport units. We can use the viewport’s width (<code>vw</code>) and height (<code>vh</code>). We also have the <code>vmin</code> and <code>vmax</code> units that are slightly more intelligent in that they evaluate an element’s width and height and apply either the smaller (<code>vmin</code>) or larger (<code>vmax</code>) of the two. So, if we declare <code>100vmax</code> on an element, and that element is <code>500px</code> wide by <code>250px</code> tall, the unit computes to <code>500px</code>.</p>
<p>That is how I am approaching this experiment. What happens if we eschew media queries in favor of only using <code>min()</code> to establish a responsive layout and lean into viewport units to make it happen? We’ll take it one piece at a time.</p>
<h2 id="font-sizing">Font Sizing</h2>
<p>There are various approaches for responsive type. Media queries are quickly becoming the “old school” way of doing it:</p>
<pre><code class="language-css">p { font-size: 1.1rem; }

@media (min-width: 1200px) {
  p { font-size: 1.2rem; }
}

@media (max-width: 350px) {
  p { font-size: 0.9rem; }
}
</code></pre>
<p>Sure, this works, but what happens when the user uses a 4K monitor? Or a foldable phone? There are other tried and true approaches; in fact, <a href="https://www.smashingmagazine.com/2023/11/addressing-accessibility-concerns-fluid-type/"><code>clamp()</code> is the prevailing go-to</a>. But we’re leaning all-in on <code>min()</code>. As it happens, just one line of code is all we need to wipe out all of those media queries, substantially reducing our code:</p>
<pre><code class="language-css">p { font-size: min(6vmin, calc(1rem + 0.23vmax)); }
</code></pre>
<p>I’ll walk you through those values…</p>
<ol>
<li><code>6vmin</code> is essentially 6% of the browser’s width or height, whichever is smallest. This allows the font size to shrink as much as needed for smaller contexts.</li>
<li>For <code>calc(1rem + 0.23vmax)</code>, <code>1rem</code> is the base font size, and <code>0.23vmax</code> is a tiny fraction of the viewport‘s width or height, whichever happens to be the largest.</li>
<li>The <code>calc()</code> function adds those two values together. Since <code>0.23vmax</code> is evaluated differently depending on which viewport edge is the largest, it’s crucial when it comes to scaling the font size between the two arguments. I’ve tweaked it into something that scales gradually one way or the other rather than blowing things up as the viewport size increases.</li>
<li>Finally, the <code>min()</code> returns the smallest value suitable for the font size of the current screen size.</li>
</ol>
<p>And speaking of how flexible the <code>min()</code> approach is, it can restrict how far the text grows. For example, we can cap this at a maximum <code>font-size</code> equal to <code>2rem</code> as a third function parameter:</p>
<pre><code class="language-css">p { font-size: min(6vmin, calc(1rem + 0.23vmax), 2rem); }
</code></pre>
<p>This isn’t a silver bullet tactic. I’d say it’s probably best used for body text, like paragraphs. We might want to adjust things a smidge for headings, e.g., <code>&lt;h1&gt;</code>:</p>
<pre><code class="language-css">h1 { font-size: min(7.5vmin, calc(2rem + 1.2vmax)); }
</code></pre>
<p>We’ve bumped up the minimum size from <code>6vmin</code> to <code>7.5vmin</code> so that it stays larger than the body text at any viewport size. Also, in the <code>calc()</code>, the base size is now <code>2rem</code>, which is smaller than the default UA styles for <code>&lt;h1&gt;</code>. We’re using <code>1.2vmax</code> as the multiplier this time, meaning it grows more than the body text, which is multiplied by a smaller value, <code>.023vmax</code>.</p>
<p>This works for me. You can always tweak these values and see which works best for your use. Whatever the case, the <code>font-size</code> for this experiment is completely fluid and completely based on the <code>min()</code> function, adhering to my self-imposed constraint.</p>
<div class="partners__lead-place"></div>
<h2 id="margin-and-padding">Margin And Padding</h2>
<p>Spacing is a big part of layout, responsive or not. We need <code>margin</code> and <code>padding</code> to properly situate elements alongside other elements and give them breathing room, both inside and outside their box.</p>
<p>We’re going all-in with <code>min()</code> for this, too. We could use absolute units, like pixels, but those aren’t exactly responsive.</p>
<p><code>min()</code> can combine relative and absolute units so they are more effective. Let’s pair <code>vmin</code> with <code>px</code> this time:</p>
<pre><code class="language-css">div { margin: min(10vmin, 30px); }
</code></pre>
<p><code>10vmin</code> is likely to be smaller than <code>30px</code> when viewed on a small viewport. That’s why I’m allowing the margin to shrink dynamically this time around. As the viewport size increases, whereby <code>10vmin</code> exceeds <code>30px</code>, <code>min()</code> caps the value at <code>30px</code>, going no higher than that.</p>
<p>Notice, too, that I didn’t reach for <code>calc()</code> this time. Margins don’t really need to grow indefinitely with screen size, as too much spacing between containers or elements generally looks awkward on larger screens. This concept also works extremely well for padding, but we don’t have to go there. Instead, it might be better to stick with a single unit, preferably <code>em</code>, since it is relative to the element’s <code>font-size</code>. We can essentially “pass” the work that <code>min()</code> is doing on the <code>font-size</code> to the <code>margin</code> and <code>padding</code> properties because of that.</p>
<pre><code class="language-css">.card-info {
  font-size: min(6vmin, calc(1rem + 0.12vmax));
  padding: 1.2em;
}
</code></pre>
<p>Now, padding scales with the <code>font-size</code>, which is powered by <code>min()</code>.</p>
<h2 id="widths">Widths</h2>
<p>Setting <code>width</code> for a responsive design doesn’t have to be complicated, right? We could simply use a single percentage or viewport unit value to specify how much available horizontal space we want to take up, and the element will adjust accordingly. Though, container query units could be a happy path outside of this experiment.</p>
<p>But we’re <code>min()</code> all the way!</p>
<p><code>min()</code> comes in handy when setting constraints on how much an element responds to changes. We can set an upper limit of <code>650px</code> and, if the computed width tries to go larger, have the element settle at a full width of <code>100%</code>:</p>
<pre><code class="language-css">.container { width: min(100%, 650px); }
</code></pre>
<p>Things get interesting with text width. When the width of a text box is too long, it becomes uncomfortable to read through the texts. There are competing theories about how many characters per line of text is best for an optimal reading experience. For the sake of argument, let’s say that number should be between 50-75 characters. In other words, we ought to pack no more than 75 characters on a line, and we can do that with the <code>ch</code> unit, which is based on the <code>0</code> character’s size for whatever font is in use.</p>
<pre><code class="language-css">p {
  width: min(100%, 75ch);
}
</code></pre>
<p>This code basically says: <em>get as wide as needed but never wider than 75 characters.</em></p>
<div class="partners__lead-place"></div>
<h2 id="sizing-recipes-based-on-min">Sizing Recipes Based On <code>min()</code></h2>
<p>Over time, with a lot of tweaking and modifying of values, I have drafted a list of pre-defined values that I find work well for responsively styling different properties:</p>
<pre><code class="language-css">:root {
  --font-size-6x: min(7.5vmin, calc(2rem + 1.2vmax));
  --font-size-5x: min(6.5vmin, calc(1.1rem + 1.2vmax));
  --font-size-4x: min(4vmin, calc(0.8rem + 1.2vmax));
  --font-size-3x: min(6vmin, calc(1rem + 0.12vmax));
  --font-size-2x: min(4vmin, calc(0.85rem + 0.12vmax));
  --font-size-1x: min(2vmin, calc(0.65rem + 0.12vmax));
  --width-2x: min(100vw, 1300px);
  --width-1x: min(100%, 1200px);
  --gap-3x: min(5vmin, 1.5rem);
  --gap-2x: min(4.5vmin, 1rem);
  --size-10x: min(15vmin, 5.5rem);
  --size-9x: min(10vmin, 5rem);
  --size-8x: min(10vmin, 4rem);
  --size-7x: min(10vmin, 3rem);
  --size-6x: min(8.5vmin, 2.5rem);
  --size-5x: min(8vmin, 2rem);
  --size-4x: min(8vmin, 1.5rem);
  --size-3x: min(7vmin, 1rem);
  --size-2x: min(5vmin, 1rem);
  --size-1x: min(2.5vmin, 0.5rem);
}
</code></pre>
<p>This is how I approached my experiment because it helps me know what to reach for in a given situation:</p>
<pre><code class="language-css">h1 { font-size: var(--font-size-6x); }

.container {
  width: var(--width-2x);
  margin: var(--size-2x);
}

.card-grid { gap: var(--gap-3x); }
</code></pre>
<p>There we go! We have a heading that scales flawlessly, a container that’s responsive and never too wide, and a grid with dynamic spacing &mdash; all without a single media query. The <code>--size-</code> properties declared in the variable list are the most versatile, as they can be used for properties that require scaling, e.g., margins, paddings, and so on.</p>
<h2 id="the-final-result-again">The Final Result, Again</h2>
<p>I shared a video of the result, but here’s a link to the demo.</p>
<figure class="break-out">
<p data-height="480" data-theme-id="light" data-slug-hash="wvVdPxL" data-user="smashingmag" data-default-tab="result" class="codepen">See the Pen [min() website [forked]](https://codepen.io/smashingmag/pen/wvVdPxL) by <a href="https://codepen.io/vayospot">Vayo</a>.</p><figcaption>See the Pen <a href="https://codepen.io/smashingmag/pen/wvVdPxL">min() website [forked]</a> by <a href="https://codepen.io/vayospot">Vayo</a>.</figcaption></figure>
<p>So, is <code>min()</code> the be-all, end-all for responsiveness? Absolutely not. Neither is a diet consisting entirely of container query units. I mean, it’s cool that we can scale an entire webpage like this, but the web is never a one-size-fits-all beanie.</p>
<p>If anything, I think this and what Chris demoed are <strong>warnings against dogmatic approaches to web design</strong> as a whole, not solely unique to responsive design. CSS features, including length units and functions, are tools in a larger virtual toolshed. Rather than getting too cozy with one feature or technique, explore the shed because you might find a better tool for the job.</p>
<div class="signature">
  <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="Smashing Editorial" width="35" height="46" loading="lazy" class="lazyload" data-src="https://www.smashingmagazine.com/images/logo/logo--red.png"><br />
  <span>(gg, yk)</span>
</div>
</article>
]]></content:encoded>
					
					<wfw:commentRss>http://computercoursesonline.com/index.php/2024/10/17/css-min-all-the-things/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
