SVG Scrolling Animation Triggered By ScrollMagic

SVG Scrolling Animation Triggered By ScrollMagic

We’ve created an SVG and animated it using GreenSock in my previous tutorial,
but what if we wanted the animation to only trigger when the user scrolls past a certain point of the website?

In today’s ScrollMagic tutorial we will trigger our GreenSock animation based on the scroll position.

Here’s the final result.


1. Include ScrollMagic

Firstly we will include ScrollMagic in the html, just after the TweenMax.min.js reference.

<script type="text/javascript" src="js/jquery.scrollmagic.min.js"></script>

I pasted the smaller version of ScrollMagic into my plugins.js.

Download Free Toolkit

All you need to know to get started with GreenSock and ScrollMagic, in one single package. Straight into your inbox.

100% Privacy. Guaranteed! Powered by ConvertKit

2. Trigger Animation In The Middle Of The Viewport

ScrollMagic Trigger Position Center

By default all animations (tweens and timelines) are triggered by ScrollMagic when the top of the triggerElement reaches the middle of the viewport.

// Controller
var controller = new ScrollMagic();

// 2. Curtain Timeline
var tlCurtain = new TimelineMax();
	tlCurtain.set($curtain, {yPercent: -100})
	.to($curtain, 0.3, {yPercent: 0, ease:Power4.easeOut})
	.to([tomatoLeft2, tomatoLeaves2, tomatoRight2, letters2, bracketRight2, bracketLeft2], 0.01, {fill: "#707070"})
	.to($curtain, 0.3, {yPercent: -100, ease:Power4.easeOut})

// 2. Curtain Scene
var scene = new ScrollScene({triggerElement: "#screen2 .imacInner"})

Firstly we create the ScrollMagic controller, then thetlCurtain timeline and then we add this timeline to a ScrollMagic scene.

The triggerElement element for this animation is set to #screen2 .imacInner, which is an absolute positioned div with fixed dimensions defined in the stylesheet.

