A pre-baking animation that performantly add's some fun confetti magic to your app.
You can skip this blurb. I wanted to play around with web performance, so here's some notes on the optimizations:
The animation frames are pre-baked as particles in a super over-simplified particle system. The element starts with the Baker determining the initial random force vectors for every single particle (750 of them) and compiling those into the first screen frame. Then, it passes that first frame to the Bake Worker, (a separate web-worker that keeps all of this frame generation off of the main thread) which calculates the next 25 screen frames, marking some particles for removal if they fall of the screen. Meanwhile, the main thread is free to continue doing UI things. Those 25 screen frames are then passed back (via a non-blocking event) to the Baker that saves them and asks for the next 25. This process goes on till all frames are rendered with all particles removed from the last frame.
The Baker now has all of the screen frames, so those can be passed to the FrameRenderer, one by one, in time, by the Animation manager. Particle objects are reused but ones that are marked as removed for that frame are removed from the scene to save a bit of time when looping over everything in the scene during the next frame.
The Baker can also be asked for screen frames when it doesn't have all of them yet. This allows for it to catch up while the animation plays. I haven't come across a case where things are naturally processed slow enough for this to happen, but it can be emulated by using chrome's dev tools to virtually slow the CPU clock speed down 4X. The FrameRenderer also caches a copy of the current frame when rendering a new one, so if the Animation manager runs out of frames while the Baker catches up, the Frame Renderer will just render the last frame till it's ready. Again, haven't seen that happen in the wild, but still a possibility.
$ npm install xello-confetti
Just include this package in your project. (Ideally right when things are starting. In angular this means in main.ts
.)
import 'xello-confetti'
then use this web component anywhere! (It's registered on the nearest DocumentFragment
or ShadowRoot
) In this case, we're also including a #confetti
for use with @ViewChild
<xello-confetti role="presentation" #confetti />
Use a element reference to it (using @ViewChild
in angular) to run the .play()
instance method to start the animation!
@ViewChild('confetti') confettiEl: ElementRef;
...
confettiEl.play() // Yay! 🎉
The look of the particles can be adjusted! By default, they are colourful rectangles in Xello's brand colours, but can be changed to other colours or textured with images.
Themes are passed in using the setTheme({ options })
instance method. Below is a list of the options you can include. All of the top level options are optional and get replaced by a sane default, however, the size object needs to be supplied in full if you do supply a size setting.
This sets the size ranges for confetti particles. All values are in THREE.js's device-independent dimensions and do not represent a pixel value.
xellofetti.setTheme({
size: {
base: 0.6, // The base size for a confetti height.
variation: 0.2, // Up to this amount will be added to the base size randomly per particle.
ratio: 0.4. // The ratio of each confetti's width to it's height.
}, ...
})
A library of textures (images) to use on materials. This allows you to texture and mask the confetti. Regular textures can be full colour. Alpha textures, used for masking, should only use shades of grey. (There's nothing special to declare if a texture is a regular texture vs alpha texture.) All textures are downloaded and cached when setting the theme so consider file size when adding many textures. Past textures are NOT deleted when running setTheme
unless you overwrite one using the same id
. When possible, don't redeclare textures if you don't have to. It downloads them again.
xellofetti.setTheme({
textures: [
{
id: 'money', // A unique ID to refer to this texture in any materials
url: '/bill.png' // The image URL to download from. (Needs CORS headers if it's NOT on the origin!)
// Note: You can also use 'data:...' urls here.
}, ...
], ...
})
A set of materials that will be randomly selected from when drawing the confetti. The list of materials that confettis are assigned is ALWAYS deleted and replaced with the one you supply.
xellofetti.setTheme({
materials: [
{
colour: '#FF0000', // The base colour for the material
textureMap: 'someTextureID', // The `id` from the texture for the "outside" of the confetti
alphaMap: 'anotherTextureID' // The `id` from the texture for alpha-masking the confetti.
}, ...
], ...
})
The <xello-confetti>
web component has a number of instance methods you can use once it's connected.
Starts the animation. It plays for 350 frames, and will scale to the size of the screen.
Stops the animation. Clears up the confetti. 🧹 Unregisters event listeners and puts everything back where it started.
Sets the theme for the confetti. This does not re-bake the animation and is generally a low-load task. See section above for details about the options
parameter.
- Element should be marked as
role="presentation"
since this effect doesn't mean anything to the accessibility tree. - The animation automatically disables if the user has a preference for less motion.
Use the npm run release
script to make a release. It creates a tag, don't forget to push those!
Use the npm run start
script to start watching for changes on source files. It will make a development build in /dist
. Serve /dist
somehow to view in a browser.