Jump to content


How can I improve this GSAP particle animation code?

Recommended Posts

Hello happy coders!


I found this particle code on another post and updated it make it work for my purpose... see codepen. Every time you mouse-over the black area, 300 particles (divs) are added and animated. While it looks pretty smooth in Safari & Firefox - it's not smooth at all in Chrome. And I know the processor load is pretty high in all browsers.


How can I improve the code to make it run more smoothly with less processor load? Maybe using canvas might be a better way to go here - but I have no experience with canvas at all. Maybe there is something else to improve it?


See the Pen RVMEqY by treemok (@treemok) on CodePen

Link to comment
Share on other sites

thanks for the demo. Once you start pushing a few hundred divs around the browser is probably going to have some trouble. Seems dangerous to let a user create 300 new particles on mouseover (or maybe that was just for the demo).

I thought I noticed the effect slightly faster without the rotation and scale applied, but that advantage appeared to go away once 900 or so were animating.



For something like this, you can't beat canvas and Blake's snow demo is the perfect example.




That's 12,000 particles but I'm sure it can handle more.




See the Pen oLGBXy by osublake (@osublake) on CodePen


  • Like 5
Link to comment
Share on other sites

Hi @Chris,


Like @Carl said, canvas is definitely the way to go for particle animations. If you've never used canvas before, a library like Pixi.js might be the best way to get started. It uses an API similar to Flash, and is focused on moving things around the screen as quickly and efficiently as possible.


I converted your demo over to Pixi. I created 15 different particle waves with 1000 particles in each one. When you mouse over the canvas, it will play a different particle wave, that is of course if all 15 aren't playing at the same time. It checks to make sure the timeline for a particle wave is not active before calling restart on it.


The particles are being rendered at the speed of light.


See the Pen 34d6cdf817aa6658f71b22e91fc04316 by osublake (@osublake) on CodePen



  • Like 6
Link to comment
Share on other sites

  • 3 weeks later...
On 5/14/2017 at 4:52 AM, OSUblake said:

a library like Pixi.js might be the best way to get started


Thanks @OSUblake for you tips and that great demo! I'll definitely look into Pixi.js - but I wonder what the file size is since I'm trying to use this for banner units with a max file size of 200Kb.


I'll probably have to code it by hand and rely on Pixi.js for bigger websites. Do you have any experience on using Pixi.js for banners ads or shine some light on file sizes?

Link to comment
Share on other sites

Hi @Chris


File size with pixi is an issue. You can do a custom build, but I doubt it's going to reduce the file size that much.


Your best is to just request for your ad sever to host it on their CDN. Pixi can be used as a replacement for EaselJS/CreateJS with Animate CC.



Link to comment
Share on other sites

If you need help doing a pure canvas version, like the particle emitter @Carl posted, I can show you how to do that.

  • Like 2
Link to comment
Share on other sites

Thanks @OSUblake,


Sorry, I'm a little slow - somehow the email notification are not working for me when I get a response to a post.


I'm handcoding all of my banners and don't rely on Animate CC or Google Web Designer. Some advertisers still don't understand the concept that a CDN hosted library shouldn't count against the total K size. This is not an issue with GSAP, since it's pretty small, but I think in the case of pixi I really want to create my own small script using canvas.


I've spend some time last weekend learning the basics of canvas and really like it. The script I'm using in my original post (using divs) is pretty versatile and I can create explosions, snow, dust etc. I can even use an image for the particles and also include gravity .


I tried to convert my old script using canvas but I'm struggling, since I'm still new to this. If you could help me doing a pure canvas version without pixi - that would be great!




Link to comment
Share on other sites

Hi @Chris


You may need to update your notifications settings to get emails...



One of these days I need to make some type of blog post about how to use canvas with GSAP. Outside of 3d transforms, the 2d canvas can do everything HTML and SVG can do... and more. :-D


Don't know if you saw this, but here's a good post about how to get started with canvas and GSAP.


Kirupa and MDN also have some good tutorials on using canvas.


I can make a version of your particles later, but here's a nice little demo showing how to render a bunch of objects with GSAP. It also demonstrates how to scale the canvas for HiDPI screens and how to improve performance by rendering to offscreen canvas (like cacheAsBitmap in Flash).


See the Pen c27882708c19b2576ce9a7cd53fb3b40 by osublake (@osublake) on CodePen



I'll post more stuff here later on, with examples that demonstrate some canvas rendering techniques and gotchas.



  • Like 5
Link to comment
Share on other sites

Canvas Context


I like to think of working with the canvas as telling a robot how to draw something, and this is done primarily through the canvas context. The context is also where you set all the styling like fill style, stroke style, line width, alpha, shadow, font, transforms, filters, blend modes, etc. Think of it kind of like the current CSS style.


Here's a list of all the properties and methods available on the context.



An important technique, particularly when doing transforms, is to save and restore the state of the context. Check out the comments in this demo. The same fillRect method is being called with 3 different context states. Note that saves can be nested, and every save call should have a matching restore call.




  • Like 4
Link to comment
Share on other sites



