Jump to content
Search Community

Help Porting to GSAP from SVG Animation

laser_bang test
Moderator Tag

Go to solution Solved by laser_bang,

Recommended Posts

Hi all,

 

I looked for forum rules and couldn't find any, I hope this doesn't violate anything. I am an artist currently developing a project that relies on SVG animations. I am trying to port my animation code to use GSAP since SVG-native is starting to sag under my requirements.

 

Would someone very knowledgeable in GSAP be willing to answer some questions for me? Since the project isn't public yet I'd rather not post details about it publicly.

 

For context, I am an experienced coder, but am totally new to GSAP. I don't think it'll take more than a few minutes, I mostly just need to be pointed in the right direction in a general sense by someone who can help me really my optimize my specific use case (several thousand linked, simultaneous animations), I can write all the code and troubleshoot from there.

 

Again, hope this is okay. Thanks!

 

 

Link to comment
Share on other sites

Hi @laser_bang welcome to the forum! 

 

You don't really need to share your project details, we love minimal demos which  are just some coloured divs with a few lines of GSAP to get the idea across.

 

Sure we be willing to answer your questions, but we have found that without any code it will be hard to pinpoint your question correctly and usually with a minimal demo most users get an answer within the first reply, saves everyone time, so if you could share some code that would be amazing.

 

You can also request for the topic to be deleted after your question has been answered. I never had it seen requested before, but I'm sure that is not a problem.

  • Like 1
Link to comment
Share on other sites

Thank you for the thoughtful reply! I can ask some general questions, which I think will be enough, because my questions are more conceptual rather than "why isn't this prototype working?"

 

1. I have different svg circles, let's say 20, all with unique opacities, and I want to cycle each circle's opacity stepwise in the same order through all 20 opacities in the set. Is there a way for me to maintain an ordered "database" of opacities that each shape can reference, rather than having to hard-code all 20 opacities for each of the 20 objects? Ideally I'd be able to pass every object only a "cycle start" position in the database and a speed to move through the queue, because the opacity information is shared across the set.

 

Would the method for doing this be different if there were only 10 unique opacities for the 20 shapes? I imagine not but just in case!

 

 

2. Will GSAP automatically handle optimization as the number of objects scales up? Say, to 200 or 2000 objects? Or are there particular tips about how to enable GSAP to scale gracefully in these kinds of situations?

Link to comment
Share on other sites

3 hours ago, laser_bang said:

1. I have different svg circles, let's say 20, all with unique opacities, and I want to cycle each circle's opacity stepwise in the same order through all 20 opacities in the set. Is there a way for me to maintain an ordered "database" of opacities that each shape can reference, rather than having to hard-code all 20 opacities for each of the 20 objects? Ideally I'd be able to pass every object only a "cycle start" position in the database and a speed to move through the queue, because the opacity information is shared across the set.

 

Would the method for doing this be different if there were only 10 unique opacities for the 20 shapes? I imagine not but just in case!

I'm sure that's doable. Very difficult to advise without a minimal demo. It sounds like an "ordered database" would basically be an Array. You don't need to hard-code every opacity, no. Use function-based values. Once we see a minimal demo, we'll be in a better position to offer advice. 

 

3 hours ago, laser_bang said:

2. Will GSAP automatically handle optimization as the number of objects scales up? Say, to 200 or 2000 objects? Or are there particular tips about how to enable GSAP to scale gracefully in these kinds of situations?

No, you don't need to do anything to GSAP in particular. It's highly optimized already. In my experience, 99.9% of the times, the performance bottleneck has absolutely nothing to do with GSAP. It's browser rendering. So, for example, GSAP might account for 0.1% of the CPU load when animating 2000 elements, and then 99.9% is graphics rendering, event bubbling, etc. in the browser. So even if you made GSAP 10x faster, you'd never even notice a difference because it'd still account for less than 1% of the overall load in this example. Focus on doing all the other things that improve performance, like using transforms instead of properties that affect layout, maybe enable will-change, etc. Be very careful about animating 2000 SVG elements because SVG by its very nature is CPU-intensive. You might want to look into <canvas>/WebGL if you're animating a ton of things. 

 

Good luck!

  • Like 2
Link to comment
Share on other sites

First, thank you for the excellent support despite my caginess. I'm still in the "wtf am I doing" stage of this and trying to figure out a way forward, and I think what I'm doing is probably an extreme edge case of one very specific application.

 

I've copied a working example below with some relevant comments in it. I'm not sure how to do the codepen thing, but the code should work saved as HTML even without a webserver. I've got four shapes in the sample, but as I've said the catch is this: let's say (in the extreme limit) I want do this on 10,000 shapes several times a second, like a primitive pixel display. This toy solution does not scale, I have tested it.

 