.imac {
    width: 450px;
    height: 296px;
    background: url('../img/imac.png') no-repeat top left;
    margin: 0 auto 200px auto;
    position: relative;
    .imacInner {
        width: 324px;
        height: 203px;
        overflow: hidden;
        position: relative;
        left: 63px;
        top: 17px;

The parent .imac also has defined dimensions, background-image and position: relative to help us out with the positioning of the .imacInner screen.

The tlCurtain consist of these 3 tweens.

$curtain is a transparent div by default, positioned outside of the iMac screen and then animated in using yPercent: 100, we then update the color of the SVG fill color to #707070 and animate the $curtain again offscreen.

Note: all the necessary variables were defined at the top of the main.js as we’ve discussed last time.

2. Trigger Animation At The Bottom Of The Viewport

ScrollMagic Trigger Position Bottom

Sometimes you want to trigger the animation when the element comes into the view. That’s what you can see with the split animation.

// Timeline
var splitAnimation = new TimelineMax({paused: true});, 0.3, {xPercent: 50, ease:Power4.easeOut}, "start")
	.to(bracketRight3, 0.3, {xPercent: -50, ease:Power4.easeOut}, "start")
	.to(tomato3, 0.3, {scale: 0.5, transformOrigin: "center center", ease:Power4.easeOut}, "start")
	.to([bracketLeft3, tomatoLeft3, tomatoLeaves3], 0.2, {xPercent: -200, autoAlpha:0, ease:Power4.easeOut})
	.to([bracketRight3, tomatoRight3], 0.2, {xPercent: 200, autoAlpha:0, ease:Power4.easeOut}, '-=0.2')
	.to(letters3, 0.4, {scale: 2, y: "-=50", ease:Cubic.easeOut}, '-=0.2');

// Scene
var scene2 = new ScrollScene({triggerElement: "#screen3 .imacInner", triggerHook: 'onEnter', offset: 203})

The splitAnimation timeline is paused by default and triggered to play when the bottom of the #screen3 .imacInner hits the bottom of the viewport.

This is achieved by setting offset: 203, where 203 pixels is the height of the iMac screen.

Custom ScrollMagic Trigger Offset

If we didn’t include the offset, the animation would start when the top of the screen hits the bottom of the browser viewport.

3. Trigger Animation At The Top Of The Viewport

ScrollMagic Trigger Position Top

The scale down and rotation animation is triggered at the top of the viewport.

// Timeline
var fallAnimation = new TimelineMax({paused: true});
	fallAnimation.set(screen4, {background: "none"})
	.to(screen4svg, 0.3, {scale: 0.7, transformOrigin: "bottom center", y: "+=40px"}, "start")
	.to(screen4ihtLogo, 0.3, {rotation: 720, transformOrigin: "bottom center"}, "+=0.5");

// Scene
var scene3 = new ScrollScene({triggerElement: "#screen4 .imacInner", triggerHook: 'onLeave'})

Again this timeline is paused by default and is played when the top of #screen4 .imacInner hits the top of the viewport.

Use triggerHook: 'onLeave’ to trigger your animations at the top.

4. Animation Controlled By Scrolling

ScrollMagic Scene Duration

My final animation example is controlled by the scrollbar.

There are quite a lot of tweens, but hey that’s what makes it look cool (hopefully)!

// Timeline
var scrollAnimation = new TimelineMax();
	scrollAnimation.set(splitDiv, {autoAlpha: 1})
	.to(splitDiv, 0.3, {height: "100px"})
	.set([tomatoRight5, letters5], {autoAlpha: 0})
	.to(tomato5, 0.6, {rotation: "45", transformOrigin: "bottom left"}, "rotate")
	.to(splitDiv, 0.3, {y: "70",rotation: "0", transformOrigin: "top center"}, "rotate")
	.to(bracketRight5, 0.1, {xPercent: 200, autoAlpha:0, ease:Power4.easeOut}, "rotate")
	.to(bracketLeft5, 0.1, {xPercent: -200, autoAlpha:0, ease:Power4.easeOut}, "rotate")
	.set(bracketRight5, {xPercent: -200, yPercent: 150, rotation: "90", transformOrigin: "bottom center", autoAlpha: 1}) //bring bracket to the bottom
	.set(letters5, {y: '-160px', scale: 2, autoAlpha: 0}) //bring text to the top
	.set(splitDiv, {autoAlpha: 0}) //hide mask
	.to(bracketRight5, 0.3, {yPercent: 50, autoAlpha: 1, ease:Power4.easeOut}, "catch")
	.to(tomato5, 0.3, {y: "+=2", rotation: "-=45", scale: 0.5, transformOrigin: "bottom left", ease:Bounce.easeOut}, "catch")
	.to(tomato5, 0.6, {x: "+=40",y: "+=40", ease:Power4.easeIn})
	.to(letters5, 0.3, {y: '-60px', autoAlpha: 1, ease:Power4.easeOut}, "+=0.3"); // bring in text;

// Scene
var scene4 = new ScrollScene({triggerElement: ".more-link", triggerHook: 'onEnter', triggerOffset: 400, duration: 300})

The important part is the duration: 300 in the ScrollScene, which defines the length of the scrolling distance when the animation will play.

A smaller number would mean that the animation would play quicker and higher number would extend the animation playback.


I hope that this article will help you to get started with GreenSock and ScrollMagic, it’s by far the most powerful combination for scrolling animations out there.

Remember, keeping it simple will make your scrolling animations effective.

And if you have any questions regarding any of these plugins, please leave a comment below.

Until next time, happy coding.

Download Free Toolkit

All you need to know to get started with GreenSock and ScrollMagic, in one single package. Straight into your inbox.

100% Privacy. Guaranteed! Powered by ConvertKit

36 thoughts on “SVG Scrolling Animation Triggered By ScrollMagic

  1. Aaron T Grogg

    Hey, Petr, cool effect, would be nice if the article linked to the products you’re using somewhere.


  2. Al Lemieux

    What if the content in your scene is taller than the viewport and you have other scenes after it? Seems like Scroll Magic doesn’t like this.

    1. Petr Tichy Post author

      Hey Al, ScrollMagic can work things out for you, but you would need to adjust the CSS and JS.

      Not every “visual” scene needs to be a ScrollMagic scene, you can mix it up to achieve the effect you’re after.

  3. Al Lemieux


    Do you know if ScrollMagic can do this:

    The scroll action is not continuous. I don’t think ScrollMagic is made for this kind of thing, but what do you think?

    1. Petr Tichy Post author

      You’re right Al, ScrollMagic is not build for this and I am not a big fan of this approach either, however a while ago I wrote this One Page Scroll with animations tutorial.

      Really consider whether you need to disable the native scrolling behavior before implementing it in your own project.

  4. Greg

    ‘triggerOffset’ doesn’t seem to be recognised and looks like it should be ‘offset’.

    ‘offset’ used in the doc examples

  5. Michael J Brett

    Your sample code contains a small error I think.

    You quote “The triggerElement element for this animation is set to #screen2 .imacInner, which is an absolute positioned div with fixed dimensions defined in the stylesheet.”

    However in the CSS you have .imacInner set as position: relative

  6. Nick

    Hi Petr,

    I bought some time ago the Skrollr tutorials from you, and now I am digging deeper into Skrollmagic, it really is magical 🙂
    Had one question I’d hope you could help me with:
    I am trying to achieve blur effect on a div, starting at the top when div reaches viewport and finishing at the end (height of the div), no problem there by setting up the code, only when the animation starts it automatically jumps from 0 blur to 10px(example) of blur.
    Any idea on this matter?
    {‘-webkit-filter’: ‘blur(10px)’}
    (other transitions work, such as opacity and so).

    Thanks a lot for the tutorials.

          1. Nick

            Hey Petr,

            Worked fantastic, as I answered you, thought it would have auto added that to the comments. Anyway so thanks again.
            And here I am again, if you got time or could help me with something small (or maybe it’s not that small);
            I’m using the section wipes with image backgrounds, I have them set to ‘cover’, but some sections have a vertical overflow, which doesn’t cause any issue, but when that specific section becomes fixed it makes the background image jump, and I can’t figure out the css to avoid this jump,
            I did use background size: 100% instead of cover and positioned it vertically against the top, but the problem with this is that if the image is little smaller in height it either repeats itself or when switched off (no-repeat) it leaves a blank space.

            On another note, I really enjoy this ScrollMagic a lot, but it seems that the really amazing thing behind the wow effect is Greensock, are you planning on any gsap specific tutorials.
            Would be really interested in this.

            Sorry for the long read and thanks again.
            Enjoy your weekend!

  7. Norman Dubois

    Hi Petr,

    thanks for this awesome article!
    It was really helpful to do the tutorial and your explanation was very good, as always 🙂
    I found one little mistake in the last animation, where you rotate the tomato.
    You wrote this line of code:

    .to(tomato5, 0.3, {y: "+=2", rotation: "-=45", scale: 0.5, transformOrigin: "bottom left", ease:Bounce.easeOut}, "catch")

    But the rotation should be rotation: ‘+=45’ to animate the tomato like in the demo.

    Also ScrollMagic is not working anymore if I pause the tween. So with the new version of ScrollMagic I think you don’t have to pause the Timeline initially. Instead just initialize it like this:

    var scrollAnimation = new TimelineMax();

    Thanks Petr for this great tutorial!

    Best regards,

  8. Fredrik

    Hi Petr,

    Thanks for a great article! Just a small question, is it possible to just “fire” the animation once, or is it always active?


  9. Shawn

    I’m going to openly admit that much of this is over my head. But what’s life without challenge right?

    I added a little GS tweenMax to a few elements for fun and it went pretty smoothly.

    Then, I wanted to extend my knowledge to see if I could attach a scroll trigger event that would initialize a tween and stumbled across this article.

    I’m trying to add the information in this tutorial to a Bootstrap setup but not getting the results I was hoping for.

    I’m not looking for anyone to do the work for me necessarily, but any clues to my missteps would be greatly appreciated.

    I’ve got a test page going here that I’ll leave up for a few days.

    Many thanks in advance for the feedback and very much appreciate the tutorial.

    1. Petr Tichy Post author

      Hi Shawn, thanks for the comment and for being honest about the challenge. With a little practice things become easier.

      Your test page has a JS error: Uncaught ReferenceError: $curtain is not defined. $curtain is a variable that I’ve setup for my demo, you might need to include in your code or remove that tween from the timeline.

      Can you be more specific about what is over your head? Is that ScrollMagic API or GSAP? Or the mix?

      Happy to answer your questions.

      1. Shawn

        Thanks Petr. I really appreciate the reply. I took a look at the $curtain issue you cited and noticed I did indeed have some missing information. Sometimes a good night’s sleep can help. I was also missing some div ids and some styles. I’m taking the “reverse engineering” approach to all this and therefore I’m literally plagiarizing your code from the demo page; I hope that’s ok.

        In any event, I think I’ve got something going now. The first animation is now working on pageload as expected as well as the $curtain effect on scene 2. If you have a moment, take a look ( I think I’ll try to start tweaking some of this now… Again, many thanks for the tutorial, the reply and the encouragement.

        P.s. I did find some other tutorials on Skollr.js and I’m thinking that approach is more my speed/knowledge. I may give some of that a try here soon.

  10. Jason

    Been playing around with magic for a little and its great, but how can you start at first scroll?

    I have an elements that is within the viewport when page loading, they will fly up or down when scrolled. The location changes based on user settings, so the elements need to start in the default location. The ‘onEnter’ doesn’t work because when page load the elements have already left their starting position… The only thing I can think of is to make an invisible trigger and place it halfway down the page browser height, but seems a little cumbersome to do it this way.

    Is there another way to go about this or a setting im missing?

    1. Petr Tichy Post author

      Hi Jason, have you tried to place invisible trigger to the top of the screen or use the body as the trigger element? Then you could use the triggerHook: 'onLeave' to trigger your animations straight on the first scroll.

      Hope that helps.

      1. Jason

        Thanks, I didnt think of that way. I ended up having something a little similar, having an invisible div at 50% top then offset it by 2 pixels… Seems both ways would do the job.

        Just was wondering if I was missing a command, I was coming from skrollr and it has a data-start command that does this. But after using both I definitely like the power of scroll magic better!

  11. Sebastien

    I love this, I haven’t figured out how to work get it to work with the latest version of the scrollMagic, yet, but that’s a minor detail.

    I do have a question though: I have a page setup that has “blocks” on it, as each block enters, it plays the animation, great. However, the number of blocks that a page may have is a variable that can change and they all use the same “animation”, is there any way to detect if a DOM structure is in view with a particular class instead of targeting the element by an identifier?

    1. Petr Tichy Post author

      Hi Sebastien, thanks for checking it out. Yes, you can create an array of triggers (blocks) based on a class and then create a scene for all of them inside of each loop, but they still need a unique ID, otherwise the first would trigger animations on all the elements with the same class.

      I will be covering this and more advanced ScrollMagic animations in the upcoming ScrollMagic Workshop.

      1. sebastien

        Thanks for the reply. I’m not too great with JS, but I figured out if I just put it in a loop using each function, and target “this” instead, it actually will treat each one as a separate element. I don’t know if this is ideal, but it’s smooth and works across all browsers modern browsers.

        See code below:

        var controller = new ScrollMagic();
        $( ".ani2" ).each(function( index ) {
            console.log( index + ": " + $( this ).text() );
            var moduleanimation = new TimelineMax();
            moduleanimation.staggerFrom( $(this).find('*') , 0.5, { delay: 0.3, opacity:0, ease:Power4.easeOut}, 0.01)
            var scene = new ScrollScene({triggerElement: this, triggerHook: 'onEnter', offset: 150})
          1. ishwar

            hi Sir
            can you give a demo on svg path fill on scrolling,
            like this site

  12. Elvis

    Thanks for the post Petr, I’m using a Pinned container to achieve the horizontal slides effects.
    But seems like using the trigger for animation within each section won’t work anymore, since there’s virtually no vertical movement. Do you have any workaround on this. Thanks a lot

  13. Jas

    Many thanks in advance for the response and very much appreciate the tutorial. I learned allot to use with my site


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.