• Smashing Animations Part 4: Optimising SVGs

    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 CSS, SVG, and SMIL animations.
    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.

    Start Clean And Design With Optimisation In Mind
    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.
    Tip: Affinity Designerand Sketchare alternatives to Adobe Illustrator and Figma. Both are independent and based in Europe. Sketch has been my default design app since Adobe killed Fireworks.

    Beginning With Outlines
    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.

    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.

    Drawing Simple Background Shapes
    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.

    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.

    Optimising The Code
    It’s not just metadata that makes SVG bulkier. The way you export from your design app also affects file size.

    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 SVGOMG, which uses SVGO v3 and consistently delivers the best optimised SVGs.

    Layering SVG Elements
    My process for preparing SVGs for animation goes well beyond drawing vectors and optimising paths — it also includes how I structure the code 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.

    That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time — 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.

    Instead of facing a wall of SVG code, I can now easily identify the background gradient’s path and its associated linearGradient, 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:
    <svg ...>
    <defs>
    <!-- ... -->
    </defs>
    <path fill="url" d="…"/>
    <!-- TITLE GRAPHIC -->
    <g>
    <path … />
    <!-- ... -->
    </g>
    </svg>

    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:
    <svg ...>
    <defs>
    <linearGradient id="grad" …>…</linearGradient>
    <filter id="trail" …>…</filter>
    </defs>
    <!-- GRADIENT -->
    <!-- TRAIL -->
    <path filter="url" …/>
    <!-- TITLE GRAPHIC -->
    </svg>

    Then come the magical stars, added in the same sequential fashion:
    <svg ...>
    <!-- GRADIENT -->
    <!-- TRAIL -->
    <!-- STARS -->
    <!-- TITLE GRAPHIC -->
    </svg>

    To keep everything organised and animation-ready, I create an empty group that will hold all the parts of Yogi:
    <g id="yogi">...</g>

    Then I build Yogi from the ground up — starting with background props, like his broom:
    <g id="broom">...</g>

    Followed by grouped elements for his body, head, collar, and tie:
    <g id="yogi">
    <g id="broom">…</g>
    <g id="body">…</g>
    <g id="head">…</g>
    <g id="collar">…</g>
    <g id="tie">…</g>
    </g>

    Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues later on — they’ll all slot into place automatically. I keep my code clean, readable, and ordered logically by layering elements this way. It also makes animating smoother, as each component is easier to identify.
    Reusing Elements With <use>
    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:
    <g id="stars">
    <path class="star-small" fill="#eae3da" d="..."/>
    <path class="star-medium" fill="#eae3da" d="..."/>
    <path class="star-large" fill="#eae3da" d="..."/>
    <!-- ... -->
    </g>

    Moving the stars’ fill attribute values to their parent group reduces the overall weight a little:
    <g id="stars" fill="#eae3da">
    <path class="star-small" d="…"/>
    <path class="star-medium" d="…"/>
    <path class="star-large" d="…"/>
    <!-- ... -->
    </g>

    But a more efficient and manageable option is to define each star size as a reusable template:

    <defs>
    <path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/>
    <path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/>
    <path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/>
    </defs>

    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 <use> and position them with x and y attributes:
    <g id="stars">
    <!-- Large stars -->
    <use href="#star-large" x="1575" y="495"/>
    <!-- ... -->
    <!-- Medium stars -->
    <use href="#star-medium" x="1453" y="696"/>
    <!-- ... -->
    <!-- Small stars -->
    <use href="#star-small" x="1287" y="741"/>
    <!-- ... -->
    </g>

    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 without compromising on flexibility or performance.
    Adding Animations
    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 opacity levels:
    @keyframes sparkle {
    0%, 100% { opacity: .1; }
    50% { opacity: 1; }
    }

    Next, I applied this looping animation to every use element inside my stars group:
    #stars use {
    animation: sparkle 10s ease-in-out infinite;
    }

    The secret to creating a convincing twinkle lies in variation. I staggered animation delays and durations across the stars using nth-child selectors, starting with the quickest and most frequent sparkle effects:
    /* Fast, frequent */
    #stars use:nth-child:nth-child{
    animation-delay: .1s;
    animation-duration: 2s;
    }

    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:
    /* Medium */
    #stars use:nth-child:nth-child{ ... }

    /* Slow, dramatic */
    #stars use:nth-child:nth-child{ ... }

    /* Random */
    #stars use:nth-child{ ... }

    /* Alternating */
    #stars use:nth-child{ ... }

    /* Scattered */
    #stars use:nth-child{ ... }

    By thoughtfully structuring the SVG and reusing elements, I can build complex-looking animations without bloated code, making even a simple effect like changing opacity sparkle.

    Then, for added realism, I make Yogi’s head wobble:

    @keyframes headWobble {
    0% { transform: rotatetranslateY; }
    100% { transform: rotatetranslateY; }
    }

    #head {
    animation: headWobble 0.8s cubic-bezierinfinite alternate;
    }

    His tie waves:

    @keyframes tieWave {
    0%, 100% { transform: rotateZrotateYscaleX; }
    33% { transform: rotateZrotateYscaleX; }
    66% { transform: rotateZrotateYscaleX; }
    }

    #tie {
    transform-style: preserve-3d;
    animation: tieWave 10s cubic-bezierinfinite;
    }

    His broom swings:

    @keyframes broomSwing {
    0%, 20% { transform: rotate; }
    30% { transform: rotate; }
    50%, 70% { transform: rotate; }
    80% { transform: rotate; }
    100% { transform: rotate; }
    }

    #broom {
    animation: broomSwing 4s cubic-bezierinfinite;
    }

    And, finally, Yogi himself gently rotates as he flies on his magical broom:

    @keyframes yogiWobble {
    0% { transform: rotatetranslateYscale; }
    30% { transform: rotatetranslateY; }
    100% { transform: rotatetranslateYscale; }
    }

    #yogi {
    animation: yogiWobble 3.5s cubic-bezierinfinite alternate;
    }

    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.
    Try this yourself:
    See the Pen Bewitched Bear CSS/SVG animationby Andy Clarke.
    Conclusion
    Whether you’re recreating a classic title card or animating icons for an interface, the principles are the same:

    Start clean,
    Optimise early, and
    Structure everything with animation in mind.

    SVGs offer incredible creative freedom, but only if kept lean and manageable. When you plan your process like a production cell — layer by layer, element by element — you’ll spend less time untangling code and more time bringing your work to life.
    #smashing #animations #part #optimising #svgs
    Smashing Animations Part 4: Optimising SVGs
    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 CSS, SVG, and SMIL animations. 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. Start Clean And Design With Optimisation In Mind 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. Tip: Affinity Designerand Sketchare alternatives to Adobe Illustrator and Figma. Both are independent and based in Europe. Sketch has been my default design app since Adobe killed Fireworks. Beginning With Outlines 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. 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. Drawing Simple Background Shapes 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. 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. Optimising The Code It’s not just metadata that makes SVG bulkier. The way you export from your design app also affects file size. 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 SVGOMG, which uses SVGO v3 and consistently delivers the best optimised SVGs. Layering SVG Elements My process for preparing SVGs for animation goes well beyond drawing vectors and optimising paths — it also includes how I structure the code 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. That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time — 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. Instead of facing a wall of SVG code, I can now easily identify the background gradient’s path and its associated linearGradient, 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: <svg ...> <defs> <!-- ... --> </defs> <path fill="url" d="…"/> <!-- TITLE GRAPHIC --> <g> <path … /> <!-- ... --> </g> </svg> 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: <svg ...> <defs> <linearGradient id="grad" …>…</linearGradient> <filter id="trail" …>…</filter> </defs> <!-- GRADIENT --> <!-- TRAIL --> <path filter="url" …/> <!-- TITLE GRAPHIC --> </svg> Then come the magical stars, added in the same sequential fashion: <svg ...> <!-- GRADIENT --> <!-- TRAIL --> <!-- STARS --> <!-- TITLE GRAPHIC --> </svg> To keep everything organised and animation-ready, I create an empty group that will hold all the parts of Yogi: <g id="yogi">...</g> Then I build Yogi from the ground up — starting with background props, like his broom: <g id="broom">...</g> Followed by grouped elements for his body, head, collar, and tie: <g id="yogi"> <g id="broom">…</g> <g id="body">…</g> <g id="head">…</g> <g id="collar">…</g> <g id="tie">…</g> </g> Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues later on — they’ll all slot into place automatically. I keep my code clean, readable, and ordered logically by layering elements this way. It also makes animating smoother, as each component is easier to identify. Reusing Elements With <use> 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: <g id="stars"> <path class="star-small" fill="#eae3da" d="..."/> <path class="star-medium" fill="#eae3da" d="..."/> <path class="star-large" fill="#eae3da" d="..."/> <!-- ... --> </g> Moving the stars’ fill attribute values to their parent group reduces the overall weight a little: <g id="stars" fill="#eae3da"> <path class="star-small" d="…"/> <path class="star-medium" d="…"/> <path class="star-large" d="…"/> <!-- ... --> </g> But a more efficient and manageable option is to define each star size as a reusable template: <defs> <path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/> <path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/> <path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/> </defs> 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 <use> and position them with x and y attributes: <g id="stars"> <!-- Large stars --> <use href="#star-large" x="1575" y="495"/> <!-- ... --> <!-- Medium stars --> <use href="#star-medium" x="1453" y="696"/> <!-- ... --> <!-- Small stars --> <use href="#star-small" x="1287" y="741"/> <!-- ... --> </g> 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 without compromising on flexibility or performance. Adding Animations 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 opacity levels: @keyframes sparkle { 0%, 100% { opacity: .1; } 50% { opacity: 1; } } Next, I applied this looping animation to every use element inside my stars group: #stars use { animation: sparkle 10s ease-in-out infinite; } The secret to creating a convincing twinkle lies in variation. I staggered animation delays and durations across the stars using nth-child selectors, starting with the quickest and most frequent sparkle effects: /* Fast, frequent */ #stars use:nth-child:nth-child{ animation-delay: .1s; animation-duration: 2s; } 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: /* Medium */ #stars use:nth-child:nth-child{ ... } /* Slow, dramatic */ #stars use:nth-child:nth-child{ ... } /* Random */ #stars use:nth-child{ ... } /* Alternating */ #stars use:nth-child{ ... } /* Scattered */ #stars use:nth-child{ ... } By thoughtfully structuring the SVG and reusing elements, I can build complex-looking animations without bloated code, making even a simple effect like changing opacity sparkle. Then, for added realism, I make Yogi’s head wobble: @keyframes headWobble { 0% { transform: rotatetranslateY; } 100% { transform: rotatetranslateY; } } #head { animation: headWobble 0.8s cubic-bezierinfinite alternate; } His tie waves: @keyframes tieWave { 0%, 100% { transform: rotateZrotateYscaleX; } 33% { transform: rotateZrotateYscaleX; } 66% { transform: rotateZrotateYscaleX; } } #tie { transform-style: preserve-3d; animation: tieWave 10s cubic-bezierinfinite; } His broom swings: @keyframes broomSwing { 0%, 20% { transform: rotate; } 30% { transform: rotate; } 50%, 70% { transform: rotate; } 80% { transform: rotate; } 100% { transform: rotate; } } #broom { animation: broomSwing 4s cubic-bezierinfinite; } And, finally, Yogi himself gently rotates as he flies on his magical broom: @keyframes yogiWobble { 0% { transform: rotatetranslateYscale; } 30% { transform: rotatetranslateY; } 100% { transform: rotatetranslateYscale; } } #yogi { animation: yogiWobble 3.5s cubic-bezierinfinite alternate; } 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. Try this yourself: See the Pen Bewitched Bear CSS/SVG animationby Andy Clarke. Conclusion Whether you’re recreating a classic title card or animating icons for an interface, the principles are the same: Start clean, Optimise early, and Structure everything with animation in mind. SVGs offer incredible creative freedom, but only if kept lean and manageable. When you plan your process like a production cell — layer by layer, element by element — you’ll spend less time untangling code and more time bringing your work to life. #smashing #animations #part #optimising #svgs
    SMASHINGMAGAZINE.COM
    Smashing Animations Part 4: Optimising SVGs
    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 CSS, SVG, and SMIL animations. 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. Start Clean And Design With Optimisation In Mind 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. Tip: Affinity Designer (UK) and Sketch (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. Beginning With Outlines 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. 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. Drawing Simple Background Shapes 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. 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. Optimising The Code It’s not just metadata that makes SVG bulkier. The way you export from your design app also affects file size. 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 SVGOMG, which uses SVGO v3 and consistently delivers the best optimised SVGs. Layering SVG Elements My process for preparing SVGs for animation goes well beyond drawing vectors and optimising paths — it also includes how I structure the code 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. That’s why I develop my SVGs in layers, exporting and optimising one set of elements at a time — 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. Instead of facing a wall of SVG code, I can now easily identify the background gradient’s path and its associated linearGradient, 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: <svg ...> <defs> <!-- ... --> </defs> <path fill="url(#grad)" d="…"/> <!-- TITLE GRAPHIC --> <g> <path … /> <!-- ... --> </g> </svg> 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: <svg ...> <defs> <linearGradient id="grad" …>…</linearGradient> <filter id="trail" …>…</filter> </defs> <!-- GRADIENT --> <!-- TRAIL --> <path filter="url(#trail)" …/> <!-- TITLE GRAPHIC --> </svg> Then come the magical stars, added in the same sequential fashion: <svg ...> <!-- GRADIENT --> <!-- TRAIL --> <!-- STARS --> <!-- TITLE GRAPHIC --> </svg> To keep everything organised and animation-ready, I create an empty group that will hold all the parts of Yogi: <g id="yogi">...</g> Then I build Yogi from the ground up — starting with background props, like his broom: <g id="broom">...</g> Followed by grouped elements for his body, head, collar, and tie: <g id="yogi"> <g id="broom">…</g> <g id="body">…</g> <g id="head">…</g> <g id="collar">…</g> <g id="tie">…</g> </g> Since I export each layer from the same-sized artboard, I don’t need to worry about alignment or positioning issues later on — they’ll all slot into place automatically. I keep my code clean, readable, and ordered logically by layering elements this way. It also makes animating smoother, as each component is easier to identify. Reusing Elements With <use> 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: <g id="stars"> <path class="star-small" fill="#eae3da" d="..."/> <path class="star-medium" fill="#eae3da" d="..."/> <path class="star-large" fill="#eae3da" d="..."/> <!-- ... --> </g> Moving the stars’ fill attribute values to their parent group reduces the overall weight a little: <g id="stars" fill="#eae3da"> <path class="star-small" d="…"/> <path class="star-medium" d="…"/> <path class="star-large" d="…"/> <!-- ... --> </g> But a more efficient and manageable option is to define each star size as a reusable template: <defs> <path id="star-large" fill="#eae3da" fill-rule="evenodd" d="…"/> <path id="star-medium" fill="#eae3da" fill-rule="evenodd" d="…"/> <path id="star-small" fill="#eae3da" fill-rule="evenodd" d="…"/> </defs> 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 <use> and position them with x and y attributes: <g id="stars"> <!-- Large stars --> <use href="#star-large" x="1575" y="495"/> <!-- ... --> <!-- Medium stars --> <use href="#star-medium" x="1453" y="696"/> <!-- ... --> <!-- Small stars --> <use href="#star-small" x="1287" y="741"/> <!-- ... --> </g> 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 without compromising on flexibility or performance. Adding Animations 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 opacity levels: @keyframes sparkle { 0%, 100% { opacity: .1; } 50% { opacity: 1; } } Next, I applied this looping animation to every use element inside my stars group: #stars use { animation: sparkle 10s ease-in-out infinite; } The secret to creating a convincing twinkle lies in variation. I staggered animation delays and durations across the stars using nth-child selectors, starting with the quickest and most frequent sparkle effects: /* Fast, frequent */ #stars use:nth-child(n + 1):nth-child(-n + 10) { animation-delay: .1s; animation-duration: 2s; } 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: /* Medium */ #stars use:nth-child(n + 11):nth-child(-n + 20) { ... } /* Slow, dramatic */ #stars use:nth-child(n + 21):nth-child(-n + 30) { ... } /* Random */ #stars use:nth-child(3n + 2) { ... } /* Alternating */ #stars use:nth-child(4n + 1) { ... } /* Scattered */ #stars use:nth-child(n + 31) { ... } By thoughtfully structuring the SVG and reusing elements, I can build complex-looking animations without bloated code, making even a simple effect like changing opacity sparkle. Then, for added realism, I make Yogi’s head wobble: @keyframes headWobble { 0% { transform: rotate(-0.8deg) translateY(-0.5px); } 100% { transform: rotate(0.9deg) translateY(0.3px); } } #head { animation: headWobble 0.8s cubic-bezier(0.5, 0.15, 0.5, 0.85) infinite alternate; } His tie waves: @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); } } #tie { transform-style: preserve-3d; animation: tieWave 10s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite; } His broom swings: @keyframes broomSwing { 0%, 20% { transform: rotate(-5deg); } 30% { transform: rotate(-4deg); } 50%, 70% { transform: rotate(5deg); } 80% { transform: rotate(4deg); } 100% { transform: rotate(-5deg); } } #broom { animation: broomSwing 4s cubic-bezier(0.5, 0.05, 0.5, 0.95) infinite; } And, finally, Yogi himself gently rotates as he flies on his magical broom: @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); } } #yogi { animation: yogiWobble 3.5s cubic-bezier(.37, .14, .3, .86) infinite alternate; } 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. Try this yourself: See the Pen Bewitched Bear CSS/SVG animation [forked] by Andy Clarke. Conclusion Whether you’re recreating a classic title card or animating icons for an interface, the principles are the same: Start clean, Optimise early, and Structure everything with animation in mind. SVGs offer incredible creative freedom, but only if kept lean and manageable. When you plan your process like a production cell — layer by layer, element by element — you’ll spend less time untangling code and more time bringing your work to life.
    Like
    Love
    Wow
    Angry
    Sad
    273
    0 Yorumlar 0 hisse senetleri 0 önizleme
  • Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead

    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.
    Now, you might’ve heard that SMIL is dead. 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.

    Tip: There’s now a website where you can see all my Toon Titles.

    Mike loves ’90s animation — especially Disney’s) Duck Tales). Unsurprisingly, my taste in cartoons stretches back a little further to Hanna-Barbera shows like Dastardly and Muttley in Their Flying Machines, Scooby-Doo, The Perils of Penelope Pitstop, Wacky Races, and, of course, The Yogi Bear Show. 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.

    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.

    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.

    Scaling makes an element grow, shrink, or stretch, which can add drama, create perspective, or simulate depth.

    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.
    These results are all achievable using CSS animations, but some SVG properties can’t be animated using CSS. Luckily, we can do more — and have much more fun — using SMIL animations in SVG. We can combine complex animations, move objects along paths, and control when they start, stop, and everything in between.
    Animations can be embedded within any SVG element, including primitive shapes like circles, ellipses, and rectangles. They can also be encapsulated into groups, paths, and polygons:
    <circle ...>
    <animate>...</animate>
    </circle>

    Animations can also be defined outside an element, elsewhere in an SVG, and connected to it using an xlink attribute:
    <g id="yogi">...</g>
    ...
    <animate xlink:href="#yogi">…</animate>

    Building An Animation
    <animate> is just one of several animation elements in SVG. Together with an attributeName value, it enables animations based on one or more of an element’s attributes.
    Most animation explanations start by moving a primitive shape, like this exciting circle:
    <circle
    r="50"
    cx="50"
    cy="50"
    fill="#062326"
    opacity="1"
    />

    Using this attributeName property, I can define which of this circle’s attributes I want to animate, which, in this example, is its cxposition:
    <circle ... >
    <animate attributename="cx"></animate>
    </circle>

    On its own, this does precisely nothing until I define three more values. The from keyword specifies the circle’s initial position, to, its final position, and the dur-ation between those two positions:
    <circle ... >
    <animate
    attributename="cx"
    from="50"
    to="500"
    dur="1s">
    </animate>
    </circle>

    If I want more precise control, I can replace from and to with a set of values separated by semicolons:
    <circle ... >
    <animate
    attributename="cx"
    values="50; 250; 500; 250;"
    dur="1s">
    </animate>
    </circle>

    Finally, I can define how many times the animation repeatsand even after what period that repeating should stop:
    <circle ... >
    <animate
    attributename="cx"
    values="50; 250; 500; 250;"
    dur="1s"
    repeatcount="indefinite"
    repeatdur="180s">
    </circle>

    Most SVG elements have attributes that can be animated. This title card from 1959’s “Brainy Bear” episode shows Yogi in a crazy scientist‘s brain experiment. Yogi’s head is under the dome, and energy radiates around him.

    To create the buzz around Yogi, my SVG includes three path elements, each with opacity, stroke, and stroke-width attributes, which can all be animated:
    <path opacity="1" stroke="#fff" stroke-width="5" ... />

    I animated each path’s opacity, changing its value from 1 to .5 and back again:
    <path opacity="1" ... >
    <animate
    attributename="opacity"
    values="1; .25; 1;"
    dur="1s"
    repeatcount="indefinite">
    </animate>
    </path>

    Then, to radiate energy from Yogi, I specified when each animation should begin, using a different value for each path:
    <path ... >
    <animate begin="0" … >
    </path>

    <path ... >
    <animate begin=".5s" … >
    </path>

    <path ... >
    <animate begin="1s" … >
    </path>

    I’ll explain more about the begin property and how to start animations after this short commercial break.
    Try this yourself:

    I needed two types of transform animations to generate the effect of Yogi drifting gently downwards: translate, and rotate. I first added an animatetransform element to the group, which contains Yogi and his chute. I defined his initial vertical position — 1200 off the top of the viewBox — then translated his descent to 1000 over a 15-second duration:
    <g transform="translate">
    ...
    <animateTransform
    attributeName="transform"
    type="translate"
    values="500,-1200; 500,1000"
    dur="15s"
    repeatCount="1"
    />
    </g>

    Yogi appears to fall from the sky, but the movement looks unrealistic. So, I added a second animatetransform element, this time with an indefinitely repeating +/- 5-degree rotation to swing Yogi from side to side during his descent:
    <animateTransform
    attributeName="transform"
    type="rotate"
    values="-5; 5; -5"
    dur="14s"
    repeatCount="indefinite"
    additive="sum"
    />

    Try this yourself:

    By default, the arrow is set loose when the page loads. Blink, and you might miss it. To build some anticipation, I can begin the animation two seconds later:
    <animatetransform
    attributename="transform"
    type="translate"
    from="0 0"
    to="750 0"
    dur=".25s"
    begin="2s"
    fill="freeze"
    />

    Or, I can let the viewer take the shot when they click the arrow:
    <animatetransform
    ...
    begin="click"
    />

    And I can combine the click event and a delay, all with no JavaScript, just a smattering of SMIL:
    <animatetransform
    ...
    begin="click + .5s"
    />

    Try this yourself by clicking the arrow:

    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 viewBox:
    <g class="dog" transform="translate">
    ...
    </g>

    <g class="yogi" transform="translate">
    ...
    </g>

    Then, I applied an animatetransform element to both groups, which moves them back into view:
    <!-- yogi -->
    <animateTransform
    attributeName="transform"
    type="translate"
    from="-1000,0"
    to="0,0"
    dur="2s"
    fill="freeze"
    />

    <!-- dog -->
    <animateTransform
    attributeName="transform"
    type="translate"
    from="-1000,0"
    to="0,0"
    dur=".5s"
    fill="freeze"
    />

    This sets up the action, but the effect feels flat, so I added another pair of animations that bounce both characters:
    <!-- yogi -->
    <animateTransform
    attributeName="transform"
    type="rotate"
    values="-1,0,450; 1,0,450; -1,0,450"
    dur=".25s"
    repeatCount="indefinite"
    />

    <!-- dog -->
    <animateTransform
    attributeName="transform"
    type="rotate"
    values="-1,0,450; 1,0,450; -1,0,450"
    dur="0.5s"
    repeatCount="indefinite"
    />

    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.
    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 translate animation:
    <animateTransform
    id="yogi"
    type="translate"
    ...
    />

    Watch out: 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.

    Then, I applied a begin to his rotate animation, which starts playing a half-second after the #yogi animation ends:
    <animateTransform
    type="rotate"
    begin="yogi.end + .5s"
    ...
    />

    I can build sophisticated sets of synchronised animations using the begin property and whether a named animation begins or ends. The bulldog chasing Yogi enters the frame two seconds after Yogi begins his entrance:
    <animateTransform
    id="dog"
    type="translate"
    begin="yogi.begin + 2s"
    fill="freeze"
    ...
    />

    One second after the dog has caught up with Yogi, a rotate transformation makes him bounce, too:
    <animateTransform
    type="rotate"
    ...
    begin="dog.begin + 1s"
    repeatCount="indefinite"
    />

    The background rectangles whizzing past are also synchronised, this time to one second before the bulldog ends his run:
    <rect ...>
    <animateTransform
    begin="dog.end + -1s"
    />
    </rect>

    Try this yourself:

    In “The Runaway Bear” 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.
    I first added a path property, using its coordinate data to give Yogi a route to follow, and specified a two-second duration for my animation:
    <g>
    <animateMotion
    dur="2s"
    path="..."
    >
    </animateMotion>
    </g>

    Alternatively, I could add a path element, leave it visible, or prevent it from being rendered by placing it inside a defs element:
    <defs>
    <path id="yogi" d="..." />
    </defs>

    I can then reference that by using a mpath element inside my animateMotion:
    <animateMotion
    ...
    <mpath href="#yogi" />
    </animateMotion>

    I experimented with several paths before settling on the one that delivered the movement shape I was looking for:

    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.
    The keyPoints 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 0 and 1:
    <animateMotion
    ...
    keyPoints="0; .35; .5; .65; 1;"
    >
    </animateMotion>

    Then I added the same number of keyTimes values, separated by semicolons, to control the pacing of this animation:
    <animateMotion
    ...
    keyTimes="0; .1; .5; .95; 1;"
    >
    </animateMotion>

    Now, Yogi rushes through the first three keyPoints, slows down as he passes the title text, then speeds up again as he exits the viewBox.
    Try this yourself:
    See the Pen Runaway Bear SVG animationby Andy Clarke.
    SMIL’s Not Dead, Baby. SMIL’s Not Dead
    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.
    SMIL includes the begin 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.
    Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool.
    Google reversed its decision to deprecate SMIL almost a decade ago, so it remains a terrific choice for designers and developers who want simple, semantic ways to add animations to their designs.
    #smashing #animations #part #3smilsnotdeadbaby #smilsnotdead
    Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead
    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. Now, you might’ve heard that SMIL is dead. 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. Tip: There’s now a website where you can see all my Toon Titles. Mike loves ’90s animation — especially Disney’s) Duck Tales). Unsurprisingly, my taste in cartoons stretches back a little further to Hanna-Barbera shows like Dastardly and Muttley in Their Flying Machines, Scooby-Doo, The Perils of Penelope Pitstop, Wacky Races, and, of course, The Yogi Bear Show. 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. 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. 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. Scaling makes an element grow, shrink, or stretch, which can add drama, create perspective, or simulate depth. 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. These results are all achievable using CSS animations, but some SVG properties can’t be animated using CSS. Luckily, we can do more — and have much more fun — using SMIL animations in SVG. We can combine complex animations, move objects along paths, and control when they start, stop, and everything in between. Animations can be embedded within any SVG element, including primitive shapes like circles, ellipses, and rectangles. They can also be encapsulated into groups, paths, and polygons: <circle ...> <animate>...</animate> </circle> Animations can also be defined outside an element, elsewhere in an SVG, and connected to it using an xlink attribute: <g id="yogi">...</g> ... <animate xlink:href="#yogi">…</animate> Building An Animation <animate> is just one of several animation elements in SVG. Together with an attributeName value, it enables animations based on one or more of an element’s attributes. Most animation explanations start by moving a primitive shape, like this exciting circle: <circle r="50" cx="50" cy="50" fill="#062326" opacity="1" /> Using this attributeName property, I can define which of this circle’s attributes I want to animate, which, in this example, is its cxposition: <circle ... > <animate attributename="cx"></animate> </circle> On its own, this does precisely nothing until I define three more values. The from keyword specifies the circle’s initial position, to, its final position, and the dur-ation between those two positions: <circle ... > <animate attributename="cx" from="50" to="500" dur="1s"> </animate> </circle> If I want more precise control, I can replace from and to with a set of values separated by semicolons: <circle ... > <animate attributename="cx" values="50; 250; 500; 250;" dur="1s"> </animate> </circle> Finally, I can define how many times the animation repeatsand even after what period that repeating should stop: <circle ... > <animate attributename="cx" values="50; 250; 500; 250;" dur="1s" repeatcount="indefinite" repeatdur="180s"> </circle> Most SVG elements have attributes that can be animated. This title card from 1959’s “Brainy Bear” episode shows Yogi in a crazy scientist‘s brain experiment. Yogi’s head is under the dome, and energy radiates around him. To create the buzz around Yogi, my SVG includes three path elements, each with opacity, stroke, and stroke-width attributes, which can all be animated: <path opacity="1" stroke="#fff" stroke-width="5" ... /> I animated each path’s opacity, changing its value from 1 to .5 and back again: <path opacity="1" ... > <animate attributename="opacity" values="1; .25; 1;" dur="1s" repeatcount="indefinite"> </animate> </path> Then, to radiate energy from Yogi, I specified when each animation should begin, using a different value for each path: <path ... > <animate begin="0" … > </path> <path ... > <animate begin=".5s" … > </path> <path ... > <animate begin="1s" … > </path> I’ll explain more about the begin property and how to start animations after this short commercial break. Try this yourself: I needed two types of transform animations to generate the effect of Yogi drifting gently downwards: translate, and rotate. I first added an animatetransform element to the group, which contains Yogi and his chute. I defined his initial vertical position — 1200 off the top of the viewBox — then translated his descent to 1000 over a 15-second duration: <g transform="translate"> ... <animateTransform attributeName="transform" type="translate" values="500,-1200; 500,1000" dur="15s" repeatCount="1" /> </g> Yogi appears to fall from the sky, but the movement looks unrealistic. So, I added a second animatetransform element, this time with an indefinitely repeating +/- 5-degree rotation to swing Yogi from side to side during his descent: <animateTransform attributeName="transform" type="rotate" values="-5; 5; -5" dur="14s" repeatCount="indefinite" additive="sum" /> Try this yourself: By default, the arrow is set loose when the page loads. Blink, and you might miss it. To build some anticipation, I can begin the animation two seconds later: <animatetransform attributename="transform" type="translate" from="0 0" to="750 0" dur=".25s" begin="2s" fill="freeze" /> Or, I can let the viewer take the shot when they click the arrow: <animatetransform ... begin="click" /> And I can combine the click event and a delay, all with no JavaScript, just a smattering of SMIL: <animatetransform ... begin="click + .5s" /> Try this yourself by clicking the arrow: 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 viewBox: <g class="dog" transform="translate"> ... </g> <g class="yogi" transform="translate"> ... </g> Then, I applied an animatetransform element to both groups, which moves them back into view: <!-- yogi --> <animateTransform attributeName="transform" type="translate" from="-1000,0" to="0,0" dur="2s" fill="freeze" /> <!-- dog --> <animateTransform attributeName="transform" type="translate" from="-1000,0" to="0,0" dur=".5s" fill="freeze" /> This sets up the action, but the effect feels flat, so I added another pair of animations that bounce both characters: <!-- yogi --> <animateTransform attributeName="transform" type="rotate" values="-1,0,450; 1,0,450; -1,0,450" dur=".25s" repeatCount="indefinite" /> <!-- dog --> <animateTransform attributeName="transform" type="rotate" values="-1,0,450; 1,0,450; -1,0,450" dur="0.5s" repeatCount="indefinite" /> 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. 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 translate animation: <animateTransform id="yogi" type="translate" ... /> Watch out: 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. Then, I applied a begin to his rotate animation, which starts playing a half-second after the #yogi animation ends: <animateTransform type="rotate" begin="yogi.end + .5s" ... /> I can build sophisticated sets of synchronised animations using the begin property and whether a named animation begins or ends. The bulldog chasing Yogi enters the frame two seconds after Yogi begins his entrance: <animateTransform id="dog" type="translate" begin="yogi.begin + 2s" fill="freeze" ... /> One second after the dog has caught up with Yogi, a rotate transformation makes him bounce, too: <animateTransform type="rotate" ... begin="dog.begin + 1s" repeatCount="indefinite" /> The background rectangles whizzing past are also synchronised, this time to one second before the bulldog ends his run: <rect ...> <animateTransform begin="dog.end + -1s" /> </rect> Try this yourself: In “The Runaway Bear” 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. I first added a path property, using its coordinate data to give Yogi a route to follow, and specified a two-second duration for my animation: <g> <animateMotion dur="2s" path="..." > </animateMotion> </g> Alternatively, I could add a path element, leave it visible, or prevent it from being rendered by placing it inside a defs element: <defs> <path id="yogi" d="..." /> </defs> I can then reference that by using a mpath element inside my animateMotion: <animateMotion ... <mpath href="#yogi" /> </animateMotion> I experimented with several paths before settling on the one that delivered the movement shape I was looking for: 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. The keyPoints 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 0 and 1: <animateMotion ... keyPoints="0; .35; .5; .65; 1;" > </animateMotion> Then I added the same number of keyTimes values, separated by semicolons, to control the pacing of this animation: <animateMotion ... keyTimes="0; .1; .5; .95; 1;" > </animateMotion> Now, Yogi rushes through the first three keyPoints, slows down as he passes the title text, then speeds up again as he exits the viewBox. Try this yourself: See the Pen Runaway Bear SVG animationby Andy Clarke. SMIL’s Not Dead, Baby. SMIL’s Not Dead 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. SMIL includes the begin 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. Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool. Google reversed its decision to deprecate SMIL almost a decade ago, so it remains a terrific choice for designers and developers who want simple, semantic ways to add animations to their designs. #smashing #animations #part #3smilsnotdeadbaby #smilsnotdead
    SMASHINGMAGAZINE.COM
    Smashing Animations Part 3: SMIL’s Not Dead Baby, SMIL’s Not Dead
    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. Now, you might’ve heard that SMIL is dead. 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. Tip: There’s now a website where you can see all my Toon Titles. Mike loves ’90s animation — especially Disney’s) Duck Tales). Unsurprisingly, my taste in cartoons stretches back a little further to Hanna-Barbera shows like Dastardly and Muttley in Their Flying Machines, Scooby-Doo, The Perils of Penelope Pitstop, Wacky Races, and, of course, The Yogi Bear Show. 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. 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. 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. Scaling makes an element grow, shrink, or stretch, which can add drama, create perspective, or simulate depth. 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. These results are all achievable using CSS animations, but some SVG properties can’t be animated using CSS. Luckily, we can do more — and have much more fun — using SMIL animations in SVG. We can combine complex animations, move objects along paths, and control when they start, stop, and everything in between. Animations can be embedded within any SVG element, including primitive shapes like circles, ellipses, and rectangles. They can also be encapsulated into groups, paths, and polygons: <circle ...> <animate>...</animate> </circle> Animations can also be defined outside an element, elsewhere in an SVG, and connected to it using an xlink attribute: <g id="yogi">...</g> ... <animate xlink:href="#yogi">…</animate> Building An Animation <animate> is just one of several animation elements in SVG. Together with an attributeName value, it enables animations based on one or more of an element’s attributes. Most animation explanations start by moving a primitive shape, like this exciting circle: <circle r="50" cx="50" cy="50" fill="#062326" opacity="1" /> Using this attributeName property, I can define which of this circle’s attributes I want to animate, which, in this example, is its cx (x-axis center point) position: <circle ... > <animate attributename="cx"></animate> </circle> On its own, this does precisely nothing until I define three more values. The from keyword specifies the circle’s initial position, to, its final position, and the dur-ation between those two positions: <circle ... > <animate attributename="cx" from="50" to="500" dur="1s"> </animate> </circle> If I want more precise control, I can replace from and to with a set of values separated by semicolons: <circle ... > <animate attributename="cx" values="50; 250; 500; 250;" dur="1s"> </animate> </circle> Finally, I can define how many times the animation repeats (repeatcount) and even after what period that repeating should stop (repeatdur): <circle ... > <animate attributename="cx" values="50; 250; 500; 250;" dur="1s" repeatcount="indefinite" repeatdur="180s"> </circle> Most SVG elements have attributes that can be animated. This title card from 1959’s “Brainy Bear” episode shows Yogi in a crazy scientist‘s brain experiment. Yogi’s head is under the dome, and energy radiates around him. To create the buzz around Yogi, my SVG includes three path elements, each with opacity, stroke, and stroke-width attributes, which can all be animated: <path opacity="1" stroke="#fff" stroke-width="5" ... /> I animated each path’s opacity, changing its value from 1 to .5 and back again: <path opacity="1" ... > <animate attributename="opacity" values="1; .25; 1;" dur="1s" repeatcount="indefinite"> </animate> </path> Then, to radiate energy from Yogi, I specified when each animation should begin, using a different value for each path: <path ... > <animate begin="0" … > </path> <path ... > <animate begin=".5s" … > </path> <path ... > <animate begin="1s" … > </path> I’ll explain more about the begin property and how to start animations after this short commercial break. Try this yourself: I needed two types of transform animations to generate the effect of Yogi drifting gently downwards: translate, and rotate. I first added an animatetransform element to the group, which contains Yogi and his chute. I defined his initial vertical position — 1200 off the top of the viewBox — then translated his descent to 1000 over a 15-second duration: <g transform="translate(1200, -1200)"> ... <animateTransform attributeName="transform" type="translate" values="500,-1200; 500,1000" dur="15s" repeatCount="1" /> </g> Yogi appears to fall from the sky, but the movement looks unrealistic. So, I added a second animatetransform element, this time with an indefinitely repeating +/- 5-degree rotation to swing Yogi from side to side during his descent: <animateTransform attributeName="transform" type="rotate" values="-5; 5; -5" dur="14s" repeatCount="indefinite" additive="sum" /> Try this yourself: By default, the arrow is set loose when the page loads. Blink, and you might miss it. To build some anticipation, I can begin the animation two seconds later: <animatetransform attributename="transform" type="translate" from="0 0" to="750 0" dur=".25s" begin="2s" fill="freeze" /> Or, I can let the viewer take the shot when they click the arrow: <animatetransform ... begin="click" /> And I can combine the click event and a delay, all with no JavaScript, just a smattering of SMIL: <animatetransform ... begin="click + .5s" /> Try this yourself by clicking the arrow: 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 viewBox: <g class="dog" transform="translate(-1000, 0)"> ... </g> <g class="yogi" transform="translate(-1000, 0)"> ... </g> Then, I applied an animatetransform element to both groups, which moves them back into view: <!-- yogi --> <animateTransform attributeName="transform" type="translate" from="-1000,0" to="0,0" dur="2s" fill="freeze" /> <!-- dog --> <animateTransform attributeName="transform" type="translate" from="-1000,0" to="0,0" dur=".5s" fill="freeze" /> This sets up the action, but the effect feels flat, so I added another pair of animations that bounce both characters: <!-- yogi --> <animateTransform attributeName="transform" type="rotate" values="-1,0,450; 1,0,450; -1,0,450" dur=".25s" repeatCount="indefinite" /> <!-- dog --> <animateTransform attributeName="transform" type="rotate" values="-1,0,450; 1,0,450; -1,0,450" dur="0.5s" repeatCount="indefinite" /> 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. 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 translate animation: <animateTransform id="yogi" type="translate" ... /> Watch out: 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. Then, I applied a begin to his rotate animation, which starts playing a half-second after the #yogi animation ends: <animateTransform type="rotate" begin="yogi.end + .5s" ... /> I can build sophisticated sets of synchronised animations using the begin property and whether a named animation begins or ends. The bulldog chasing Yogi enters the frame two seconds after Yogi begins his entrance: <animateTransform id="dog" type="translate" begin="yogi.begin + 2s" fill="freeze" ... /> One second after the dog has caught up with Yogi, a rotate transformation makes him bounce, too: <animateTransform type="rotate" ... begin="dog.begin + 1s" repeatCount="indefinite" /> The background rectangles whizzing past are also synchronised, this time to one second before the bulldog ends his run: <rect ...> <animateTransform begin="dog.end + -1s" /> </rect> Try this yourself: In “The Runaway Bear” 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. I first added a path property, using its coordinate data to give Yogi a route to follow, and specified a two-second duration for my animation: <g> <animateMotion dur="2s" path="..." > </animateMotion> </g> Alternatively, I could add a path element, leave it visible, or prevent it from being rendered by placing it inside a defs element: <defs> <path id="yogi" d="..." /> </defs> I can then reference that by using a mpath element inside my animateMotion: <animateMotion ... <mpath href="#yogi" /> </animateMotion> I experimented with several paths before settling on the one that delivered the movement shape I was looking for: 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. The keyPoints 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 0 and 1: <animateMotion ... keyPoints="0; .35; .5; .65; 1;" > </animateMotion> Then I added the same number of keyTimes values, separated by semicolons, to control the pacing of this animation: <animateMotion ... keyTimes="0; .1; .5; .95; 1;" > </animateMotion> Now, Yogi rushes through the first three keyPoints, slows down as he passes the title text, then speeds up again as he exits the viewBox. Try this yourself: See the Pen Runaway Bear SVG animation [forked] by Andy Clarke. SMIL’s Not Dead, Baby. SMIL’s Not Dead 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. SMIL includes the begin 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. Don’t let the misconception that SMIL is “dead” stop you from using this fantastic tool. Google reversed its decision to deprecate SMIL almost a decade ago, so it remains a terrific choice for designers and developers who want simple, semantic ways to add animations to their designs.
    0 Yorumlar 0 hisse senetleri 0 önizleme
  • I've lived in 6 places since becoming a digital nomad last year. One stood out above the others.

    It's been 12 months since Sarah Khan and her husband became digital nomads. Bali was her favorite place to work.

    Sarah Khan

    2025-05-16T00:14:01Z

    d

    Read in app

    This story is available exclusively to Business Insider
    subscribers. Become an Insider
    and start reading now.
    Have an account?

    Last year, Sarah Khan, 33, and her husband moved out and became digital nomads.
    So far, they've worked from Bali, Rome, Tuscany, Bangkok, Phuket, and Alicante.
    Bali has been her favorite place to work.

    The Mediterranean sparkles to my left as I type from a foldable desk on the terrace of a cozy home in a quiet coastal town in Spain. Rolling green hills stretch to my right, framing the space that will be home for the next three months, until we pack up and move again.It's been 12 months since my husband and I embarked on a nomadic life. We sold everything, ended our four-year apartment lease in Singapore, and boarded a one-way flight to Bali. Since then, we've worked from Bali, Rome, Tuscany, Bangkok, Phuket, and now the coast of Alicante in Spain.Friends and fellow travelers often ask, "Where's your favorite place to work?" I'm sometimes hesitant to answer because it's so subjective. Choosing a base as a nomad involves a different set of criteria than picking a vacation spot. For me, factors like community, longer-stay visas, reliable WiFi, easy access to nature, and a vibrant wellness scene are at the top of the checklist.

    Still, if I had to choose, the place that stands out— and one I'd happily return to — is Bali.

    Rent for the villa in Bali was a month, which included a pool, fast WiFi, and weekly cleaning.

    Sarah Khan

    I felt at homeBali was my first port of call as a digital nomad, and I spent a happy four months working and living there.Despite internet discourse about how "overrun" parts of the island have become, it remains my favourite place to work remotely to this day. Perhaps I'm biased — with my Indonesian roots and years of vacationing there, I feel instantly at home.My husband and I chose Berawa as our base, a laidback neighbourhood just outside the buzz of Canggu. Located on Bali's southern coast, Canggu has transformed from a sleepy surf village into the island's hippest enclave, packed with trendy cafés and black sand beaches that draw yogis and surfers in equal measure.This was my first time staying in Berawa, and it turned out to be the ideal spot for an extended stay. You get proximity to the action of Canggu without actually living in the thick of it. My two-bedroom villa, tucked down a quiet lane off a main road, placed me less than 10 minutes from central Canggu.Bali's cost of living has crept up in recent years, but it still offered value for our longer stay. Our villa rent was a month, which included a pool, fast WiFi, and weekly cleaning — less than half of what I'd paid for my apartment in Singapore.A remote worker's dream setupBali was an easy place to get started on my nomad life. The island is exceptionally well-equipped for long stays: the WiFi is generally reliable, there are plenty of supermarkets and pharmacies available for daily necessities, and ride-hailing apps are affordable and convenient.
    After a year on the road, I've come to appreciate how rare this combination is.The island also boasts one of the best remote work ecosystems I've experienced, from coworking spaces like Outpost and BWork to laptop-friendly cafés. I rotated through a few favourites: the workspace upstairs at Woods, Zin Cafe, and Lighthouse, a coworking café with beautiful rice field views and its own on-site podcast and video studio.

    Lighthouse is a coworking café with beautiful rice field views.

    Sarah Khan

    It's also easy to stay active and healthy in Bali. Gyms, yoga studios, and affordable massages are aplenty, especially around Berawa. And food options are great: from warungs serving fragrant local dishes to health-forward cafés and world-class restaurants.When work felt overwhelming and I needed a break, I could hop on a scooter and be at the beach in minutes. There were also many options for weekend escapes: We managed trips to the pristine Nusa Lembongan and Ceningan islands, a day trip to serene Sidemen, and explored the east coast's slower-paced beach towns like Amed and Candidasa.These experiences revealed a quieter, more soulful side of Bali — one I'd missed on past short trips.

    Weekend escapes included a visit to an outdoor spa in Sidemen.

    Sarah Khan

    The downsidesOf course, no place is perfect. Traffic in Canggu can be chaotic, and the island's infrastructure is still catching up with its tourism growth. There's also a digital nomad community that, at times, can feel like a bubble and disconnected from authentic local life.But once you find your rhythm and favourite nooks, it's easy to tune out the noise and settle into Bali's slower, softer pace.I made it a point to skip the touristy spots, stay just outside the main areas, and design my life and routine around the kind of experience I wanted.A year into nomadic living, I've felt uprooted, disoriented, and occasionally exhausted. But in Bali, I found a version of myself I liked: Focused, centered, and rested.

    Recommended video
    #i039ve #lived #places #since #becoming
    I've lived in 6 places since becoming a digital nomad last year. One stood out above the others.
    It's been 12 months since Sarah Khan and her husband became digital nomads. Bali was her favorite place to work. Sarah Khan 2025-05-16T00:14:01Z d Read in app This story is available exclusively to Business Insider subscribers. Become an Insider and start reading now. Have an account? Last year, Sarah Khan, 33, and her husband moved out and became digital nomads. So far, they've worked from Bali, Rome, Tuscany, Bangkok, Phuket, and Alicante. Bali has been her favorite place to work. The Mediterranean sparkles to my left as I type from a foldable desk on the terrace of a cozy home in a quiet coastal town in Spain. Rolling green hills stretch to my right, framing the space that will be home for the next three months, until we pack up and move again.It's been 12 months since my husband and I embarked on a nomadic life. We sold everything, ended our four-year apartment lease in Singapore, and boarded a one-way flight to Bali. Since then, we've worked from Bali, Rome, Tuscany, Bangkok, Phuket, and now the coast of Alicante in Spain.Friends and fellow travelers often ask, "Where's your favorite place to work?" I'm sometimes hesitant to answer because it's so subjective. Choosing a base as a nomad involves a different set of criteria than picking a vacation spot. For me, factors like community, longer-stay visas, reliable WiFi, easy access to nature, and a vibrant wellness scene are at the top of the checklist. Still, if I had to choose, the place that stands out— and one I'd happily return to — is Bali. Rent for the villa in Bali was a month, which included a pool, fast WiFi, and weekly cleaning. Sarah Khan I felt at homeBali was my first port of call as a digital nomad, and I spent a happy four months working and living there.Despite internet discourse about how "overrun" parts of the island have become, it remains my favourite place to work remotely to this day. Perhaps I'm biased — with my Indonesian roots and years of vacationing there, I feel instantly at home.My husband and I chose Berawa as our base, a laidback neighbourhood just outside the buzz of Canggu. Located on Bali's southern coast, Canggu has transformed from a sleepy surf village into the island's hippest enclave, packed with trendy cafés and black sand beaches that draw yogis and surfers in equal measure.This was my first time staying in Berawa, and it turned out to be the ideal spot for an extended stay. You get proximity to the action of Canggu without actually living in the thick of it. My two-bedroom villa, tucked down a quiet lane off a main road, placed me less than 10 minutes from central Canggu.Bali's cost of living has crept up in recent years, but it still offered value for our longer stay. Our villa rent was a month, which included a pool, fast WiFi, and weekly cleaning — less than half of what I'd paid for my apartment in Singapore.A remote worker's dream setupBali was an easy place to get started on my nomad life. The island is exceptionally well-equipped for long stays: the WiFi is generally reliable, there are plenty of supermarkets and pharmacies available for daily necessities, and ride-hailing apps are affordable and convenient. After a year on the road, I've come to appreciate how rare this combination is.The island also boasts one of the best remote work ecosystems I've experienced, from coworking spaces like Outpost and BWork to laptop-friendly cafés. I rotated through a few favourites: the workspace upstairs at Woods, Zin Cafe, and Lighthouse, a coworking café with beautiful rice field views and its own on-site podcast and video studio. Lighthouse is a coworking café with beautiful rice field views. Sarah Khan It's also easy to stay active and healthy in Bali. Gyms, yoga studios, and affordable massages are aplenty, especially around Berawa. And food options are great: from warungs serving fragrant local dishes to health-forward cafés and world-class restaurants.When work felt overwhelming and I needed a break, I could hop on a scooter and be at the beach in minutes. There were also many options for weekend escapes: We managed trips to the pristine Nusa Lembongan and Ceningan islands, a day trip to serene Sidemen, and explored the east coast's slower-paced beach towns like Amed and Candidasa.These experiences revealed a quieter, more soulful side of Bali — one I'd missed on past short trips. Weekend escapes included a visit to an outdoor spa in Sidemen. Sarah Khan The downsidesOf course, no place is perfect. Traffic in Canggu can be chaotic, and the island's infrastructure is still catching up with its tourism growth. There's also a digital nomad community that, at times, can feel like a bubble and disconnected from authentic local life.But once you find your rhythm and favourite nooks, it's easy to tune out the noise and settle into Bali's slower, softer pace.I made it a point to skip the touristy spots, stay just outside the main areas, and design my life and routine around the kind of experience I wanted.A year into nomadic living, I've felt uprooted, disoriented, and occasionally exhausted. But in Bali, I found a version of myself I liked: Focused, centered, and rested. Recommended video #i039ve #lived #places #since #becoming
    WWW.BUSINESSINSIDER.COM
    I've lived in 6 places since becoming a digital nomad last year. One stood out above the others.
    It's been 12 months since Sarah Khan and her husband became digital nomads. Bali was her favorite place to work. Sarah Khan 2025-05-16T00:14:01Z Save Saved Read in app This story is available exclusively to Business Insider subscribers. Become an Insider and start reading now. Have an account? Last year, Sarah Khan, 33, and her husband moved out and became digital nomads. So far, they've worked from Bali, Rome, Tuscany, Bangkok, Phuket, and Alicante. Bali has been her favorite place to work. The Mediterranean sparkles to my left as I type from a foldable desk on the terrace of a cozy home in a quiet coastal town in Spain. Rolling green hills stretch to my right, framing the space that will be home for the next three months, until we pack up and move again.It's been 12 months since my husband and I embarked on a nomadic life. We sold everything, ended our four-year apartment lease in Singapore, and boarded a one-way flight to Bali. Since then, we've worked from Bali, Rome, Tuscany, Bangkok, Phuket, and now the coast of Alicante in Spain.Friends and fellow travelers often ask, "Where's your favorite place to work?" I'm sometimes hesitant to answer because it's so subjective. Choosing a base as a nomad involves a different set of criteria than picking a vacation spot. For me, factors like community, longer-stay visas, reliable WiFi, easy access to nature, and a vibrant wellness scene are at the top of the checklist. Still, if I had to choose, the place that stands out— and one I'd happily return to — is Bali. Rent for the villa in Bali was $1,800 a month, which included a pool, fast WiFi, and weekly cleaning. Sarah Khan I felt at homeBali was my first port of call as a digital nomad, and I spent a happy four months working and living there.Despite internet discourse about how "overrun" parts of the island have become, it remains my favourite place to work remotely to this day. Perhaps I'm biased — with my Indonesian roots and years of vacationing there, I feel instantly at home.My husband and I chose Berawa as our base, a laidback neighbourhood just outside the buzz of Canggu. Located on Bali's southern coast, Canggu has transformed from a sleepy surf village into the island's hippest enclave, packed with trendy cafés and black sand beaches that draw yogis and surfers in equal measure.This was my first time staying in Berawa, and it turned out to be the ideal spot for an extended stay. You get proximity to the action of Canggu without actually living in the thick of it. My two-bedroom villa, tucked down a quiet lane off a main road, placed me less than 10 minutes from central Canggu.Bali's cost of living has crept up in recent years, but it still offered value for our longer stay. Our villa rent was $1,800 a month, which included a pool, fast WiFi, and weekly cleaning — less than half of what I'd paid for my apartment in Singapore.A remote worker's dream setupBali was an easy place to get started on my nomad life. The island is exceptionally well-equipped for long stays: the WiFi is generally reliable, there are plenty of supermarkets and pharmacies available for daily necessities, and ride-hailing apps are affordable and convenient. After a year on the road, I've come to appreciate how rare this combination is.The island also boasts one of the best remote work ecosystems I've experienced, from coworking spaces like Outpost and BWork to laptop-friendly cafés. I rotated through a few favourites: the workspace upstairs at Woods, Zin Cafe, and Lighthouse, a coworking café with beautiful rice field views and its own on-site podcast and video studio. Lighthouse is a coworking café with beautiful rice field views. Sarah Khan It's also easy to stay active and healthy in Bali. Gyms, yoga studios, and affordable massages are aplenty, especially around Berawa. And food options are great: from warungs serving fragrant local dishes to health-forward cafés and world-class restaurants.When work felt overwhelming and I needed a break, I could hop on a scooter and be at the beach in minutes. There were also many options for weekend escapes: We managed trips to the pristine Nusa Lembongan and Ceningan islands, a day trip to serene Sidemen, and explored the east coast's slower-paced beach towns like Amed and Candidasa.These experiences revealed a quieter, more soulful side of Bali — one I'd missed on past short trips. Weekend escapes included a visit to an outdoor spa in Sidemen. Sarah Khan The downsidesOf course, no place is perfect. Traffic in Canggu can be chaotic, and the island's infrastructure is still catching up with its tourism growth. There's also a digital nomad community that, at times, can feel like a bubble and disconnected from authentic local life.But once you find your rhythm and favourite nooks, it's easy to tune out the noise and settle into Bali's slower, softer pace.I made it a point to skip the touristy spots, stay just outside the main areas, and design my life and routine around the kind of experience I wanted.A year into nomadic living, I've felt uprooted, disoriented, and occasionally exhausted. But in Bali, I found a version of myself I liked: Focused, centered, and rested. Recommended video
    0 Yorumlar 0 hisse senetleri 0 önizleme
  • Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension

    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 grew up watching on TV. Shows like Dastardly and Muttley in Their Flying Machines, Scooby-Doo, The Perils of Penelope Pitstop, Wacky Races, and, of course, Yogi Bear.

    Mike loves ’90s animation — especially Disney’s Duck Tales). So, that is the aesthetic applied throughout the design.

    I used animations throughout and have recently added an extra dimension to them using masking. So, to explain how this era of animation relates to masking in CSS, I’ve chosen an episode of The Yogi Bear Show, “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!
    What could possibly go wrong?

    What’s A Mask?
    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.

    In CSS, masking controls visibility using a bitmap, vector, or gradient mask image. 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.

    A clip-path functions similarly to a mask 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.”

    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.
    How do I choose when to use clip path and when to choose mask?

    I’ll explain my reasons in each example.

    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.

    Masking Using A Clipping Path
    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:
    <figure>
    <svg viewBox="0 0 1400 960" id="cave">
    <g class="background">…</g>
    <g class="foreground">…</g>
    </svg>
    </figure>

    I defined a keyframe animation that moves the character from 2000px on the right to its natural position in the center of the frame by altering its translate value:
    @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;
    }
    }

    Then, I applied that animation to the foreground group:
    .foreground {
    opacity: 0;
    animation: foreground 5s 2s ease-in-out infinite;
    }

    Try this yourself:

    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 clip-path.
    There are several ways to define a clip-path in CSS. I could use a primitive shape like a rectangle, where each of the first four values specifies its corner positions. The round keyword and the value that follows define any rounded corners:
    clip-path: rect;

    Or xywhvalues, which I find easier to read:
    clip-path: xywh;

    I could use a circle:
    clip-path: circle;

    Or an ellipse:
    clip-path: ellipse;

    I could use a polygon shape:
    clip-path: polygon;

    Or even the points from a path I created in a graphics app like Sketch:
    clip-path: path;

    Finally — and my choice for this example — I might use a mask that I defined using paths from an SVG file:
    clip-path: url;

    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:
    <figure>
    <svg viewBox="0 0 1400 960" id="cave">...</svg>
    <svg height="0" width="0" id="mask">...</svg>
    </figure>

    This contains a single SVG clipPath. By placing this inside the defs element, this path isn’t rendered, but it will be available to create my CSS clip-path:
    <svg height="0" width="0" id="mask">
    <defs>
    <clipPath id="mask-cave">...</clipPath>
    </defs>
    </svg>

    I applied the clipPath URL to my illustration, and now Mike’s mascot only becomes visible when he enters the cave:
    #cave {
    clip-path: url;
    }

    Try this yourself:

    While a clipPath 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 mask as its properties have been baseline and highly usable since 2023.
    The mask property is a shorthand and can include values for mask-clip, mask-mode, mask-origin, mask-position, mask-repeat, mask-size, and mask-type. I find it’s best to learn these properties individually to grasp the concept of masks more easily.
    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:
    mask-image: url;

    I could apply a mask using a vector graphic:
    mask-image: url;

    Or generate an image using a conical, linear, or radial gradient:
    mask-image: linear-gradient;

    …or:

    mask-image: radial-gradient;

    I might apply more than one mask to an element and mix several image types using what should be a familiar syntax:
    mask-image:
    image),
    linear-gradient;

    mask shares the same syntax as CSS backgrounds, which makes remembering its properties much easier. To apply a background-image, add its URL value:
    background-image: url;

    To apply a mask, swap the background-image property for mask-image:
    mask-image: url;

    The mask property also shares the same browser styles as CSS backgrounds, so by default, a mask will repeat horizontally and vertically unless I specify otherwise:

    /* Options: repeat, repeat-x, repeat-y, round, space, no-repeat */
    mask-repeat: no-repeat;

    It will be placed at the top-left corner unless I alter its position:
    /* Options: Keywords, units, percentages */
    mask-position: center;

    Plus, I can specify mask-size in the same way as background-size:

    /* Options: Keywords, units, percentages */
    mask-size: cover;

    Finally, I can define where a mask starts:
    mask-origin: content-box;
    mask-origin: padding-box;
    mask-origin: border-box;

    Using A Mask Image
    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 mask-image which defines the visible area and applied it to the figure element:
    figure {
    mask-image: url;
    }

    To ensure the mask matched the illustration’s dimensions, I also set the mask-size to always cover its content:
    figure {
    mask-size: cover;
    }

    Try this yourself:

    figure {
    clip-path: ellipse;
    }

    However, the hard edges of a clip clip-path don’t create the effect I was aiming to achieve:
    Try this yourself:

    Finally, to add an extra touch of realism, I added a keyframe animation — which changes the mask-size and creates the effect that the lamp light is flickering — and applied it to the figure:
    @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;
    }

    Try this yourself:

    I started by creating the binocular shape, complete with some viewfinder markers.

    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:
    figure {
    mask-image: url;
    mask-position: 50% 50%;
    mask-repeat: no-repeat;
    mask-size: 85%;
    }

    Try this yourself:

    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:
    <figure>
    <svg>…</svg>
    </figure>

    I created a circular clip-path and set its default size to 75%. 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:
    @keyframes error {
    0% { clip-path: circle; }
    100% { clip-path: circle; }
    }

    figure {
    clip-path: circle;
    animation: error 1s 3s ease-in forwards;
    }

    The animation now focuses someone’s attention on the hapless hero, before he sinks lower and lower into the bubblingly hot lava.
    Try this yourself:
    See the Pen Mike Worth’s error pageby Andy Clarke.
    Bringing It All To Life
    Masking adds an extra dimension to web animation and makes stories more engaging and someone’s experience more compelling — 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.
    The end. Or is it? …
    Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen now.
    #smashing #animations #part #how #css
    Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension
    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 grew up watching on TV. Shows like Dastardly and Muttley in Their Flying Machines, Scooby-Doo, The Perils of Penelope Pitstop, Wacky Races, and, of course, Yogi Bear. Mike loves ’90s animation — especially Disney’s Duck Tales). So, that is the aesthetic applied throughout the design. I used animations throughout and have recently added an extra dimension to them using masking. So, to explain how this era of animation relates to masking in CSS, I’ve chosen an episode of The Yogi Bear Show, “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! What could possibly go wrong? What’s A Mask? 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. In CSS, masking controls visibility using a bitmap, vector, or gradient mask image. 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. A clip-path functions similarly to a mask 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.” 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. How do I choose when to use clip path and when to choose mask? I’ll explain my reasons in each example. 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. Masking Using A Clipping Path 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: <figure> <svg viewBox="0 0 1400 960" id="cave"> <g class="background">…</g> <g class="foreground">…</g> </svg> </figure> I defined a keyframe animation that moves the character from 2000px on the right to its natural position in the center of the frame by altering its translate value: @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; } } Then, I applied that animation to the foreground group: .foreground { opacity: 0; animation: foreground 5s 2s ease-in-out infinite; } Try this yourself: 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 clip-path. There are several ways to define a clip-path in CSS. I could use a primitive shape like a rectangle, where each of the first four values specifies its corner positions. The round keyword and the value that follows define any rounded corners: clip-path: rect; Or xywhvalues, which I find easier to read: clip-path: xywh; I could use a circle: clip-path: circle; Or an ellipse: clip-path: ellipse; I could use a polygon shape: clip-path: polygon; Or even the points from a path I created in a graphics app like Sketch: clip-path: path; Finally — and my choice for this example — I might use a mask that I defined using paths from an SVG file: clip-path: url; 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: <figure> <svg viewBox="0 0 1400 960" id="cave">...</svg> <svg height="0" width="0" id="mask">...</svg> </figure> This contains a single SVG clipPath. By placing this inside the defs element, this path isn’t rendered, but it will be available to create my CSS clip-path: <svg height="0" width="0" id="mask"> <defs> <clipPath id="mask-cave">...</clipPath> </defs> </svg> I applied the clipPath URL to my illustration, and now Mike’s mascot only becomes visible when he enters the cave: #cave { clip-path: url; } Try this yourself: While a clipPath 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 mask as its properties have been baseline and highly usable since 2023. The mask property is a shorthand and can include values for mask-clip, mask-mode, mask-origin, mask-position, mask-repeat, mask-size, and mask-type. I find it’s best to learn these properties individually to grasp the concept of masks more easily. 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: mask-image: url; I could apply a mask using a vector graphic: mask-image: url; Or generate an image using a conical, linear, or radial gradient: mask-image: linear-gradient; …or: mask-image: radial-gradient; I might apply more than one mask to an element and mix several image types using what should be a familiar syntax: mask-image: image), linear-gradient; mask shares the same syntax as CSS backgrounds, which makes remembering its properties much easier. To apply a background-image, add its URL value: background-image: url; To apply a mask, swap the background-image property for mask-image: mask-image: url; The mask property also shares the same browser styles as CSS backgrounds, so by default, a mask will repeat horizontally and vertically unless I specify otherwise: /* Options: repeat, repeat-x, repeat-y, round, space, no-repeat */ mask-repeat: no-repeat; It will be placed at the top-left corner unless I alter its position: /* Options: Keywords, units, percentages */ mask-position: center; Plus, I can specify mask-size in the same way as background-size: /* Options: Keywords, units, percentages */ mask-size: cover; Finally, I can define where a mask starts: mask-origin: content-box; mask-origin: padding-box; mask-origin: border-box; Using A Mask Image 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 mask-image which defines the visible area and applied it to the figure element: figure { mask-image: url; } To ensure the mask matched the illustration’s dimensions, I also set the mask-size to always cover its content: figure { mask-size: cover; } Try this yourself: figure { clip-path: ellipse; } However, the hard edges of a clip clip-path don’t create the effect I was aiming to achieve: Try this yourself: Finally, to add an extra touch of realism, I added a keyframe animation — which changes the mask-size and creates the effect that the lamp light is flickering — and applied it to the figure: @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; } Try this yourself: I started by creating the binocular shape, complete with some viewfinder markers. 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: figure { mask-image: url; mask-position: 50% 50%; mask-repeat: no-repeat; mask-size: 85%; } Try this yourself: 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: <figure> <svg>…</svg> </figure> I created a circular clip-path and set its default size to 75%. 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: @keyframes error { 0% { clip-path: circle; } 100% { clip-path: circle; } } figure { clip-path: circle; animation: error 1s 3s ease-in forwards; } The animation now focuses someone’s attention on the hapless hero, before he sinks lower and lower into the bubblingly hot lava. Try this yourself: See the Pen Mike Worth’s error pageby Andy Clarke. Bringing It All To Life Masking adds an extra dimension to web animation and makes stories more engaging and someone’s experience more compelling — 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. The end. Or is it? … Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen now. #smashing #animations #part #how #css
    SMASHINGMAGAZINE.COM
    Smashing Animations Part 2: How CSS Masking Can Add An Extra Dimension
    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 grew up watching on TV. Shows like Dastardly and Muttley in Their Flying Machines, Scooby-Doo, The Perils of Penelope Pitstop, Wacky Races, and, of course, Yogi Bear. Mike loves ’90s animation — especially Disney’s Duck Tales). So, that is the aesthetic applied throughout the design. I used animations throughout and have recently added an extra dimension to them using masking. So, to explain how this era of animation relates to masking in CSS, I’ve chosen an episode of The Yogi Bear Show, “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! What could possibly go wrong? What’s A Mask? 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. In CSS, masking controls visibility using a bitmap, vector, or gradient mask image. 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. A clip-path functions similarly to a mask 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.” 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. How do I choose when to use clip path and when to choose mask? I’ll explain my reasons in each example. 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. Masking Using A Clipping Path 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: <figure> <svg viewBox="0 0 1400 960" id="cave"> <g class="background">…</g> <g class="foreground">…</g> </svg> </figure> I defined a keyframe animation that moves the character from 2000px on the right to its natural position in the center of the frame by altering its translate value: @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; } } Then, I applied that animation to the foreground group: .foreground { opacity: 0; animation: foreground 5s 2s ease-in-out infinite; } Try this yourself: 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 clip-path. There are several ways to define a clip-path in CSS. I could use a primitive shape like a rectangle, where each of the first four values specifies its corner positions. The round keyword and the value that follows define any rounded corners: clip-path: rect(0px 150px 150px 0px round 5px); Or xywh (x, y, width, height) values, which I find easier to read: clip-path: xywh(0 0 150px 150px round 5px); I could use a circle: clip-path: circle(60px at center); Or an ellipse: clip-path: ellipse(50% 40% at 50% 50%); I could use a polygon shape: clip-path: polygon(...); Or even the points from a path I created in a graphics app like Sketch: clip-path: path("M ..."); Finally — and my choice for this example — I might use a mask that I defined using paths from an SVG file: clip-path: url(#mask-cave); 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: <figure> <svg viewBox="0 0 1400 960" id="cave">...</svg> <svg height="0" width="0" id="mask">...</svg> </figure> This contains a single SVG clipPath. By placing this inside the defs element, this path isn’t rendered, but it will be available to create my CSS clip-path: <svg height="0" width="0" id="mask"> <defs> <clipPath id="mask-cave">...</clipPath> </defs> </svg> I applied the clipPath URL to my illustration, and now Mike’s mascot only becomes visible when he enters the cave: #cave { clip-path: url(#mask-cave); } Try this yourself: While a clipPath 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 mask as its properties have been baseline and highly usable since 2023. The mask property is a shorthand and can include values for mask-clip, mask-mode, mask-origin, mask-position, mask-repeat, mask-size, and mask-type. I find it’s best to learn these properties individually to grasp the concept of masks more easily. 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: mask-image: url(mask.webp); I could apply a mask using a vector graphic: mask-image: url(mask.svg); Or generate an image using a conical, linear, or radial gradient: mask-image: linear-gradient(#000, transparent); …or: mask-image: radial-gradient(circle, #ff104c 0%, transparent 100%); I might apply more than one mask to an element and mix several image types using what should be a familiar syntax: mask-image: image(url(mask.webp)), linear-gradient(#000, transparent); mask shares the same syntax as CSS backgrounds, which makes remembering its properties much easier. To apply a background-image, add its URL value: background-image: url("background.webp"); To apply a mask, swap the background-image property for mask-image: mask-image: url("mask.webp"); The mask property also shares the same browser styles as CSS backgrounds, so by default, a mask will repeat horizontally and vertically unless I specify otherwise: /* Options: repeat, repeat-x, repeat-y, round, space, no-repeat */ mask-repeat: no-repeat; It will be placed at the top-left corner unless I alter its position: /* Options: Keywords, units, percentages */ mask-position: center; Plus, I can specify mask-size in the same way as background-size: /* Options: Keywords (auto, contain, cover), units, percentages */ mask-size: cover; Finally, I can define where a mask starts: mask-origin: content-box; mask-origin: padding-box; mask-origin: border-box; Using A Mask Image 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 mask-image which defines the visible area and applied it to the figure element: figure { mask-image: url(mask.svg); } To ensure the mask matched the illustration’s dimensions, I also set the mask-size to always cover its content: figure { mask-size: cover; } Try this yourself: figure { clip-path: ellipse(45% 35% at 50% 50%); } However, the hard edges of a clip clip-path don’t create the effect I was aiming to achieve: Try this yourself: Finally, to add an extra touch of realism, I added a keyframe animation — which changes the mask-size and creates the effect that the lamp light is flickering — and applied it to the figure: @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; } Try this yourself: I started by creating the binocular shape, complete with some viewfinder markers. 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: figure { mask-image: url(mask.svg); mask-position: 50% 50%; mask-repeat: no-repeat; mask-size: 85%; } Try this yourself: 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: <figure> <svg>…</svg> </figure> I created a circular clip-path and set its default size to 75%. 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: @keyframes error { 0% { clip-path: circle(75%); } 100% { clip-path: circle(15%); } } figure { clip-path: circle(75%); animation: error 1s 3s ease-in forwards; } The animation now focuses someone’s attention on the hapless hero, before he sinks lower and lower into the bubblingly hot lava. Try this yourself: See the Pen Mike Worth’s error page [forked] by Andy Clarke. Bringing It All To Life Masking adds an extra dimension to web animation and makes stories more engaging and someone’s experience more compelling — 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. The end. Or is it? … Mike Worth’s website will launch in June 2025, but you can see examples from this article on CodePen now.
    0 Yorumlar 0 hisse senetleri 0 önizleme
CGShares https://cgshares.com