Physics-Based Background Scroll Effects
The Game Plan
Let’s get right to it. Here’s the first demo. Scroll it and watch the background.
Notice the bubbles don’t have a fixed path as you scroll. They float around, bump into each other, and carry momentum. By simulating physics for the bubbles, we’ve created a background that feels lively and organic.
How do we accomplish this? Here’s the high-level overview.
- Use Matter.js to simulate a bunch of circlular bodies (the bubbles).
- Render these bodies onto a
<canvas>that is fixed behind the page content and sized to match the width and height of the viewport.
- Add a scroll event listener on the window to detect scrolling and apply appropriate velocity to the bodies.
Feel free to check out the code that makes this all happen. If you want the guided tour, read on and I’ll cover some of the highlights.
Don’t forget about usability! Make sure your page content is readable and be cognizant of those with motion sensitivity. The
prefers-reduced-motion media query is relevant here, though sadly only supported by Safari at the time of writing.
These demos were made to show off the concept, but you should temper them in practice. Consider the tone and purpose of your site and use your best judgement.
Digging Into the Code
We start with a
<canvas> element in the HTML for Matter.js to use.
A little bit of CSS positions the
<canvas> behind the main page content.
We also need to set the width and height of the
The code is organized as a prototype object with several configurable options, an
init() function to kick things off, and then a bunch of other properties and methods to handle various things.
Having all the customizable options lumped together makes it really easy to tweak values and experiment (go ahead, try it out). We can also easily override them when calling
init() to kick things off.
init() function also creates and configures 3 important objects for using Matter.js: the engine (does the work of simulating physics), the render (sets up the
<canvas> and draws on it), and the runner (keeps the engine running).
After that we can calculate how many bodies (bubbles) will fit in the viewport, then create them in a loop.
createBody() method looks like this.
It picks a random position and radius for the circle body, then calls
Matter.Bodies.circle() to create it. A lot of the customizable options come into play here.
One of the things we pass into
Matter.Bodies.circle() is a
plugin.wrap object. This is for the matter-wrap plugin, which we’re using to make bodies wrap around to the other side of the
<canvas> when they move beyond an edge. This lets us recycle bodies as they move in/out of view, instead of constantly creating new ones while scrolling.
Speaking of scrolling, here’s the line of code to add the scroll event listener.
Scrolling can fire events very rapidly, so we use
onScrollThrottled() to create a timeout that limits how often we call
onScroll(), which is where the real work is done.
onScroll() determines how far the page has been scrolled, then adds an appropriate amount of vertical velocity to each body. It injects a little randomness, and even a bit of horizontal velocity, to make the bubbles more lively.
That concludes the guided tour. I left some minor things out, but again, the full source code is all yours if you’re interested. The nice thing about this code is that it’s very reusable. With some minor tweaks and a bit of CSS, we can easily create other physics-based background scroll effects.
Read on and I’ll show you.
The next demo shows off a hex bokeh effect. Give it a scroll and watch the background.
As mentioned, we’re recycling just about all the code from the first demo, but with a few adjustments. The most obvious is that we’ve replaced circles with hexagons.
We’ve also changed a couple options. Notably,
xVarianceRange has positive range values to create the slight diagonal scroll effect,
opacity is reduced to make bodies semi-transparent, and
collisions are disabled so bodies float over each other.
Here’s the twist. We’re actually using 2
<canvas> backgrounds together to create a depth of field effect.
<canvas> has a
1px blur just to soften the edges, while the
<canvas> behind it has a
10px blur to appear out of focus. This is done with a CSS filter.
pixelsPerBody, creating larger bodies but less of them.
And there you have it. We’ve created a markedly different effect without changing the core of how the code works.
Let’s do it again!
Now with SVG Filters
This final demo uses an SVG filter to achieve the famous gooey blob effect. Once again, give the demo a scroll and watch how the blobs move.
This demo goes back to simulating fully opaque circular bodies, except without collisions.
airFriction is increased and
scrollVelocity is decreased to give the blobs a thicker feel as you scroll.
xVarianceRange is bigger to make the blobs more chaotic.
Here’s the SVG filter to create the gooey blob effect. If you want an explanation of how it works, here’s an excellent article.
Once again, we’re using double
<canvas> backgrounds. They both apply the goo filter, but the second background has a slight blur for depth of field.
yVarianceRange with negative values, causing the blobs to move in the opposite direction of scrolling. We also override
pixelsPerBody to add more mass to the blobs and
colors to make these blobs lighter.
Hopefully this article has shown you something interesting to play with. Again, all the code is yours to take and is structured to make it easy to tweak. You can even play around with the demos on CodePen (floaty bubbles, hex bokeh, goo blobs).
Thanks for reading!