csssvg

How to scale svg to viewport, but keep aspect ratio on filled image


I want to fill a viewport with an svg, and add a fill to a path in this svg.

When I set preserveAspectRatio="xMinYMin slice" to the image pattern, it will preserve it's aspect ratio nicely, but the path will not scale.

If I then add preserveAspectRatio="none" to the svg element, the whole element will fit to the viewport, but my image will not scale properly.

What am I overlooking?

<svg height="100%" width="100%" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1400 750" preserveAspectRatio="none">
    <defs>
      <pattern id='home' width="1" height="1" preserveAspectRatio="xMinYMin slice">
        <image xlink:href='https://i.imgur.com/4AiXzf8.jpg' width="100%" height="100%" preserveAspectRatio="xMinYMin slice"></image>
      </pattern>
    </defs>
    <path id="bg" class="cls-1" d="M0,0V622.58S250,711,683,711s683-88.42,683-88.42V0Z"style="fill: url(#home)"></path>
  </svg>


Solution

  • If you set the whole SVG to preserveAspectRatio="none", then everything inside the SVG will stretch and there is no way to counteract that. So you have to do it a different way.

    What we have to do is remove the viewBox and the preserveAspectRatio and just set the width of the SVG to 100% and the height to the height of our <path> (711px). That way the SVG viewport fills the width of the page and keeps its height at the size we want.

    The next step is to move the <image> out of the <pattern>, make its width and height 100% x 100% and set preserveAspectRatio="xMinYMin slice". So it now scales to fill our wide SVG, and keeps its aspect ratio.

    The last step is to apply a <clipPath> to the image to give it the shape we want. To get the clip path to automatically fit itself to the <image>, we need to use clipPathUnits="objectBoundingBox".

    The thing with objetBoundingBox units is that (0,0) represents the top left of the element it is applied to, and (1,1) corresponds to the bottom-right of the element it is applied to. But our path is much bigger than that. It has a width and height of 1366x711.

    To fix that, we need to scale the path so that it is only 1x1 in size, instead of 1366x711. So we apply a transform and scale it by 1/1366 in X, and 1/711 in Y.

    transform="transform="scale(0.000732, 0.001406)"
    

    The final result is this:

    body {
      margin: 0;
      padding: 0;
    }
    <svg width="100%" height="711px" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <clipPath id="clip" clipPathUnits="objectBoundingBox">
          <path d="M0,0V622.58S250,711,683,711s683-88.42,683-88.42V0Z"
                transform="scale(0.000732, 0.001406)"></path>
        </clipPath>
      </defs>
      <image xlink:href='https://i.imgur.com/4AiXzf8.jpg' width="100%" height="100%" preserveAspectRatio="xMinYMin slice"
             clip-path="url(#clip)"></image>
    </svg>