Search code examples
svg

Set children animate values in SVG "use" element


I've got an animated spinner SVG file. It's 12 bars in a circle that alter opacity.

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgba(0, 0, 0, 0) none repeat scroll 0% 0%; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<g transform="rotate(0 50 50)">
  <rect x="48" y="17.5" rx="0" ry="0" width="4" height="15" fill="#336699">
    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate>
  </rect>
</g><g transform="rotate(30 50 50)">
  <rect x="48" y="17.5" rx="0" ry="0" width="4" height="15" fill="#336699">
    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate>
  </rect>
</g><g transform="rotate(60 50 50)">
  <rect x="48" y="17.5" rx="0" ry="0" width="4" height="15" fill="#336699">
    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
  </rect>
</g>
... [and so on, 12 times]

This works fine, but it's really repetitive, so I factored out one rect in a defs tag, and used use to place it.

<defs>
<g id="bar">
  <rect x="48" y="17.5" rx="0" ry="0" width="4" height="15" fill="#336699">
    <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" repeatCount="indefinite"></animate>
  </rect>
</g>
</defs>

<use xlink:href="#bar" transform="rotate(0 50 50)"/>
<use xlink:href="#bar" transform="rotate(30 50 50)"/>
...

This also works fine except I need to set the begin property in the nested animate tag. I've no idea how to reach that. The MDN for SVG's use doesn't seem to talk about altering children of elements made with use and searching Google and SO doesn't turn up anything useful, since use is such a common word.

I tried <use xlink:href="#bar" transform="rotate(0 50 50)"><animate begin="-0.9167s"></animate></use> and a few other things that didn't work.

How do I set an attribute value of a child of an element made with use ?

Thanks all, and as this is my second question on SO, I'm more than open to suggestions on question form/content. Cheers.

To be clear, I don't want to do the animation in CSS or Javascript, I want everything contained in the SVG file. If it's not possible, that's good to know, too.


Solution

    • If you don't want repetition, you need code to create repetitive lines.
      That can be done with a native Web Component (JSWC)

    • If you don't want code, you need to write repetitive lines.


    Web Component:

    customElements.define("svg-spinner", class extends HTMLElement {
      connectedCallback() {
        let size = this.getAttribute("size") || 100;
        let background = this.getAttribute("background") || "transparent";
        let fill = this.getAttribute("fill") || "#336699";
        let spokes = new Array(12).fill("").map((_, idx) => {
            return `<rect transform="rotate(${idx*30} 50 50)" fill="${fill}" 
                          x="48" y="17.5" rx="0" ry="0" width="4" height="15">` +
                   `<animate attributeName="opacity"  begin="${-(idx/12)}" values="1;0" 
                             keyTimes="0;1" dur="1s" repeatCount="indefinite">
                    </animate></rect>`}).join("")
        let style = `margin:auto;background:${background} none repeat scroll 0% 0%;display:inline-block;shape-rendering:auto`;
        let svg = `<svg style="${style}" width="${size}px" height="${size}px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">` +
                   spokes + `</svg>`
        this.innerHTML = svg;
      }
    });
    <svg-spinner size="150"></svg-spinner>
    <svg-spinner size="150" fill="green"></svg-spinner>
    <svg-spinner size="150" fill="hotpink" background="pink"></svg-spinner>