Oak
Oak

Reputation: 26868

How can I apply a mask from an inline SVG that will work in Chrome?

I want to clip my element using an SVG shape which is defined in the same HTML file (an inline SVG).

It works with clip-path:

div {
  width: 200px;
  height: 200px;
  background-color: red;
  clip-path: url("#c");
}
<div>
  <svg>
    <defs>
      <clippath id="c">
        <circle cx="100" cy="100" r="50" />
      </clippath>
    </defs>
  </svg>
</div>

But when using a mask, although this works fine in Firefox, it applies no masking in Chrome:

div {
  width: 200px;
  height: 200px;
  background-color: red;
  mask: url("#m");
}
<div>
  <svg>
    <defs>
      <mask id="m">
        <circle cx="100" cy="100" r="50" fill="white" />
      </mask>
    </defs>
  </svg>
</div>

Searching around (example), it seems as though Chrome does not expect the mask to refer to a definition, but instead to an entire image. Is there a way to refer to an entire image if it has been inlined, though? Or, is there anything else I could do to apply a mask from an inline element?

Upvotes: 8

Views: 5934

Answers (2)

Kaiido
Kaiido

Reputation: 136736

Here is an ugly js solution, which will

  • grab the content of your <mask>,
  • copy it in a new svg element (luckily chrome doesn't need width and height),
  • convert it to a data-URI and set it to the -webkit-mask-image property.

const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const mask = document.querySelector("mask");
svg.innerHTML = mask.innerHTML;
const markup = new XMLSerializer().serializeToString(svg);
const url = `data:image/svg+xml;charset=utf8,${ encodeURIComponent(markup) }`;
const div = document.getElementById('mask-me');
div.style.webkitMaskImage = `url('${ url }')`;
#mask-me {
  width: 200px;
  height: 200px;
  background-color: red;
  /**
    This will be overidden by -webkit-mask-image even in Firefox.
    We could have avoided it with a CSS variable, e.g
    -webkit-mask-image: var(--workaround-url);
    but Safari acts in all ways as if they did support inline SVG as mask,
    when they don't.
    So, for them, we have to use the workaround everywhere...
  mask-image: url(#m);
  **/
}
<div id="mask-me">
  <svg>
    <defs>
      <mask id="m">
        <circle cx="100" cy="100" r="50" fill="white" />
      </mask>
    </defs>
  </svg>
</div>


For the ones needing the mask-mode: luminance feature, you need to do it manually in the generated SVG image through a <feColorMatrix type="luminanceToAlpha"/>:

const svgNS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(svgNS, "svg");
// create a <filter><feColorMatrix type="luninanceToAllpha"/>
const alpha = document.createElementNS(svgNS, "feColorMatrix");
alpha.setAttribute("type", "luminanceToAlpha");
const filter = document.createElementNS(svgNS, "filter");
filter.setAttribute("id", "alpha");
filter.append(alpha);
svg.append(filter);
// apply the filter on all the <mask> content
const g = document.createElementNS(svgNS, "g");
g.setAttribute("filter", "url(#alpha)");
// luminanceToAlpha disregards semi-transparent pixels,
// so we add a black background to have no such thing 
const bg = document.createElementNS(svgNS, "rect");
bg.setAttribute("width", "100%");
bg.setAttribute("height", "100%");
g.append(bg);

var mask = document.querySelector('mask').cloneNode(true);
g.append(...mask.children);
svg.append(g);

const markup = new XMLSerializer().serializeToString(svg);
const url = "data:image/svg+xml;charset=utf8," + encodeURIComponent(markup);
const div = document.getElementById("mask-me");
div.style.webkitMaskImage = `url('${ url }')`;
.border {
  display: inline-block;
  border: 1px solid;
}
#mask-me {
  width: 300px;
  height: 200px;
  background-color: red;
  /** Safari doesn't really support mask-image using inline SVG either...
    mask-image: url(#m);
    mask-mode: luminance;
  **/
}
<div id="mask-me">
  <svg width="0">
    <defs>
      <mask id="m">
        <circle cx="100" cy="100" r="50" fill="white" />
        <rect x="90" y="90" width="20" height="20" fill="black" />
      </mask>
    </defs>
  </svg>
</div>

Upvotes: 5

Paul LeBeau
Paul LeBeau

Reputation: 101820

Last time I checked Chrome doesn't support the mask property. It only supports mask-image.

I imagine that is still the case.

Upvotes: -1

Related Questions