Search code examples
htmlcsssvgresponsive-designmasking

How to properly scale an SVG mask within a container element?


I'm trying to apply an SVG mask to a div element, but I'm encountering an issue where the mask is not scaling correctly with the container. The mask appears to overflow the container, showing only part of the masked content. I am aware that the SVG is 4000 x 7000 but there surely is a way to scale down, right?

In the attached image, you can see how the plain SVG, and then the mask made out of it (the goal is to apply the gradient effect using the mask).

Below you can also find the code for both of the shown examples:

simple SVG:

<html>
    <head>   
        <style>
            body {
                background-color: beige;
            }

            .logo {
                width: 50%;
                max-width: 300px;
                min-width: 150px;
                z-index: 10;
                aspect-ratio: 4.5;
                margin: 10px 0 25px;
            }

            .wrapper {
                width: 100%;
                display: flex;
                align-items: center;
                flex-direction: column;
            }

            .scale {
                width: 30px;
                height: 30px;
                border: 1px solid black;
                font-size: 15;
            }
        </style>
    </head>
    <body>
        <div class="wrapper">
            <svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7000 4000">
                <path d="M 3000,3000 L 5000,0 L 7000,3000 Z" />
                <path d="M 2000,3000 L 0,0 L 4000,0 Z"/>
            </svg>
            <div class="scale">
                30px
            </div>
        </div>
    </body>
</html>

masked gradient:

<html>
    <head>   
        <style>
            body {
                background-color: beige;
            }

            .logo {
                width: 50%;
                max-width: 300px;
                min-width: 150px;
                z-index: 10;
                aspect-ratio: 4.5;
                margin: 10px 0 25px;
            }

            svg {
                position: absolute;
            }

            .wrapper {
                width: 100%;
                display: flex;
                align-items: center;
                flex-direction: column;
            }

            @keyframes logoGradientAnimation {
                0%   { background-position:  100%; }
                100% { background-position: -100%; }
            } 

            .gradient {
                height: 100%;
                width: 100%;
                object-fit: cover;
                -webkit-mask-image: url(#logo-mask);
                mask-image: url(#logo-mask);
                background: linear-gradient(135deg, black 60%, #ea3c96, black 75%);
                animation: 3s cubic-bezier(.25,.50,.75,.50) infinite logoGradientAnimation;
                background-size: 200%;
                mask-size: contain;
            }

            mask > path {
                fill: white !important;
            }

            .scale {
                width: 30px;
                height: 30px;
                border: 1px solid black;
                font-size: 15;
            }
        </style>
    </head>
    <body>
        <div class="wrapper">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 7000 4000">
                <defs>
                    <mask id="logo-mask">
                        <path d="M 3000,3000 L 5000,0 L 7000,3000 Z" />
                        <path d="M 2000,3000 L 0,0 L 4000,0 Z"/>
                    </mask>
                </defs>
            </svg>
            <div class="logo">
                <div class="gradient">
                </div>
            </div>
            <div class="scale">
                30px
            </div>
        </div>
    </body>
</html>

I've tried setting the width and height of the SVG to 100%, and also trying to set maskUnits & maskContentUnits but the mask still overflows the .logo container. How can I ensure that the mask scales correctly with the element it is applied to?

Any advice is very welcome!


Solution

  • Apparently, the mask-size property has no effect on inline SVG references.

    As a workaround you may convert your mask SVG to a data URL:

    mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7000 4000'><path d='M 3000 3000 L 5000 0 L 7000 3000 Z'/><path d='M 2000 3000 L 0 0 L 4000 0 Z' /></svg>");
    

    body {
      background-color: beige;
    }
    
    .logo {
      width: 50%;
      max-width: 300px;
      min-width: 150px;
      z-index: 10;
      aspect-ratio: 7/4;
      margin: 10px 0 25px;
      position: relative;
    }
    
    .wrapper {
      width: 100%;
      display: flex;
      align-items: center;
      flex-direction: column;
    }
    
    .gradient {
      position: absolute;
      width: 100%;
      height: 100%;
      object-fit: cover;
      mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 7000 4000'><path d='M 3000 3000 L 5000 0 L 7000 3000 Z'/><path d='M 2000 3000 L 0 0 L 4000 0 Z' /></svg>");
      background: linear-gradient(135deg, black 60%, #ea3c96, black 75%);
      animation: 3s cubic-bezier(0.25, 0.5, 0.75, 0.5) infinite logoGradientAnimation;
      background-size: 200%;
      mask-size: cover;
      mask-repeat: no-repeat;
    }
    
    .scale {
      width: 30px;
      height: 30px;
      border: 1px solid black;
      font-size: 15;
    }
    
    @keyframes logoGradientAnimation {
      0% {
        background-position: 100%;
      }
      100% {
        background-position: -100%;
      }
    }
    <div class="wrapper">
      <div class="logo">
        <div class="gradient">
        </div>
      </div>
      <div class="scale">
        30px
      </div>
    </div>