0

I am trying to create an image with an SVG mask layer over it. I have 3 different svg paths being stored in a js array and based on which one the user pics (using a select box) a different mask should appear over the image. The problem is that the clip-paths are not all the same size.

How can I scale them so that whichever mask/clip-path the user selects will scale to fit properly over the entire image? I want to do this programmatically as eventually these paths will be user generated so I won't know what size they will be.

The image will always be the same size 400px X 400px and I'd like the scaled svg path mask to be centered in that frame if the scaled image doesn't take up the entire height or width.

Any help would be greatly appreciated.

var nomask = "M0 0 L400 0 L400 400 L0 400 L0 0 z";
var heart = "M 321.4214 178.5786 l -141.4214 141.4214 l -141.4214 -141.4214 a 100 100 315 0 1 141.4214 -141.4214 a 100 100 315 0 1 141.4214 141.4214 z";
var star = "M 22.928 118.8 l 35.12 -18.048 l 35.112 18.048 l -7.856 -46.032 l 23.328 -34.752 l -34.488 3.28 L 60.304 7.376 L 42.832 40.96 l -39.184 5.568 l 28.184 26.848 l -6.64 37.84 z";
var blob = "M215,100.3c97.8-32.6,90.5-71.9,336-77.6\
            c92.4-2.1,98.1,81.6,121.8,116.4c101.7,149.9,53.5,155.9,14.7,178c-96.4,54.9,5.4,269-257,115.1c-57-33.5-203,46.3-263.7,20.1\
            c-33.5-14.5-132.5-45.5-95-111.1C125.9,246.6,98.6,139.1,215,100.3z";

var masks = [nomask, star, blob, heart];

function changeMask(mask) {
  document.getElementById("svgPath").children[0].setAttribute('d', masks[mask]);
}
body {
  margin: 10px;
}

.imageContainer {
  margin: 5px 0;
  border: 1px solid black;
  padding: 5px;
  width: fit-content;
}

.clippingMask {
  -webkit-clip-path: url(#svgPath);
  clip-path: url(#svgPath);
}
<select id="maskSelect" onchange="changeMask(this.value);">
  <option value="1">star</option>
  <option value="2">blob</option>
  <option value="3">heart</option>
  <option value="0" selected>- no mask -</option>
</select>

<div class="imageContainer">
  <img class="clippingMask" src="https://picsum.photos/400">
</div>

<svg height="0" width="0">
  <defs>
    <clipPath id="svgPath">
      <path fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10"
      d="M0 0 L400 0 L400 400 L0 400 L0 0 z" />
    </clipPath>
  </defs>
</svg>

Note that in my example to star is much to small and the blob is too big.

Thanks!

chrwahl
  • 8,675
  • 2
  • 20
  • 30
user3182413
  • 59
  • 1
  • 7
  • `` s are not responsive by default - so you either need to apply some scaling tricks or switch to `` elements. See also ["Responsive clip-path with inline SVG"](https://stackoverflow.com/questions/28311741/responsive-clip-path-with-inline-svg?noredirect=1&lq=1) or ["Problem with CSS SVG Clip path - not working"](https://stackoverflow.com/questions/71286372/problem-with-css-svg-clip-path-not-working/71299443#71299443) – herrstrietzel Apr 04 '23 at 19:59
  • Use objectBoundingBox units – Robert Longson Apr 04 '23 at 20:38
  • thanks @herrstrietzel. Any hints or help on how to convert this and use instead? Is that what I should have been using all along? – user3182413 Apr 05 '23 at 01:57

1 Answers1

1

To make your clip-paths resonsive you can use Yoksel's clip-path helper.

It will scale and fit your d path data string to a 1x1 bounding box.
As commented by Robert Longson you also need to apply. clipPathUnits="objectBoundingBox"

var nomask = "M0,0 L1,0 L1,1 L0,1 L0,0";
var heart = "M0.941,0.572 l-0.414,0.453 l-0.414,-0.453 a0.293,0.32,315,0,1,0.414,-0.453 a0.293,0.32,315,0,1,0.414,0.453";
var star = "M0.184,1 L0.518,0.838 L0.853,1 L0.778,0.587 L1,0.275 L0.671,0.304 L0.54,0 L0.373,0.301 L0,0.351 L0.268,0.592 L0.205,0.932z";
var blob = "M0.27,0.172 C0.416,0.101,0.405,0.017,0.771,0.005 C0.909,0,0.917,0.18,0.953,0.255 C1,0.578,1,0.591,0.975,0.639 C0.831,0.757,0.983,1,0.591,0.887 C0.506,0.814,0.288,0.986,0.198,0.93 C0.148,0.899,0,0.832,0.056,0.691 C0.137,0.487,0.096,0.255,0.27,0.172";

var masks = [nomask, star, blob, heart];

function changeMask(mask) {
  document.getElementById("svgPath").children[0].setAttribute('d', masks[mask]);
}
body {
  margin: 10px;
}

.imageContainer {
  margin: 5px 0;
  border: 1px solid black;
  padding: 5px;
  width: fit-content;
}

.clippingMask {
  -webkit-clip-path: url(#svgPath);
  clip-path: url(#svgPath);
}
<select id="maskSelect" onchange="changeMask(this.value);">
  <option value="1">star</option>
  <option value="2">blob</option>
  <option value="3">heart</option>
  <option value="0" selected>- no mask -</option>
</select>

<div class="imageContainer">
  <img class="clippingMask" src="https://picsum.photos/400">
</div>

<svg height="0" width="0">
  <defs>
    <clipPath id="svgPath" clipPathUnits="objectBoundingBox" >
      <path fill="#FFFFFF" stroke="#000000" stroke-miterlimit="10"
      d="M0 0 L400 0 L400 400 L0 400 L0 0 z" />
    </clipPath>
  </defs>
</svg>

Further reading: About clip-path caveats and pitfalls

Eric Meyer: Scaling SVG Clipping Paths for CSS Use
Css-tricks: Unfortunately, clip-path: path() is Still a No-Go
Css-tricks: Clipping and Masking in CSS

Clip path helper

Convert SVG absolute clip-path to relative

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34