Search code examples
htmlcsssvgcss-animations

SVG animation applied with mask


I'd like to apply an SVG animation to my download button.

In my practice, the arrow is supposed to scroll down recursively in the red box.

It would be something like this.

The animation still expects calibration but the main issue is that the mask doesn't work as intended, as the arrow is still visible while moving out of the red box.

What goes wrong?

I hope it can be solved without javascript involving.

see it in jsfiddle

@keyframes arrow-slide-recursively {
  from {
    transform: translate(0%, 0%)
  }
  to {
    transform: translate(0%, 50%)
  }
}

.fanbox-helper-fixed-menu-button {
  background-color: transparent;
  cursor: pointer;
  border: medium;
  padding: 4px;
  width: 54px;
  height: 54px;
}


.fanbox-helper-fixed-menu-button.busy {
  opacity: 0.3;
  pointer-events: none;
}

#fanbox-helper-quick-save-button.busy .arrow {
  animation: arrow-slide-recursively 2s linear infinite;
}
<button id="fanbox-helper-quick-save-button" class="fanbox-helper-fixed-menu-button busy">
  <svg class="fanbox-helper-svg-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
    <mask id="fanbox-helper-quick-save-arrow-area" x="120" width="272" height="440">
      <rect x="120" width="272" height="440" fill="red"></rect>
    </mask>
    
    <rect x="120" width="272" height="440" fill="red"></rect>
    
    <g id="fanbox-helper-quick-save-arrow" mask="#fanbox-helper-quick-save-arrow-area">
      <path class="arrow stick" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" d="m256,96 l0,256" stroke-width="40"></path>
      <path class="arrow edge left" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m256,392 l-104,-104"></path>
      <path class="arrow edge right" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m256,392 l104,-104"></path>
    </g>
    <path class="bowl" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m64,312 l0,148 l384,0 l0,-148"></path>
  </svg>
</button>


Solution

  • You haven't applied the mask correctly – you need to wrap the reference in a url() function like so

    svg{
      overflow:visible;
      outline:1px solid #ccc;
    }
    
    @keyframes arrow-slide-recursively {
      from {
        transform: translate(0%, 0%)
      }
      to {
        transform: translate(0%, 50%)
      }
    }
    
    .fanbox-helper-fixed-menu-button {
      background-color: transparent;
      cursor: pointer;
      border: medium;
      padding: 4px;
      width: 54px;
      height: 54px;
    }
    
    
    .fanbox-helper-fixed-menu-button.busy {
      opacity: 0.3;
      pointer-events: none;
    }
    
    #fanbox-helper-quick-save-button.busy .arrow {
      animation: arrow-slide-recursively 2s linear infinite;
    }
    <button id="fanbox-helper-quick-save-button" class="fanbox-helper-fixed-menu-button busy">
      
      <svg class="fanbox-helper-svg-icon" xmlns="http://www.w3.org/2000/svg" id="Capa_1" viewBox="0 0 512 512">
        <mask id="fanbox-helper-quick-save-arrow-area" >
          <rect x="120" width="272" height="440" fill="#fff"></rect>
        </mask>
        <rect x="120" width="272" height="440" fill="red"></rect>
        
        <g id="fanbox-helper-quick-save-arrow" mask="url(#fanbox-helper-quick-save-arrow-area)">
          <path class="arrow stick" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" d="m256,96 l0,256" stroke-width="40"></path>
          <path class="arrow edge left" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m256,392 l-104,-104"></path>
          <path class="arrow edge right" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m256,392 l104,-104"></path>
        </g>
        <path class="bowl" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m64,312 l0,148 l384,0 l0,-148"></path>
      </svg>
    </button>

    Besides, a red fill color will result in semi-transparency. For a fully opaque rendering within the mask's rect you need to use '#fff' or 'white'.

    Frankly I highly recommend to use a clip-path instead as it is also more performant (it doesn't introduce an alpha-channel)

    svg {
      overflow: visible;
      outline: 1px solid #ccc;
    }
    
    @keyframes arrow-slide-recursively {
      from {
        transform: translate(0%, 0%)
      }
      to {
        transform: translate(0%, 50%)
      }
    }
    
    .fanbox-helper-fixed-menu-button {
      background-color: transparent;
      cursor: pointer;
      border: medium;
      padding: 4px;
      width: 54px;
      height: 54px;
    }
    
    .fanbox-helper-fixed-menu-button.busy {
      opacity: 0.3;
      pointer-events: none;
    }
    
    .busy .arrow {
      animation: arrow-slide-recursively 2s linear infinite;
    }
    <button id="fanbox-helper-quick-save-button" class="fanbox-helper-fixed-menu-button busy">
    
      <svg class="fanbox-helper-svg-icon" xmlns="http://www.w3.org/2000/svg" id="Capa_1" viewBox="0 0 512 512">
        <clipPath id="clip">
              <rect x="120" width="272" height="440" fill="red" />
        </clipPath>
        <rect x="120" width="272" height="440" fill="red"></rect>
    
          <g class="fanbox-helper-quick-save-arrow" clip-path="url(#clip)" >
            <path  class="arrow" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" d="M256 96v256m0 40-104-104m104 104 104-104" stroke-width="40" />
          </g>
        <path class="bowl" stroke="black" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke-width="40" d="m64,312 l0,148 l384,0 l0,-148"></path>
      </svg>
    </button>