Here's a post that explains a technique for doing transforms in canvas the same way GSAP does them in the DOM and SVG.


And here's an interactive demo comparing that technique to GSAP. The red box is SVG and is being set by GSAP. The yellow box is canvas using the STARS technique, and the black dot is the transform origin. The canvas box should be perfectly aligned with the SVG box.




  • Like 3
Link to comment
Share on other sites

Canvas Appears Blurry


On HiDPI screens, like Retina, 4K/5K, and a lot of phones, the canvas may appear blurry. The TL;DR version of it is that you need to scale the canvas to match to the pixel density.


This should appear sharp on a HiDPI screen.


See the Pen b5596e04392578c09763f53f5e4ba8b2 by osublake (@osublake) on CodePen


This may appear blurry on a HiDPI screen.




  • Like 4
Link to comment
Share on other sites

Blurry Lines


Drawing horizontal and vertical lines may appear blurry. This is by design, and makes more sense if you understand how stroke-width works in SVG. The stroke is centered on a path, so half of it will be on one side of the path, and the other half will be on the other side of the path. So drawing a 1px line at a whole pixel value will cause it to be drawn inside 2 pixels because it is straddling the pixel.


The fix is to translate the context back half a pixel before stroking.

context.translate(-0.5, -0.5);


Notice the difference between these two rectangles. The bottom rectangle is stroked using the 1/2 pixel translation trick.



This is very useful, for example, when drawing grids.


See the Pen VpBoQg by osublake (@osublake) on CodePen



  • Like 3
Link to comment
Share on other sites

Offscreen Canvas


Canvas is fast, but it's not a silver bullet. You still need to be mindful of how things get rendered. If you're constantly drawing something that does not change much, like its shape or color, you may be able to improve performance by rendering that object to an offscreen canvas (cacheAsBitmap).


Here's a good EaselJS demo showing the performance difference caching stuff to an offscreen canvas can make. Checking the cache enabled checkbox will provide a massive performance boost.



And here's a simplified demo without any animations showing how to use an offscreen canvas. This is same technique I'm using in that canvas orbs demo to draw the gradient.



  • Like 4
Link to comment
Share on other sites

Particle Animation


To create a particle animation, we need to create some display objects for GSAP to work with. Here's what a very basic sprite object might look like.

