1

I have an image inside an inline SVG that switches between two masks when it is hovered over.

However, the CSS transition is not working with transition-property: mask;

Is there a different method that can be used that works with a CSS transition?

I also tried styling <mask> but it seems that definition elements cannot be styled (?).

<!DOCTYPE html>
<html>
<head>
 <meta charset="utf-8">
</head>
<body>
<div style="width: 604px; height: 302px; margin: 20px auto;"> <!-- IE needs width AND height specified to scale the SVG inside correctly. -->
<svg id="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1208 604">

 <style type="text/css">
  /* <![CDATA[ */
  .mask_hover a:link,
  .mask_hover a:visited {
   mask: url(#mask_right);
  }
  .mask_hover a:hover,
  .mask_hover a:active {
   transition-property: mask;
   transition-duration: 0.4s;
   transition-timing-function: ease-in-out;
   mask: url(#mask_left);
  }
  /* ]]> */
 </style>

 <defs>
  <mask id="mask_left">
   <circle id="circle_l" cx="416" cy="216" r="202" fill="#fff"/>
  </mask>
  
  <mask id="mask_right">
   <circle id="circle_r" cx="809" cy="337" r="202" fill="#fff"/>
  </mask>
  
 </defs>
 
 <g class="mask_hover">
  <a xlink:href="#">
   <image id="img" width="1208" height="604" xlink:href="https://i.stack.imgur.com/LCpGU.jpg"/>
  </a>
 </g>
</svg>
</div>
</body>
</html>

Image credit: Pixabay

Mentalist
  • 1,530
  • 1
  • 18
  • 32

1 Answers1

4

It is not really the mask property that can't be animated, but the url() <func>.

To create a transition, you need to have a state 1 that goes to a state 2 with the possibility to create an interpolation between both states.
When you use url(#1) and wish to go to url(#2), there is no way to create any interpolation between these two states, because url(#1.5) will not be the in-between state.

What can be animated though is the content of your mask.

In SVG2, you can set directly the properties that did change (i.e cx and cy) from CSS, but this is still only supported by Blink&Safari browsers:

<svg id="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1208 604">
  <style type="text/css">
  /* <![CDATA[ */
    .mask_hover a:link,
    .mask_hover a:visited {
      mask: url(#mask);
    }
    #mask > circle {
      transition-property: cx, cy;
      transition-duration: 0.4s;
      transition-timing-function: ease-in-out;    
    }
    .mask_hover a:hover + defs #mask > circle,
    .mask_hover a:active + defs #mask > circle {
      cx: 416;
      cy: 216;
    }
  /* ]]> */
  </style>
  <g class="mask_hover">
    <a xlink:href="#">
      <image id="img" width="1208" height="604" xlink:href="https://i.stack.imgur.com/LCpGU.jpg"/>
    </a>
    <!-- we need to move it here so we can target it with our CSS rules -->
    <defs>
      <mask id="mask">
        <circle id="circle_l" cx="809" cy="337" r="202"   fill="#fff"/>
      </mask>  
    </defs>
  </g>
</svg>

For other browsers that don't support this part of SVG2, you should be able to do it with the transform property, but somehow, Firefox doesn't accept it...

<svg id="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1208 604">
  <style type="text/css">
  /* <![CDATA[ */
    .mask_hover a:link,
    .mask_hover a:visited {
    mask: url(#mask);
    }
    #mask > circle {
    transition-property: transform;
    transition-duration: 0.4s;
    transition-timing-function: ease-in-out;    
    }
    .mask_hover a:hover + defs #mask > circle,
    .mask_hover a:active + defs #mask > circle {
    transform: translate(-393px, -121px);
    }
  /* ]]> */
  </style>
  <g class="mask_hover">
    <a xlink:href="#">
     <image id="img" width="1208" height="604" xlink:href="https://i.stack.imgur.com/LCpGU.jpg"/>
    </a>
  <!-- we need to move it here so we can target it with our CSS rules -->
  <defs>
    <mask id="mask">
     <circle id="circle_l" cx="809" cy="337" r="202" fill="#fff"/>
    </mask>  
  </defs>
  </g>
</svg>

So you may also want to try with SMIL, but there you won't be able to have the :active rule, and Safari is buggy in implementing an hover state...

<svg id="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1208 604">
  <style type="text/css">
  /* <![CDATA[ */
    .mask_hover a:link,
    .mask_hover a:visited {
      mask: url(#mask);
    }
  /* ]]> */
  </style>
  <defs>
    <mask id="mask">
      <circle id="circle_r" cx="809" cy="337" r="202" fill="#fff">
        <animateTransform attributeName="transform"
          attributeType="XML"
          type="translate"
          to="-393 -121"
          dur="0.4s"
          fill="freeze"
          begin="anchor.mouseover"/>
        <animateTransform attributeName="transform"
          attributeType="XML"
          type="translate"
          to="0 0"
          dur="0.4s"
          fill="freeze"
          begin="anchor.mouseout"/>
      </circle>
   </mask>
  </defs>
  <g class="mask_hover">
    <a xlink:href="#" id="anchor">
      <image id="img" width="1208" height="604" xlink:href="https://i.stack.imgur.com/LCpGU.jpg"/>
    </a>
  </g>
</svg>

So a final way might be to implement the whole thing using only clip-path from CSS:

<svg id="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1208 604">
  <style type="text/css">
  /* <![CDATA[ */
    .mask_hover a image {
      transition: clip-path .4s;
    }
    .mask_hover a {
      pointer-events: all;
    }
    .mask_hover a:link image,
     .mask_hover a:visited image{
      clip-path: circle(202px at 809px 337px);
    }
    .mask_hover a:hover image,
     .mask_hover a:active image{
      clip-path: circle(202px at 416px 216px);
    }
  /* ]]> */
  </style>
  <g class="mask_hover">
    <a xlink:href="#">
      <image id="img" width="1208" height="604" xlink:href="https://i.stack.imgur.com/LCpGU.jpg"/>
      <!-- so we can hover everywhre -->
      <rect fill="none" width="1208" height="604"/>
    </a>
  </g>
</svg>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thank you for this well explained answer! I should have explained more about what I'm aiming for and why. The transition I want to animate is a fade-in/fade-out effect. I have done this by animating the opacity of various instances of an image that are each clipped differently ([this is the result](https://stackoverflow.com/a/55039369/2454914)). The problem is that it is not performant when dealing with a large number of clipping paths (nearly 50). Do you think it would be possible to animate (with a transition) the fill color of the mask from black (invisible) to white (visible)? – Mentalist Mar 15 '19 at 03:35
  • Yes, is now animatable, so this should theoretically work, but once again, you may fall in a lot of discrepancy with various implementations, and particularly old IE may not work. – Kaiido Mar 15 '19 at 03:38
  • Seeing as `` is inside of `` by necessity, do you know of a way to target its fill color? I couldn't work it out. – Mentalist Mar 15 '19 at 03:40
  • The same I did: you move the defs as a sibling of your Then `a:hover + defs > mask > circle { fill: #CCC }` – Kaiido Mar 15 '19 at 03:42
  • Ah, I see now. Brilliant! Didn't know that was possible. I will try with that approach. – Mentalist Mar 15 '19 at 03:46