Teon
Teon

Reputation: 37

Animating SVG element on viewport

My question is related to my previous question Triggering css animate class on scroll

I am trying to figure out how to trigger the viewport Javascript on the inner SVG element class. You can see here the example: http://codepen.io/anon/pen/Afqza

<div style="height: 400px;"></div>
<svg version="1.1" id="lock" xmlns="http://www.w3.org/2000/svg" width="85px" height="85px" viewBox="0 0 103 103" ><g><g><g><g><circle style="fill:#E84849;" cx="51.5" cy="51.501" r="51.125"/></g></g></g></g><g><g><g class="shackle"><path style="fill:#CFC7BE;" d="M78.345,46.518c0-14.869-11.813-28.387-26.386-28.387c-14.573,0-26.386,13.518-26.386,28.387h6.829c0-11.021,8.756-21.419,19.557-21.419s19.557,10.398,19.557,21.419H78.345z"/><path style="fill:#E8E7E7;" d="M61.385,20.101v7.816c3.039,1.927,5.583,4.717,7.362,7.975V24.879C66.562,22.886,64.076,21.26,61.385,20.101z"/></g><g><path style="fill:#F4E028;" d="M78.358,79.801c0,3.116-2.579,5.642-5.765,5.642H31.281c-3.186,0-5.764-2.525-5.764-5.642V46.419c0-3.116,52.841-3.116,52.841,0V79.801z"/></g><g><path style="fill:#DAC425;" d="M58.047,59.944c0-3.253-2.638-5.89-5.889-5.89c-3.253,0-5.889,2.637-5.889,5.89c0,2.481,1.536,4.599,3.705,5.468v5.431c0,1.151,0.935,2.084,2.085,2.084c1.153,0,2.086-0.933,2.086-2.084v-5.36C56.418,64.666,58.047,62.498,58.047,59.944z"/></g><g><path style="fill:#D0B82B;" d="M46.048,59.944c0-3.253,2.637-5.89,5.891-5.89c0,0-4.105,2.737-4.105,5.99c0,3.312,3.097,5.276,3.097,5.276v5.581c0,1.153,1.104,2.024,1.104,2.024c-1.15,0-2.085-0.933-2.085-2.084v-5.36C47.677,64.666,46.048,62.498,46.048,59.944z"/></g></g><g><polygon style="fill:#F8E349;" points="68.747,85.442 61.385,85.442 61.385,44.219 68.747,44.585 "/></g></g></svg>


    .shackle {

     animation-name: open;
    -webkit-animation-name: open;   
    animation-duration: 0.5s;   
    -webkit-animation-duration: 0.5s;
    transform: rotate(0deg);
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
    -webkit-transform: rotate(0deg);
    transform-origin: bottom left;

}

@keyframes open {
    0% {
        transform: rotate(0deg);    
    }
    50% {
        transform: rotate(-10deg);
    }
    100% {
        transform: rotate(0deg);    
    }                       
}

@-webkit-keyframes open {
    0% {
        -webkit-transform: rotate(0deg);    
    }
    50% {
        -webkit-transform: rotate(-10deg);
    }
    100% {
        -webkit-transform: rotate(0deg);    
    }

}

var onAppear = [];

document.addEventListener("DOMContentLoaded", function() {
  onAppear = [].map.call(document.querySelectorAll("#lock"), function(item ) {
    return item;
  });
}, false);

window.addEventListener("scroll", function() {
  onAppear.forEach(function(elem) {
    var vwTop = window.pageYOffset;
    var vwBottom = (window.pageYOffset + window.innerHeight);
    var elemTop = elem.offsetTop;
    var elemHeight = elem.offsetHeight;

    if (vwBottom > elemTop && ((vwTop - elemHeight) < elemTop)) {
     elem.classList.add("shackle");
    } else {
      elem.classList.remove("shackle");
    }
  });
}, false);

Currently the whole padlock animates instead of the shackle that I want to animate. Must be something simple but I cannot figure it out.

Upvotes: 2

Views: 2057

Answers (1)

haxxxton
haxxxton

Reputation: 6442

The issue is that you arent applying the animation class to the shackle element, you are applying it to the lock element.

Because you are playing around with CSS3 and SVG, I can assume you dont need to accommodate for IE7 and below. Therefore, we can assume it's safe to use JS's querySelector method.

  1. First, we'll update the style definition to indicate the class is for an animation definition (and also to separate it from the class on the shackle element that we'll use to select it).

    change .shackle to .animShackle in CSS

  2. Second, we'll need to update the scroll event listener to search within the supplied element for the .shackle classed element, and then apply the animation class to that.

    update JS

JS

window.addEventListener("scroll", function() {
  onAppear.forEach(function(elem) {
    var vwTop = window.pageYOffset;
    var vwBottom = (window.pageYOffset + window.innerHeight);
    var elemTop = elem.offsetTop;
    var elemHeight = elem.offsetHeight;
    var shackle = elem.querySelector('.shackle');

    if (vwBottom > elemTop && ((vwTop - elemHeight) < elemTop)) {
     shackle.classList.add("animShackle");
    } else {
      shackle.classList.remove("animShackle");
    }
  });
}, false);

UPDATE

To make the code more extensible to the need for additional elements with their own animations we need to change some of our variable names so that they feel more universal, and update the way we are getting and setting the animation class.

  1. Add a universal class to the animated SVG's so that we can find them in our onAppear function

    add class animatedSVG update querySelectorAll method to use new class rather than single id

  2. Update the class name on the animated element within the SVG so that we can access it within the scroll onAppear.forEach method

    update class .shackle to .animatedElement in HTML update elem.querySelector method to use new class rather than non-generic .shackle

  3. Use the SVG's id attribute to create a classname for animation

    add a new variable called animationClass made from the SVG id with 'anim' prepended

HTML now requires the following 3 things:

  1. id attribute on SVG
  2. class="animatedSVG" on SVG
  3. class="animatedElement" on element within SVG you wish to animate

Updated JS

var onAppear = [];

document.addEventListener("DOMContentLoaded", function() {
  onAppear = [].map.call(document.querySelectorAll(".animatedSVG"), function(item) {
    return item;
  });
}, false);

window.addEventListener("scroll", function() {
  onAppear.forEach(function(elem) {
    var vwTop = window.pageYOffset;
    var vwBottom = (window.pageYOffset + window.innerHeight);
    var elemTop = elem.offsetTop;
    var elemHeight = elem.offsetHeight;
    var animatedElem = elem.querySelector('.animatedElement');
    var animationClass = 'anim'+elem.id;

    if (vwBottom > elemTop && ((vwTop - elemHeight) < elemTop)) {
     animatedElem.classList.add(animationClass);
    } else {
      animatedElem.classList.remove(animationClass);
    }
  });
}, false);

DEMO

Upvotes: 2

Related Questions