var sprite = {
  texture: texture, // Image or canvas to render
  width:  texture.naturalWidth  || texture.width,
  height: texture.naturalHeight || texture.height,  
  originX: 0.5, // Transform origin, so 0.5 would be 50%
  originY: 0.5,  
  alpha: 1,
  rotation: 0,
  scale: 1,
  x: 0,
  y: 0,


Now you can animate that object just like you would if it were an HTML or SVG element.

TweenMax.to(sprite, 2, {
  x: 100,
  y: 200,
  rotation: 180,
  scale: 0.5,
  alpha: 0.2,
  ease: Sine.easeInOut


And on every animation frame, you would render the sprite like this.

TweenLite.ticker.addEventListener("tick", render);

function render() {
  context.clearRect(0, 0, canvas.width, canvas.height);  
  var offsetX = sprite.originX * sprite.width;
  var offsetY = sprite.originY * sprite.height;

  context.translate(sprite.x + offsetX, sprite.y + offsetY);
  context.rotate(sprite.rotation * Math.PI / 180);
  context.scale(sprite.scale, sprite.scale);
  context.globalAlpha = sprite.alpha;
  context.drawImage(sprite.texture, -offsetX, -offsetY);



  • Like 3
Link to comment
Share on other sites

Great stuff, Blake!

I think you just created a nice college course in this thread. 

Hope plenty of folks find it. 

Thanks so much for sharing.


  • Like 4
Link to comment
Share on other sites

This is all really helpful - thanks Blake. I'll need some time to work through it and will report back :) 

Link to comment
Share on other sites

  • 2 weeks later...

More stuff to add to my unofficial guide to canvas...




You can use CSS and SVG filters with canvas (currently Chrome and Firefox).


context.filter = "url(#svgFilter) blur(10px) sepia(60%)";


The beauty of canvas is that it's like a rubber stamp, so you can draw a canvas on itself. Here I'm burring the canvas, drawing the canvas on itself, and then drawing another copy of the canvas on itself with a drop shadow. To do that with SVG or HTML would require creating copies of each element several times over.


See the Pen f36f09e7fcedb6ed5f32a93d5c4ceb00 by osublake (@osublake) on CodePen


  • Like 4
Link to comment
Share on other sites

Matrix Transforms


Here's how to do more advanced transforms, like skew and some GSAP related stuff like xPercent/yPercent using a matrix. Just add some skew and xPercent/yPercent properties to a sprite object.

var sprite = {
  // ...
  skewX: 0,
  skewY: 0,
  xPercent: 0,
  yPercent: 0


And in the render function, you would calculate the matrix for each sprite like this.

var originX = sprite.originX * sprite.width;
var originY = sprite.originY * sprite.height;  
var xPercent = originX + sprite.xPercent * sprite.width;
var yPercent = originY + sprite.yPercent * sprite.height;
var a =  Math.cos(rotation + skewY) * sprite.scaleX;
var b =  Math.sin(rotation + skewY) * sprite.scaleX;
var c = -Math.sin(rotation - skewX) * sprite.scaleY;
var d =  Math.cos(rotation - skewX) * sprite.scaleY;
var tx = (sprite.x - ((originX * a) + (originY * c))) + xPercent;
var ty = (sprite.y - ((originX * b) + (originY * d))) + yPercent;
context.setTransform(a, b, c, d, tx, ty)
context.drawImage(0, 0, sprite.width, sprite.height);


It's a little more code, but its faster, and can do more advanced transforms. And using the setTransform method clears any previous transforms, reducing or eliminating the need to call save and restore.


Canvas drawn over SVG comparison.



  • Like 4
Link to comment
Share on other sites

Really good resources to learn canvas, @OSUblake!


I read everything you've posted and really enjoy playing with the code. The CPU usage is much lower compared to using divs - just what I was hoping for.


I've used your fountain code and updated it for my needs. I tried to set it up in a way where it is easy to update the variables and create multiple canvas animations with the same code.


It works great everywhere but on mobile (iPhone) I can see that the stars are not rotating around it's center. And the "stripes" are not finishing the linear gradient to transparent.


If you have any comments on my code, or if there is anything I can optimize, please let me know. Otherwise thanks again for getting me this far with canvas!


See the Pen BZrZrE by treemok (@treemok) on CodePen



  • Like 2
Link to comment
Share on other sites

Hey @Chris


You did really good job with this. Once you get the hang of canvas, I think you'll find it much easier and faster to prototype and build animations. Most of the verbosity of working with canvas can be abstracted away in helper functions or classes.


The linear gradient is being cropped on the high resolution screens (mobile). For now, just change this.

// canvas.width = widthSpr * resolution;
// canvas.height = heightSpr * resolution;

canvas.width = widthSpr;
canvas.height = heightSpr;


I need to double check to make sure that's correct. I'll get back to you on that.


If you're not seeing the stars being rotated, it's most likely because the image hasn't finished loading, so the width and height are 0. That's why I added this listener to some of my demos. It will fire when all the images are loaded.

window.addEventListener("load", init);


I've written several little libraries that could help you out with a lot of this stuff. Are you just looking to do particle animations, or other stuff?


  • Like 3
Link to comment
Share on other sites

yes,  this helped:


On 7/1/2017 at 8:36 PM, OSUblake said:

canvas.width = widthSpr;


The other issue was that stars were rotating off center. They were swerving. Removing the resolution fixed this as well:


var width  = (texture.naturalWidth  || texture.width  || 0) /*/ resolution*/;
var height = (texture.naturalHeight || texture.height || 0) /*/ resolution*/;

var width  = (texture.naturalWidth  || texture.width  || 0);
var height = (texture.naturalHeight || texture.height || 0);


So far, I mostly need canvas for particle animation in banners. And that means it needs to be small in file size and compatible in all current browsers starting with IE11. I've checked out your latest filter example above, but unfortunately browser support is not there yet for cool effects like blur.


I also have to keep an eye on my CPU usage, which needs to stay under 40% (on my system) to make sure it works smoothly even on slower computers than mine. My latest  codepen example above is at about 30% --  which is great considering there are about 300 particles animated at the same time. This wasn't possible with my original "div" version.


What I would like to work with next are some canvas animation effects to "explode" an image. Or construct an image from pixels that animate in. This would be also a cool effect to animate in a movie title for example. Flash had an effect like this build in, but I could never it it because the file size became too large.


I'm open to to learn anything that works cross browser, simplifies the code, and makes me a better animator.

Link to comment
Share on other sites

I'll fix some of the code I posted for adapting to a resolution later. And most of the code I posted isn't optimized. I'll try to post optimized examples.


And yes, support for filters is limited at the moment. IE11 will never have them as Microsoft is no longer adding new features to Internet Explorer.


To improve performance on mobile, I use this script to detect for mobile, and base the number of particles to create from it. It's not 100% accurate, but works for most cases.


To achieve maximum performance for particles, it's actually better to not use GSAP for the animation. Too much overhead. :o


But we can still make use of GSAP for other things, like easing.


And you can make an image explode. You can even make a video explode.



I'll post more stuff later, but here's some pens worth studying...

See the Pen cEsze?editors=0010 by dissimulate (@dissimulate) on CodePen


See the Pen otgzH?editors=0010 by dissimulate (@dissimulate) on CodePen


See the Pen dILAG by zadvorsky (@zadvorsky) on CodePen


See the Pen MKwebd by vinsongrant (@vinsongrant) on CodePen



  • Like 3
Link to comment
Share on other sites

Simple Physics


Using circle collision detection. It's super fast and really simple...


1500 particles. Performance should be really good. No tweens, timelines, or transforms are used in this one. 



I'm also going to start using ES6 classes as that's a better way to create objects.

  • Like 5
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.