Matt van Andel
Matt van Andel

Reputation: 657

Scopes for SVG masks

I am using Javascript to dynamically insert an SVG-based control/timer in a custom carousel. For reference, this is the SVG (feel free to use this if you like it, btw)...

<svg class="timer-control" width="48" height="48" viewbox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <mask id="timer-animask">
      <circle cx="50%" cy="50%" r="24" fill="#fff" />
      <circle cx="50%" cy="50%" r="20" fill="#000" />
    </mask>
  </defs>
  <g mask="url(#timer-animask)">
    <path id="timer-ring" transform="translate(24,24)"/>
  </g>
  <g id="pause">
    <rect x="18" y="16" width="4" height="16"></rect>
    <rect x="25" y="16" width="4" height="16"></rect>
  </g>
  <g id="play">
    <polygon points="0,0 0,18 18,9" transform="translate(18,15)"/>
  </g>
</svg>

There are two approaches I can use to render this... I can either insert the SVG directly into the DOM or I can insert an object with a link to the external SVG file.

Here's the problem... ideally, I want to just insert the SVG directly into the DOM (again, using Javascript here), but when I have multiple instances of the SVG, the mask breaks since it references a duplicate id as the SVG is bound to the DOM.

I can think of two workarounds, neither of which I like:

Option 1: Object SVG

Embed an object instead. This keeps the SVG scope local to the object/file, so the mask does not break [as a result of conflicting ids]. However, it means I have to handle dynamic file paths within the Javascript, and I can no longer use CSS to style the SVG's components (I would have to use Javascript exclusively to reach inside and manipulate attributes). This seems bulky, inefficient, and inelegant. It will work, but I am not a fan.

Option 2: DOM Insert w/ Dynamic ID

Generate a random id seed before I insert the SVG directly into the DOM, and append that seed to all of the SVG's group ids.

E.G.

<svg class="timer-control" id="timer-control-345235" width="48" height="48" viewbox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <mask id="timer-animask-345235">
      <circle cx="50%" cy="50%" r="24" fill="#fff" />
      <circle cx="50%" cy="50%" r="20" fill="#000" />
    </mask>
  </defs>
  <g mask="url(#timer-animask-345235)">
    <path id="timer-ring-345235" transform="translate(24,24)"/>
  </g>
  <g id="pause-345235">
    <rect x="18" y="16" width="4" height="16"></rect>
    <rect x="25" y="16" width="4" height="16"></rect>
  </g>
  <g id="play-345235">
    <polygon points="0,0 0,18 18,9" transform="translate(18,15)"/>
  </g>
</svg>

Like Option 1, this is functional and it lets me use CSS to manipulate the SVG (which is good), but it also seems janky and inelegant.

Question

So here's my question... is there a better approach I haven't considered yet, or are Options 1 or 2 really my best options?

Upvotes: 1

Views: 749

Answers (1)

Paul LeBeau
Paul LeBeau

Reputation: 101918

Or you can include the SVG once at the top. Then reference the controls you need via <use>.

#pause-345235
{
  fill: orange;
}

#play-345235
{
  fill: green;
}
<svg class="timer-control" id="timer-control-345235" width="0" height="0" viewbox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <mask id="timer-animask-345235">
      <circle cx="50%" cy="50%" r="24" fill="#fff" />
      <circle cx="50%" cy="50%" r="20" fill="#000" />
    </mask>
  </defs>
  <g mask="url(#timer-animask-345235)">
    <path id="timer-ring-345235" transform="translate(24,24)"/>
  </g>
  <g id="pause-345235">
    <rect x="18" y="16" width="4" height="16"></rect>
    <rect x="25" y="16" width="4" height="16"></rect>
  </g>
  <g id="play-345235">
    <polygon points="0,0 0,18 18,9" transform="translate(18,15)"/>
  </g>
</svg>




<svg width="48" height="48">
  <use xlink:href="#pause-345235"/>
</svg>


<svg width="48" height="48">
  <use xlink:href="#play-345235"/>
</svg>

Upvotes: 1

Related Questions