Can I add a mask to an svg-element without using an id?
Asked Answered
C

3

6

I want to assign a svg-mask to a svg-image. I can make this work using an id on the mask like this:

<svg id="svg1" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
     <defs>
       <mask id="mask">
         <circle cx="100" cy="100" r="100" fill="white"></circle>
       </mask>   
     </defs>
     <rect x="0" y="0" width="200" height="200" fill="red" mask="url(#mask)"></rect>
</svg>

However I want to load this svg multiple times, with a different id in the svg-tag. Therefore I will generate duplicates of the '#mask'-id. Using multiple id's is invalid code. So I want to use a class to refer to the appropriate mask. That means I cannot use the mask=url()-technique.

<svg id="svg2" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg" version="1.1">
     <defs>
       <mask class="mask">
         <circle cx="100" cy="100" r="100" fill="white"></circle>
       </mask>   
     </defs>
     <rect x="0" y="0" width="200" height="200" fill="red" mask="url(can't use this)"></rect>
</svg>

Is there a way I can apply a mask to the rect element if the mask has a class instead of id? Maybe using javaScript or some other way I didn't think of.

The full story/context: I am actually making an svg image slider-module for Joomla with php. This php generates a module containing javascript, css and an svg. I use the javascript to animate the mask. I do actually have it working with unique id's. I was just wondering if there is a way to assign a mask to an element without referring to id's. I may want to do this because my code is getting a bit more confusing to read, because I have to use some php in my javascript/svg and css for each unique id.

Comp answered 3/12, 2017 at 22:53 Comment(2)
Is the mask always the same? If so, load it once separately. Or generate and use a different id for each (derived from the svg element is, for instance)Crumhorn
@Crumhorn No the masks are not always the same. They can be the same sometimes, but not always. At the moment I am using the different id-approach. But I think I might prefer to refer to it changing only one id in the entire svg.Comp
C
8

No. You can only reference masks via an id. You cannot reference SVG masks any other way.

Cabbageworm answered 4/12, 2017 at 4:34 Comment(3)
I was afraid that would be the case. I'll wait a week or so accepting the answer in case someone suddenly shows up with a miracle solution :p. Feel free to remind me if I forget xD.Comp
There won't be a miracle solution. You can verify this yourself by reading the SVG spec. :)Cabbageworm
@RobMonhemius If you're worried about collisions, you can always use a UUID (JS; Python) to generate a name you're sure no-one else is currently using.Gemini
J
6

According to your description I understand you have a identical grafical entity you want to mask with different forms, multiple times. Write that down DRY:

<!-- start with an invisible svg that only contains mask definitions -->
<svg width="0" height="0"
     xmlns="http://www.w3.org/2000/svg">
    <defs>
        <!-- first, you have a circular mask -->
       <mask id="circle-mask">
         <circle cx="100" cy="100" r="80" fill="white" />
       </mask>   
        <!-- then, you have a different mask, lets say a diamond -->
       <mask id="diamond-mask">
         <polygon points="100,20 180,100 100,180 20,100" fill="white" />
       </mask>   
     </defs>
</svg>

<!-- further into your document, you want to mask a rectangle -->
<svg id="svg1" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg">
     <!-- reference the circle mask -->
     <rect x="0" y="0" width="200" height="200" fill="red" mask="url(#circle-mask)" />
</svg>

<!-- with the circle again, as often as you want, nothing changes -->
<svg id="svg2" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg">
     <!-- the mask is the same, so no difference to above -->
     <rect x="0" y="0" width="200" height="200" fill="red" mask="url(#circle-mask)" />
</svg>

<!-- and now with the diamond; that one is different -->
<svg id="svg3" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg">
     <!-- if the mask changes, you need to change the reference -->
     <rect x="0" y="0" width="200" height="200" fill="red" mask="url(#diamond-mask)" />
</svg>

You could also reference the masks in a stylesheet and give your referencing elements a class according to the mask shape:

.masked.circular rect {
    mask: url(#circle-mask);
}
.masked.diamond rect {
    mask: url(#diamond-mask);
}
<svg width="0" height="0"
     xmlns="http://www.w3.org/2000/svg">
    <defs>
       <mask id="circle-mask">
         <circle cx="100" cy="100" r="80" fill="white" />
       </mask>   
       <mask id="diamond-mask">
         <polygon points="100,20 180,100 100,180 20,100" fill="white" />
       </mask>   
     </defs>
</svg>

<svg id="svg1" class="masked circular" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg">
     <rect x="0" y="0" width="200" height="200" fill="red" />
</svg>

<svg id="svg2" class="masked circular" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg">
     <rect x="0" y="0" width="200" height="200" fill="red" />
</svg>

<svg id="svg1" class="masked diamond" width="5cm" height="5cm" viewBox="0 0 200 200"
     xmlns="http://www.w3.org/2000/svg">
     <rect x="0" y="0" width="200" height="200" fill="red" />
</svg>
Jelsma answered 4/12, 2017 at 2:0 Comment(1)
That wouldn't work for me. I suppose I should have provided more context in the question (I tried to keep the question as simple as possible). I added the situation to the question. Thx for trying to answer tough ;).Comp
C
0

Yet another wonderful shortsightedness of the spec that makes me want to go live in a cabin in the woods.

Here's a <script> that you should hopefully just be able to drop into an SVG and de-duplicate mask ids with:

<script>
  {
    const svg = document.currentScript.closest("svg");
    const mask = svg.querySelector("mask");
    const target = svg.querySelector("[mask]");
    const id = `mask-${String(Math.random()).slice(2)}`;
    mask.setAttribute("id", id);
    target.setAttribute("mask", `url(#${id})`);
  }
</script>

Notes:

  • Uses Math.random() as an id for brevity, almost guaranteed to be unique. In my case, 1 in a trillion chances of an id collision is fine because I'm just using masks for some animation flourishes.
  • Surrounding {} braces needed to avoid re-defining svg and other vars. type="module" not usable here, unless you want to use a method besides currentScript to get the current svg.

Ideas for modification:

  • Use window.maskId ??= 0 and const id = "mask-" + window.maskId++; instead of Math.random().
  • Check for duplicate ids before generating a new one.
  • Keep any original id name for better rendered SVG readability (e.g. my-mask-1).
  • Do other things that require unique IDs besides <mask>s.
Clemenciaclemency answered 11/9, 2024 at 1:35 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.