Based on the comments here and some more research today, I think the way forward is probably abandoning SVG in favor of Pixi graphics + some GSAP (if only bc of wrap()), but If anyone has thoughts on the most efficient way to do this, I'd be highly appreciative of any advice. I can for sure figure all this out myself, but I'd rather spend time on the actual work itself than building the framework for it. And I'd like to get a framework that could scale even further if possible!

 

Some additional questions/notes:

1. One main clunkiness is that each shape has a different initial value. Is there a way to pass an initial starting value to `wrap()`? That would make things SO much easier for me, but it looks like there isn't from the docs.

2. The order of the circles and the order of the colors matters, but they aren't related to each other. Each circle is fixed, only the color changes. And they all cycle through the same set of colors in the same order.

 

 

<!DOCTYPE html>

<html>

<head>

<title></title>

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>

</head>

<body>

<svg id="a" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" height="100%" viewBox="0 0 6000 3000">

<svg id="a" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1500" height="2068.4" viewBox="0 0 1500 2068.4">
  <!-- Circles are fixed throughout -->
  <circle class='c1' cx="286" cy="253.9" r="231.9" />
  <circle class='c2' cx="286"  cy="749" r="231.9" />
  <circle class='c3' cx="286" cy="1246.1" r="231.9" />
  <circle class='c4' cx="286" cy="1743.1" r="231.9" />
</svg>

</svg>
<script type="text/javascript">
	
		
	// Define the color array
    // NOTE that the color array is not the same length as the number of circles
    // NOTE that the initial "starting" values of the circles does not go in the order of the array
	var colorArray = ["purple","green","red","blue","orange"]
    
    // Arrays of circle classes and starting array indices to pass to for loop
	var objClasses = ['.c1', '.c2', '.c3', '.c4']
	var startValues = [2,1,3,4]

    // Use gsap.set to set initial values
    // NOTE: As best I can tell, `gsap.getProperty` does not work on custom CSS variables if the CSS variable is set natively in the svg/html file already, it has to be set via gsap.set
    // If I'm wrong and there's a better way to do this please let me know
    
	for (var i = 0; i < objClasses.length; i++)
    gsap.set(objClasses[i], { '--curColor':startValues[i] });


// gsap.to call for each of the four circles. This current solution does not scale elegantly.

	gsap.to('.c1', {
    fill: function() {
     let curColor = gsap.getProperty('.c1', '--curColor');
      gsap.set('.c1', { '--curColor':curColor+1 });
      return gsap.utils.wrap(colorArray, curColor)
    },
    duration:2,
    ease: 'none',
    repeat: -1,
    repeatRefresh:true
  });

	gsap.to('.c2', {
    fill: function() {
     let curColor = gsap.getProperty('.c2', '--curColor');
      gsap.set('.c2', { '--curColor':curColor+1 });
      return gsap.utils.wrap(colorArray, curColor)
    },
    duration:2,
    ease: 'none',
    repeat: -1,
    repeatRefresh:true
  });

		gsap.to('.c3', {
    fill: function() {
     let curColor = gsap.getProperty('.c3', '--curColor');
      gsap.set('.c3', { '--curColor':curColor+1 });
      return gsap.utils.wrap(colorArray, curColor)
    },
    duration:2,
    ease: 'none',
    repeat: -1,
    repeatRefresh:true
  });

	gsap.to('.c4', {
    fill: function() {
     let curColor = gsap.getProperty('.c4', '--curColor');
      gsap.set('.c4', { '--curColor':curColor+1 });
      return gsap.utils.wrap(colorArray, curColor)
    },
    duration:2,
    ease: 'none',
    repeat: -1,
    repeatRefresh:true
  });


</script>
</body>
</html>

 

 

 

 

 

 

 

Link to comment
Share on other sites

I think this is an incredibly basic question, but I can't find an answer after like an hour or so of searching and it's likely just my javascript naivete showing: if I am creating shapes procedurally using `canvas`, how do I select those shapes for gsap. The code for procedural shape generation is (in this case rectangles):

 

 const rows = 10
 
  const cols = 500

  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {

      ctx.beginPath();
      ctx.rect(j * 12, i * 300, 12, 300);
      ctx.fill();

}
}

Link to comment
Share on other sites

Hi,

 

As far as I know you can't draw a circle in canvas and then access that particular circle and animate it's color. If you aim to do that is better to use a canvas rendering engine like PIXI:

https://pixijs.com/

 

If you don't want to use a third party library, you have to manually draw and clear the canvas context on every tick or GSAP update in order to update the graphics properties for that particular frame, a little bit like this example:

See the Pen MWJQovm by rhernando (@rhernando) on CodePen

 

For color specific you can use this:

See the Pen vYVJOwg by GreenSock (@GreenSock) on CodePen

 

Hopefully this helps.

Happy Tweening!

  • Like 2
