From a Sass Loop to a Full SVG Loader Generator

Exploring SVG while learning Vue.js led to the creation of an online generator for SVG loaders. The tool offers two output modes—-SMIL or Sass-based CSS—-along with configurable styles, colors, shapes, and effects. Users can paste in a custom path or text, download the final SVG, copy the code, or launch a live CodePen demo.

Three Starting Points

The project grew from three separate inspirations that came together at the right time.

First, Sarah Drasner’s SVG Animations book introduced the idea of using Sass loops to stagger animation delays. Her chapter on animating data visualizations demonstrated how a small loop can replace many repetitive CSS declarations.

Second, a request to recreate a classic Apple-style loader provided a practical use case. The solution used a similar Sass loop approach:

@for $i from 1 to 12 {
  .loader:nth-of-type(#{$i}) {
    animation: 1s $i * 0.08s opacityLoader infinite;
  }
}
@keyframes opacityLoader {
 to { opacity: 0; }
}

That snippet defines a counter (i) from 1 to 12 and increases the animation delay on each successive :nth-child element. Two lines of Sass replaced numerous hand-written CSS rules. Here is the equivalent in vanilla CSS:

.loader:nth-of-type(1) {
  animation: 1s 0.08s opacityLoader infinite;
}
.loader:nth-of-type(2) {
  animation: 1s 0.16s opacityLoader infinite;
}

/* ... */

.loader:nth-of-type(12) {
  animation: 1s 0.96s opacityLoader infinite;
}
@keyframes opacityLoader {
  to { opacity: 0; }
}

Third, the idea emerged to build a gallery of loaders all driven by this same looping pattern—something the author often struggled to find online but thought could be genuinely useful to others.

A round segmented spinner where each segment fades in and out in succession to create a circling effect.
This is a mockup of the loader I was asked to make.

Two Output Formats from a Single Generator

Initially, producing accurate Sass output proved difficult. A switch to SMIL animations seemed like the path forward, but community help (credited to ekrof) got Sass working as well. The generator now supports both, even though ensuring identical results between the two isn’t always possible.

SMIL vs. CSS/Sass: Key Differences

  • SMIL is self-contained. It animates SVG using presentation attributes directly in the markup, without any external dependencies—something neither CSS nor Sass can offer.
  • SMIL survives image contexts. Animations persist when an SVG is embedded as an <img> or a CSS background image. A <style> block inside an SVG works for CSS, but not for Sass. This is why the generator offers an SVG file download only when SMIL is selected.
  • SMIL often looks slightly smoother. The author couldn’t identify the definitive cause (suspecting GPU acceleration, though both seem to use the same engine), and invites deeper insight from readers.
Two spinners, one left and one right. They are both red and consist of circles that fade in and out in succession as an animated GIF.
SMIL (left) and Sass (right)

Chaining animations also behaves differently between the two approaches:

  • SMIL uses additive="sum" so each new animation builds on the previous one without overriding it.
  • In CSS/Sass, the W3C specification states that when multiple animations target the same property, the one listed last wins.

That ordering rule explains why the resulting Sass output can differ depending on how animations are sequenced.

The Transform Problem

Applying transformations was a constant hurdle. Shapes were rotated inline to position them around a circle facing the center:

<svg>
  <!-- etc. -->
  <use class="loader" xlink:href="#loader" transform="rotate(0 50 50)" />
  <use class="loader" xlink:href="#loader" transform="rotate(30 50 50)" />
  <use class="loader" xlink:href="#loader" transform="rotate(60 50 50)" />
  <!-- etc. -->
</svg>

SMIL supports typed transforms via <animateTransform> (such as scale or translate), which nicely composes with the shape’s existing inline rotation:

<animateTransform
  attributeName="transform"
  type="translate"
  additive="sum"
  dur="1s"
  :begin="`${i * 0.08}s`"
  repeatCount="indefinite"
  from="0 0"
  to="10"
/>

CSS, however, overrides transform rather than composing with it. Any inline SVG rotation was lost when the animation started, resetting the position and producing results that looked the same regardless of the shape or effect.

The same two red spinners as before but with different results. The SMIL version on the left seems to work as expected but the Sass one on the right doesn't animate in a circle like it should.

The workaround was structural: wrap each shape in a <g> element, keep the rotation on the group, and apply the animation to the child shape. This prevents the animation from disturbing the inline transform.

<svg>
  <!-- etc. -->
  <g class="loader" transform="rotate(0 50 50)">
    <use xlink:href="#loader" />
  </g>
  <g class="loader" transform="rotate(30 50 50)">
    <use xlink:href="#loader" />
  </g>
  <!-- etc. -->
</svg>

With that layout, both languages yield nearly identical visuals.

Why Vue and Nuxt?

Vue.js was chosen for the front-end for several reasons:

  • Single file components keep HTML, CSS, and JavaScript together in one easy-to-manage file.
  • Binding and dynamically updating SVG or HTML attributes feels intuitive.
  • Neither HTML nor SVG needs conversion to JSX, so markup remains untouched.

Nuxt.js complements Vue by providing:

  • A quick boilerplate that minimizes configuration for faster development.
  • Automatic routing and built-in support for auto-importing components.
  • A clear structure of pages, components, and layouts.
  • Simplified SEO via meta tag management.

Practical Ways to Use the Generated Loaders

The generator isn’t limited to one output shape. Serving both SMIL and CSS/Sass opens several integration paths.

Use the Downloaded SMIL SVG as a CSS Background

Because SMIL animations stay intact when the SVG is referenced as a file (via url()), the easiest route is to download the SVG, place it on your server, and set it as the background image. The same works for pseudo-element backgrounds.

Embed the SVG Directly Into Markup

Since SVG is just code, copy-pasting the generator’s output into your HTML page is another way for SMIL animations to run uninterrupted.

Animate the Inline SVG with a Sass Loop

This was the original anchor of the project, ultimately enabled after working around the transform issues. Instead of writing out every animation-delay rule manually, the generated Sass loop targets a .loader class already inserted on the SVG. After compilation, the loader spins as intended.

Ongoing Work and an Open Invitation

The generator’s most interesting option is the custom shape feature—adding text, emojis, or any other SVG element into the loader design:

The same circle spinner but using custom SVG shapes: one a word, one a poop emoji, and bright pink and orange asterisk.
Custom text, emoji, and SVG

Future development may introduce a third style that works with a single custom element for even simpler outputs. That would require careful handling of user-defined values for durations, directions, distances, and degrees across both output modes. The larger project still needs code cleanup, and the author notes that ongoing Vue experience should help refine it.

The project is open source on GitHub, and pull requests are actively welcomed. Feedback and suggestions are appreciated—offers for Vue course recommendations, especially those about SVG or generator building patterns, are also gladly received.

On SMIL longevity: Chrome once planned to deprecate SMIL (see the note at the top of MDN’s SMIL documentation), though that deprecation was suspended and hasn’t made headlines recently. For anyone concerned about browser support:

ChromeFirefoxIEEdgeSafari
5411796
Android ChromeAndroid FirefoxAndroidiOS Safari
15115336.0-6.1