Search code examples
cssurlsvgtransitionfill

Fill SVG with url and apply transition on it


i have svg which has a shape and i want to put different images on this shape when i click on links and i want to apply transition when the images changed.

I used fill attribute and put in the fill the url of the image using javascript and the images appeared but i can't apply the transition on fill attribute.

const rect = document.getElementById('logo');
const toggleLinks = document.querySelectorAll('.image-link');

toggleLinks.forEach(link => {
  link.addEventListener('click', function() {
      const fillAttribute = this.getAttribute('data-fill');

     rect.style.setProperty('fill', `url(${fillAttribute})`);
  });
});
#logo{
    transition: fill 0.5s ease;
}
<svg  id="logo" viewbox="0 0 322 419.6">
  <path  class="st0"d="M289.9,234.5c31.4-37.8,41.3-91.4,22.8-140.8c-27.9-74.6-111-112.4-185.5-84.5c-48.8,18.3-79.9,60.7-90.9,108                    C36.1,117.6,5.7,268,0.7,318.4c-1.1,10.9-0.6,28.5,5,43.4c17.4,46.6,69.5,69.4,116.1,52c16.1-6,29.4-16.6,39.3-29.2                     c0,0,0.1-0.2,0.1-0.2S258.3,272.5,289.9,234.5z" /> 
  <defs>
      <pattern id="imageFill1" x="0" y="0" width="1"height="1" >
        <image id = "patternImage "xlink:href="images-testing/production.jpg" height="100%"/>
      </pattern>
      <pattern id="imageFill2" x="0" y="0" width="1"height="1">
        <image id = "patternImage "xlink:href="images-testing/branding.jpg"height="100%"/>
      </pattern>
      <pattern id="imageFill3" x="0" y="0" width="1"height="1">
          <image id = "patternImage "xlink:href="images-testing/media.jpg" height="100%"/>
      </pattern>
  </defs>
</svg> 

<h1 style="color:white"><a class="image-link" data-fill="#imageFill1">Production</a></h1>
<h1 style="color:white"><a class="image-link" data-fill="#imageFill2">Branding</a></h1>
<h1 style="color:white"><a class="image-link" data-fill="#imageFill3">Media</a></h1>


Solution

  • Css animations or transitions actually just interpolate property values – provided these values can be interpolated: You can't interpolate between URLs.

    Instead you need to stack all images and change their order and opacity to get a cross-fade transition.

    const logo = document.getElementById("logo");
    const shape = document.getElementById("shape");
    const toggleLinks = document.querySelectorAll(".image-link");
    const slides = document.querySelectorAll('.slide')
    
    toggleLinks.forEach((link) => {
      link.addEventListener("click", (e)=> {
        const slideID = e.currentTarget.getAttribute("data-fill");
        const slide =  document.getElementById(slideID);
        // reset previously acive
        slides.forEach(slide=>{
              slide.classList.remove('prev')
              slide.classList.remove('active')
        })
        slide.previousElementSibling.classList.add('prev')
        slide.classList.add('active')
        // change stack order
        logo.append(slide)
      });
    });
    svg{
      width:10em;
    }
    
    a{
      cursor: pointer
    }
    
    .prev{
        fill-opacity:1;
    }
    
    .active {
      animation: fadeIn 1s forwards;
    }
    
    @keyframes fadeIn{
      0%{
          fill-opacity:0;
      }
      100% {
        fill-opacity: 1;
      }
    }
    <svg id="logo" viewbox="0 0 322 419.6">
      <defs>
        <path id="shape" class="st0" d="m289.9 234.5c31.4-37.8 41.3-91.4 22.8-140.8-27.9-74.6-111-112.4-185.5-84.5-48.8 18.3-79.9 60.7-90.9 108-.2.4-30.6 150.8-35.6 201.2-1.1 10.9-.6 28.5 5 43.4 17.4 46.6 69.5 69.4 116.1 52 16.1-6 29.4-16.6 39.3-29.2 0 0 .1-.2.1-.2s97.1-111.9 128.7-149.9z" />
        <pattern id="imageFill1" x="0" y="0" width="1" height="1">
          <image id="patternImage1" href="https://picsum.photos/id/237/200/300" width="100%" height="100%" preserveAspectRatio="xMinYMin slice" />
        </pattern>
        <pattern id="imageFill2" x="0" y="0" width="1" height="1">
          <image id="patternImage2" href="https://picsum.photos/id/236/200/300" width="100%" height="100%" preserveAspectRatio="xMinYMin slice" />
        </pattern>
        <pattern id="imageFill3" x="0" y="0" width="1" height="1">
          <image id="patternImage3" href="https://picsum.photos/id/230/200/300" width="100%" height="100%" preserveAspectRatio="xMinYMin slice" />
        </pattern>
      </defs>
      
      <use id="slide1" class="slide" href="#shape" fill="url(#imageFill1)" />
      <use id="slide2" class="slide" href="#shape" fill="url(#imageFill2)" />
      <use id="slide3" class="slide active" href="#shape" fill="url(#imageFill3)" />
    </svg>
    
    <h2><a class="image-link" data-fill="slide1">Production</a></h2>
    <h2><a class="image-link" data-fill="slide2">Branding</a></h2>
    <h2><a class="image-link" data-fill="slide3">Media</a></h2>

    I'm cloning the filled shape with <use> elements.
    Each image has its own cloned shape filled with the respective pattern fill.

      <use id="slide1" class="slide" href="#shape" fill="url(#imageFill1)" />
      <use id="slide2" class="slide" href="#shape" fill="url(#imageFill2)" />
      <use id="slide3" class="slide active" href="#shape" fill="url(#imageFill3)" />  
    

    We can easily move the current image on top via svg.append().