DEV Community

Cover image for A cursor trailing effect library for this Christmas
Fin Chen
Fin Chen

Posted on

A cursor trailing effect library for this Christmas

We did a cursor trailing effect on our website. Learned how to do holiday effects the right way, experimented with Canvas and Transition, built a NPM library cursor-effect.
Here's our story...

How it started

I saw an ancient HTML spell the other day:

  How would you suppose this element behave?
Enter fullscreen mode Exit fullscreen mode

Surprised by the fact it still works

Good job safari...

Good job safari

It reminds me of the old days where cursor trail is a sexy effect. So I went googling but most result are telling you how to set cursor trail on Windows

Image description

Luckily there's Cursor Effects ( , which seem to be used on StackOverflow before. So we implemented the same effect on our site: Yourator, with some customization tweaks. This post is what we've learned from implementing this effect, and we also published the effect as an npm library:

Cursor Trails:

Image description

the effect is up on production site til this weekend

Learn from the original cursor-effect library



skeleton of init method

We only needed snowflake effect, so this is what we'll be talking about. The main flow of starts from init, which contains the basic working flow of this effect:

  1. Initialize environment (canvas) for snowflakes
  2. Draw emoji character (possibleEmoji) on canvas
  3. bindEvents listens for mouse & touch event
  4. loop updates snowflake continuously

bindEvents listens for mouse & touch event


We're doing the same thing in onMouseMove and onTouchMove : call addParticle upon the event and create a snowflake at where cursor is.

onWindowResize is responsible for adjusting canvas size.

Why not use CSS width: 100%, height: 100% ?

Since canvas is a canvas with assigned resolution, CSS can only adjust its visual size. If you create a canvas with 100px x 100px and stretch it to 200px x 1000px, then you'll have a 100px x 100px canvas (and pixels in it) which it stretched 2x wide and 10x long. So we need to adjust size of canvas according to window size.
Enter fullscreen mode Exit fullscreen mode


The last line of init was a loop() call, to create an infinite loop using requestAnimationFrame. This loop is responsible to update snowflake position and behaviors, it will call update on each snowflake (particles), or clean up expiring snowflakes. This is the most CPU intensive part.

Image description

More detail on particle update: manage its own lifespan, position, rotating and draw snowflake emoji on canvas accordingly


The original cursor-effect repo is a effect we need. But to be used on our site we have to add some improvements:

  • Use image arrays to render customizable images
  • We want to have more control over snowflake's behavior such as: appearing frequency, it's speed and life, etc.
  • Touch event on touch device will trigger mousemove and touchstart at the same time, generating two (almost overlapping) snowflakes at once.
  • We want to import this library through npm for easier maintainence

What we do

Use image arrays to render customizable images

Change the fillText with drawImage, also add calculation for snowflake opacity: globalAlpha . Since there are several canvas context manipulation, we use save & restore to prevent polluting original context.

Image description

And since image loading is async, we need loadImage handles the image url array

Image description

With the help of promise all (or Promise.allSettled) , to load image before init() call

Image description

Control snowflake's behavior over initialization options

Main benefit is this boosts prototyping and discussion productivity, you can live-tweak and quickly show the result, or even hand the prototype to stakeholders and let them decide the behaviors.

This part is simple, just don't forget the option defaults

Image description

Touch event on touch device will trigger mousemove and touchstart

bindEvent method listens for mousemove touchstart touchmove , but Touch event on touch device will trigger mousemove and touchstart on user touch, causing excess particle creation, you can see touch event order it on MDN. To prevent this we need to detect if this device is touch device or not

Image description

NPM-ify for easier maintainence

Uses ESM and published on NPM, use when in need.

 npm install cursor-trails 

Something learned about Canvas and Transition

Which we adjust cursor-effect to deal with image loading, FPS drops significantly, thought it was because we throw too many image creating at a short time. Even rewrite one version using CSS Transition, just to find out it was because mass creating of SVG element consumes a lot of CPU.

Canvas is very effective with drawing bitmap image in a fixed space. While CSS Transition suits animating DOM elements on the page. So creating lots of image element on canvas is more smooth then creating and transform it.

I must highlight chrome's devtool's "rendering" tab (edge has it also), especially two checked in this image

Image description

it shows fps and paint areas, as gif below

Image description

This was a version where we use CSS Transition, you can see image repaint areas

Other considerations

The effect is sexy (in a retro 90s way), but we shouldn't forgot this is not the main purpose of users on our site (they are here for job searching and career development). So after some discussion we decided to let this sexy feature reside only in the main search section at home page. It's spacey, it is what users first sees, it wont interfere with other thing users want to do. Hope this gets some balance with Christmas ambient and user's opration.

We we're planning to use prefers-reduced-motion to deal with low-end devices, but due to time limitation, that will be put on the roadmap.

This library now only have snow falling effect, hope we can have more strategies on particle behavior. Maybe even customizable strategy, ex: fixed, floating, fadeout of cursor trailing effects.

That's about it.
🎄❄️🧑‍🎄 Merry Christmas 🎄❄️🧑‍🎄

Here's our repo (again):


Top comments (0)