PixelsPencil PixelsPencil - 2 months ago 31
Javascript Question

Animate SVG paths with opacity on user scroll sequentially

I've built an SVG that I want to animate, it has three parts/layers, each has a predefined amount of paths.

The SVG is a circle logo type thing, has words at the top part of the circle and dashes around the remaining space and then type in the middle of the circle.

Like this but simpler:

What I want to do is this:

  1. Set opacity of svg to 0 before it comes into viewport

  2. Detect when SVG starts to come into viewport

  3. Change opacity of each path in first two
    blocks sequentially based on scroll position (scrolling down)

  4. When scrolling up the sequence reverses

I've been attempting to do this for two days on and off with nothing but failure. I succeeded working with simple jquery animation and css animation (see below code) but could not get these playing nice with scroll position.

Another idea I had but then realised it would be very cumbersome and not work in reverse was to have multiple if / else based on scroll position but I don't want this to have an explicit number due to different resolutions this will have to work on.


<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve">
<g id="mainsvg">
<g id="top-circle-words">

<g id="bot-circle-dashes">

<g id="middle-words">

CSS - animation attempt - works but no control - just plays on load:

svg {width:100%;max-width:50%;margin:50% auto;display:block;}

@-webkit-keyframes animIn { 0% { opacity: 0; } 100% { opacity:1; } }
@-webkit-keyframes animOut { 0% { opacity: 1; } 100% { opacity:0; } }

.animIn { -webkit-animation: animIn 500ms 1.5s normal backwards; -webkit-animation-fill-mode: both; }
.animOut { -webkit-animation: animOut 500ms 3s reverse backwards; -webkit-animation-fill-mode: both; }

g#top-circle-words path:nth-child(1) { -webkit-animation-delay: 50ms }
g#top-circle-words path:nth-child(2) { -webkit-animation-delay: 100ms }
g#top-circle-words path:nth-child(3) { -webkit-animation-delay: 150ms }
g#top-circle-words path:nth-child(4) { -webkit-animation-delay: 200ms }
g#top-circle-words path:nth-child(5) { -webkit-animation-delay: 250ms }
g#top-circle-words path:nth-child(6) { -webkit-animation-delay: 300ms }
g#top-circle-words path:nth-child(7) { -webkit-animation-delay: 350ms }
g#top-circle-words path:nth-child(8) { -webkit-animation-delay: 400ms }
g#top-circle-words path:nth-child(9) { -webkit-animation-delay: 450ms }
g#top-circle-words path:nth-child(10) { -webkit-animation-delay: 500ms }

g#bot-circle-dashes path:nth-child(1) {-webkit-animation-delay: 550ms }
g#bot-circle-dashes path:nth-child(2) {-webkit-animation-delay: 600ms }
g#bot-circle-dashes path:nth-child(3) {-webkit-animation-delay: 650ms }
g#bot-circle-dashes path:nth-child(4) {-webkit-animation-delay: 700ms }
g#bot-circle-dashes path:nth-child(5) {-webkit-animation-delay: 750ms }
g#bot-circle-dashes path:nth-child(6) {-webkit-animation-delay: 800ms }
g#bot-circle-dashes path:nth-child(7) {-webkit-animation-delay: 850ms }
g#bot-circle-dashes path:nth-child(8) {-webkit-animation-delay: 900ms }
g#bot-circle-dashes path:nth-child(9) {-webkit-animation-delay: 1s }
g#bot-circle-dashes path:nth-child(10) {-webkit-animation-delay: 1.1s }
g#bot-circle-dashes path:nth-child(11) {-webkit-animation-delay: 1.2s }

g#middle-words {-webkit-animation-delay: 300ms; -webkit-animation-duration: 2s}

JS - useing .each();

$("g#top-circle-words path, g#bot-circle-dashes path").each(function(index) {
$(this).delay(20*index).animate({opacity: 1}, 50);

$("g#middle-words").delay(50).animate({opacity: 1}, 500);

So if you take the above code, it should work, it's the control of it that I cannot get right? You can see with the CSS, that also works if you add the .animIn class manually to all the elements you want to animate in without using JQ.

I did find another post on here where the OP was having a similar issue (kinda) as me and I tried to adapt the code that was marked correct but could not get that working either?

$(function() {
var prevRange = -1;
var range = -1;

$(document).on('scroll', function() {
var top = $(document).scrollTop();
if (top >= 2200 && top < 2401) {
range = Math.floor(top/10)-220;
} else {
range = -1;

if(range != prevRange) {
prevRange = range;
var leftPx = (826 - range*5) + "px";
$('path').stop().animate({left: leftPx}, 300, "easeOutQuad" );

Also this is something I wrote but I could see this being a pain to work with if I used multiple IF statements:

// $(document).ready(function() {
// $(window).scroll(function() {
// if ($(this).scrollTop() > 100){
// $('g#top-circle-words path:nth-child(1)').css( { 'opacity': '.5' } );
// }
// else {
// $('path').css({
// 'opacity': '.9',
// "border": "0"
// });
// }
// console.log($(document).scrollTop());
// });
// });

Can anyone please help me with this - been doing my brain in - apologies as I'm still learning JS and JQ so if someone can advise me the right direction to take or provide some insight with explainantion I would really appreciate it.


I'd recommend using ScrollMagic and potentially GSAP if you need.

This should be the right setup, but may need to be tweaked depending on your exact SVG:

var tween = TweenMax.fromTo("path", 1, { opacity: 0 }, { opacity: 1 });
var scene = new ScrollMagic.Scene({triggerElement: "#mySvg" })
                .addIndicators({name: "tween css class"}) // add indicators (requires plugin)