Link to comment
Share on other sites

Okay, one last thing!

 

First, I want to thank y'all so much. With this guidance, I've managed to recreate my largest ~150MB animated SVGs in very simple 100KB html/JS files. The size savings appear to be exponential as the animations scale, too. This is all incredible and beyond what I hoped. Thank you!

 

BUT, the code that I am currently using is very "jittery." I know it's GSAP, because the code jitters with both SVG and Pixi implementation, and Pixi can't be what's slowing it down if I'm rendering things like I think I am!

 

The jittery behavior can be observed at full-scale, here: http://nickbloom.net/gallery/green.html

 

The intended behavior can be observed in Webkit browsers only via the (24MB!) animated SVG, here: http://nickbloom.net/gallery/green.svg

 

A potentially important note: Each cell has a different "framerate" so they change at different speeds. For animated SVG, you pass the total length of each animation for each cell, and SVG does the framerate math. So for GSAP, I just divided each cell's animation length by the total number of "frames" (in this case, 593) to give each cell its fractional speed. This, to me, should recreate the SVG animation exactly, but perhaps I'm misunderstanding `duration`.

 

Page sourcing the html file linked above will get you all the code (everything is in that file!), but the relevant javascript absent only the lengthy arrays of colors, speeds, and start positions, is:

 

//INPUTS
var colors = [...];
var speeds = [...];
var startValues = [...];


const rows = 12;

const cols = 400;



//CREATE PIXI
let app = new PIXI.Application({width: 1500, height: 750});
document.body.appendChild(app.view);

//MATH FOR GRID CREATION
let cell_width = app.view.width/cols;
let cell_height = app.view.height/rows;

//CREATE GRID (BITMAPPED, LEVERAGING TINT FOR SPEEEED)
for (let i = 0; i < rows; i++) {
  for (let j = 0; j < cols; j++) {
  var s = new PIXI.Sprite(PIXI.Texture.WHITE);
  s.width = cell_width;
  s.height = cell_height;
  s.tint = 0xCCCCCC;
  s.position.x = j * cell_width;
  s.position.y = i * cell_height;
  app.stage.addChild(s);
}
}

// SET INITIAL VALUES (THIS SHOULD BE STRAIGHT FROM CASSIE'S CODE ABOVE)
  gsap.set(app.stage.children, {pixi:{
    tint: (i) => {
      console.log(i, '-' ,startValues[i])
      return gsap.utils.wrap(colors, startValues[i]);
    }}
  });

// LOOP VALUES (THIS SHOULD BE STRAIGHT FROM CASSIE'S CODE ABOVE, BUT I TOOK OUT THE DELAY AND PASSED A SPEED FOR EACH)
  gsap.to(app.stage.children, {pixi:{
    tint: (i) => {
      console.log(startValues[i])
      return gsap.utils.wrap(colors, startValues[i]++)
    }},
    duration: (i) => {
      return speeds[i]
    },
    repeat: -1,
    repeatRefresh:true
  });

 

Link to comment
Share on other sites

Wow, you're trying to animate the color of 4,800 individual objects an average of 6 times per second which means you're asking it to parse 3-channel color (RGB) 172,800 times PER SECOND, and that's just setting up the tweens! Then there's the interpolation and graphics rendering that Pixi must do. You're forcing it to dump all the parsed start/end values roughly every 0.167 seconds and re-calculate them all. That's a lot!

 

This can definitely be optimized further but it's beyond the scope of free help we can provide in these forums. The key here in my opinion is to pre-calculate the color channels and manually apply them, probably in an onUpdate or a custom plugin. Honestly I'm not even sure that using Pixi here is helping. It may actually be faster to use regular <div> elements. If you'd like to explore paid consulting options, feel free to reach out and we can look into scheduling that and getting you pricing. 

 

Actually, as a courtesy (and because I was curious), I spent a few hours optimizing this for you:

See the Pen VwEzQrY?editors=0010 by GreenSock (@GreenSock) on CodePen

 

I hope that helps. 

  • Like 3
Link to comment
Share on other sites

  • Solution

I can't tell you how much I appreciate all of this! I meant to mention in my initial post that I was thinking about becoming a paying customer. The goal is to release everything of mine as non-commercial Creative Commons, but – as a person who provides expert consultations myself – I want to make sure people are fairly compensated.

 

I have much bigger plans for these, so I think it's worth at least discussing a paid membership more. What's the best way to set up that consult?

  • Like 1
Link to comment
Share on other sites

I'm so glad that was helpful. And we sure appreciate folks like you who recognize the value of the time invested here in these forums solutions. 

 

Yep, the only way we can do what we do is because of the support of Club GreenSock members. Please don't feel any obligation to sign up, but you can do so anytime at https://greensock.com/club 

 

Good luck with the project! 👍

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...