Jump to content
Search Community

SVG Masks in a Loop

WilliamBay test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

https://staging.thebirdthebear.com/

I'm working on this particular animation, and having some issues. 

It's a four-branch frilly thing that is supposed to reveal each branch. Triggered with Scrollmagic.

You can see it working properly on the first iteration in the section "OUR CRAFT."

 

Each branch is being revealed by removing the respective mask.

 

My issue however, is that the animation is not animating on the second iteration, "BRYAN & CARYN." 

 

The masks in the SVG are called out as IDs, but the paths inside the masks are Classes (they're what are being called here in the code below).
I'm not sure if that's the issue or what... 

Thoughts? 

 

 

const branchDraw = function() {
  const layerOneMask = document.querySelectorAll(".layeronemask")
  const layerTwoMask = document.querySelectorAll(".layertwomask")
  const layerThreeMask = document.querySelectorAll(".layerthreemask")
  const layerFourMask = document.querySelectorAll(".layerfourmask") 

  const tl = new TimelineMax()

  for ( var i = 0; i < layerOneMask.length; i++ ){ 

  tl
    .from(layerOneMask[i], 0.5, { drawSVG: 1, ease: Power2.easeOut })
    .from(layerTwoMask[i], 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerThreeMask[i], 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerFourMask[i], 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25");

    const scene = new ScrollMagic.Scene({ triggerElement: layerOneMask[i], offset: -300 })
      .addTo(controller)
      .setTween(tl)
      
  }
}
branchDraw()

 

Link to comment
Share on other sites

I don't know ScrollMagic, and it's not a GSAP product, but maybe you need to create a new timeline.

 

// const tl = new TimelineMax()

for ( var i = 0; i < layerOneMask.length; i++ ){ 
  
  const tl = new TimelineMax()

  tl
    .from(layerOneMask[i], 0.5, { drawSVG: 1, ease: Power2.easeOut })
    .from(layerTwoMask[i], 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerThreeMask[i], 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerFourMask[i], 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25");

    const scene = new ScrollMagic.Scene({ triggerElement: layerOneMask[i], offset: -300 })
      .addTo(controller)
      .setTween(tl)      
}

 

 

  • Like 3
Link to comment
Share on other sites

3 minutes ago, PointC said:

Yep - Blake is exactly right. You have to create a timeline for each of the mask groups that are hitting separate ScrollMagic triggers . Declaring the variable inside the loop should take care of it for you.

 

Yay! I answered my first ScrollMagic question without resorting to, "You don't need ScrollMagic for that".

  • Like 1
  • Haha 3
Link to comment
Share on other sites

We're getting somewhere now! It makes sense about the mask (also stopped me from going down a rabbit hole when console.log( mask ) didn't return anything in the console! :D

 

So in this case, I'm grabbing the SVG itself, and using that as my iterator. Each animate, but the second one pulls two of the branches backwards...
Very strange. 
https://staging.thebirdthebear.com/ 

 

23120324_10155937385209885_2483515398630

const branchDraw = function() {
  const branchGroup = document.querySelectorAll(".branch-group")
  const layerOneMask = document.querySelectorAll(".layeronemask")
  const layerTwoMask = document.querySelectorAll(".layertwomask")
  const layerThreeMask = document.querySelectorAll(".layerthreemask")
  const layerFourMask = document.querySelectorAll(".layerfourmask") 

  for ( var i = 0; i < branchGroup.length; i++ ){ 

  const tl = new TimelineMax() 

  tl
    .from(layerOneMask, 0.5, { drawSVG: 1, ease: Power2.easeOut })
    .from(layerTwoMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerThreeMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerFourMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25");

    const scene = new ScrollMagic.Scene({ triggerElement: branchGroup[i], offset: -300 })
      .addTo(controller)
      .setTween(tl)    
  }

}
branchDraw() 

 

Link to comment
Share on other sites

Try this...

 

const branchDraw = function() {
  const branchGroup = document.querySelectorAll(".branch-group")
  //const layerOneMask = document.querySelectorAll(".layeronemask")
  //const layerTwoMask = document.querySelectorAll(".layertwomask")
  //const layerThreeMask = document.querySelectorAll(".layerthreemask")
  //const layerFourMask = document.querySelectorAll(".layerfourmask") 

  for ( var i = 0; i < branchGroup.length; i++ ){ 

  const group = branchGroup[i];  
  const layerOneMask = group.querySelector(".layeronemask")
  const layerTwoMask = group.querySelector(".layertwomask")
  const layerThreeMask = group.querySelector(".layerthreemask")
  const layerFourMask = group.querySelector(".layerfourmask") 
    
  const tl = new TimelineMax() 

  tl
    .from(layerOneMask, 0.5, { drawSVG: 1, ease: Power2.easeOut })
    .from(layerTwoMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerThreeMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerFourMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25");

    const scene = new ScrollMagic.Scene({ triggerElement: group, offset: -300 })
      .addTo(controller)
      .setTween(tl)    
  }

}

 

Link to comment
Share on other sites

Can you make a simplified demo of that on CodePen?

 

I don't know if I missed something in the code I posted, but instead of messing with for-loops, I like to call a function to make it easier to understand. So what I wrote could be written like this.

 

const branchGroups = Array.from(document.querySelectorAll(".branch-group"));

// Or old school
const branchGroups = Array.prototype.slice.call(document.querySelectorAll(".branch-group"));

branchGroups.forEach(branchDraw);

function branchDraw(group) {
   
  const layerOneMask = group.querySelector(".layeronemask")
  const layerTwoMask = group.querySelector(".layertwomask")
  const layerThreeMask = group.querySelector(".layerthreemask")
  const layerFourMask = group.querySelector(".layerfourmask") 
    
  const tl = new TimelineMax() 

  tl
    .from(layerOneMask, 0.5, { drawSVG: 1, ease: Power2.easeOut })
    .from(layerTwoMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerThreeMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25")
    .from(layerFourMask, 0.75, { drawSVG: 1, ease: Power2.easeOut }, "-=.25");

    const scene = new ScrollMagic.Scene({ triggerElement: group, offset: -300 })
      .addTo(controller)
      .setTween(tl)
}

 

 

Link to comment
Share on other sites

If you watch the animation in the console, you'll see that the second group of masks is drawing correctly and at the right trigger. However, they are not affecting the appearance of the second set of branches. I think the problem is the second SVG is picking up the masks from the first SVG since they have the same IDs. I pulled the branches out of your site and put them into a pen.  (Squeeze to a narrow window to watch both at once)

 

See the Pen 242b45d5ce84cc3b4280457482d203f8 by PointC (@PointC) on CodePen

Notice the 2nd copy of the SVG has the <defs> commented out and yet the masks are still working correctly. I think you'll have to switch to unique mask IDs.

 

  • Like 4
Link to comment
Share on other sites

6 minutes ago, PointC said:

I think the problem is the second SVG is picking up the masks from the first SVG since they have the same IDs.

 

6 minutes ago, PointC said:

I think you'll have to switch to unique mask IDs.

 

I was afraid of this.
Now am I going to be able to do this dynamically on the same SVG, or will I have to create unique SVGs for each animation?

Link to comment
Share on other sites

18 minutes ago, WilliamBay said:

Now am I going to be able to do this dynamically on the same SVG, or will I have to create unique SVGs for each animation?

 

You should be fine. You just can't have the same mask ID. The same class of path that draws the mask should be no problem.

 

See the Pen 4e6d5a3e434210743dacda359a1338e3 by PointC (@PointC) on CodePen

Happy tweening.

:)

 

  • Like 1
Link to comment
Share on other sites

OK! I finally got it! Thanks so much for your help on that @OSUblake and everyone else.

Do you mind if I ask a fundamental JS question?

I understand what's going on for the most part. 

 

But this line:

branchGroups.forEach(branchDraw)


You call the function on the next line, but I thought you had to have a defined function when you're passing it in like that? Do you mind explaining a bit how that works? Or maybe point me to a Youtube explainer video on that concept? Pretty pictures and videos help! :D 

Link to comment
Share on other sites

That's a good question, and a concept a lot of people might not be familiar with, hoisting.

https://www.w3schools.com/js/js_hoisting.asp

 

The JavaScript compiler will move all variable declarations (var) and named functions to the top of their scope.

 

This crazy looking code is totally valid.

num = 8;
num += 4;
var num;
console.log(num); // => 12

 

 

That's because the compiler is reading the code like this.

var num;
num = 8;
num += 4;
console.log(num); // => 12

 

 

The same goes for named functions. The branchDraw function comes after I call it.

branchGroups.forEach(branchDraw);

function branchDraw() {
  ...
}

 

 

But the compiler reads it like this.

function branchDraw() {
  ...
}

branchGroups.forEach(branchDraw);

 

 

But that will only work for named functions. Assigning an anonymous function to a variable will result in an error.

branchGroups.forEach(branchDraw); // TypeError: branchDraw is not a function

var branchDraw = function() {
  ...
}

 

 

And that's because the compiler is reading it like this. It only moves the declaration to the top. The initialization of the variable, assigning the function to it, stays where it is.

 

var branchDraw;

branchGroups.forEach(branchDraw); // TypeError: branchDraw is not a function

branchDraw = function() {
  ...
}

 

 

Knowing that, a good way to organize your code is by putting the "what" at the top, i.e. your variables, and the "how" at the bottom i.e. your functions.

// What
var a = 1;
var b = 2;
var c = add(a, b);
var d = subtract(c, a);
var e = multiply(d, b);

// How
function add() {
 ...
}
 
function subtract() {
 ...
}
 
function multiply() {
 ...
}

 

 

 

And here's a little video about hoisting.

 

 

  • Like 3
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...