Animating with CSS variables and Web Audio

We will learn to animate elements by utilising web audio and CSS variables

4 min read
by Mees Rutten | Mon Jul 20 2020

In this article i'll recreate a web animation I used for a friend of mine who is a musician. I created his online portfolio filled with web animation, more about that later!

Tech stack

  • HTML for creating element to animate, etc.
  • P5js to get 5 values from audio clips
  • CSS Variables, just regular CSS while utilising CSS :root variables

Goal

We want to recreate the classic audio visualisation bars in a performant way.

Markup

First we create some bars. We want them to alternate in direction.

1<div class="half-height">
2 <div class="divider">
3 <div class="bar horizontal"></div>
4 <div class="bar horizontal"></div>
5 <div class="bar horizontal"></div>
6 <div class="bar horizontal"></div>
7 <div class="bar horizontal"></div>
8 </div>
9</div>
10<div class="half-height">
11 <div class="divider reverse">
12 <div class="bar horizontal"></div>
13 <div class="bar horizontal"></div>
14 <div class="bar horizontal"></div>
15 <div class="bar horizontal"></div>
16 <div class="bar horizontal"></div>
17 </div>
18</div>

Don't forget to include the P5 library and add-ons you need

1<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>
2<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
3<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.sound.min.js"></script>

CSS

Normal CSS!? For this example, just regular CSS was fine. I created some bars with a gradient background.

1.divider {
2 width: 100%;
3 display: flex;
4 flex-direction: column;
5 align-items: flex-end;
6}
7.divider.reverse {
8 align-items: flex-start;
9}
10.divider .bar.horizontal {
11 width: 50vw;
12 height: 0.5rem;
13 transition: all 60ms ease-in-out;
14 background: linear-gradient(
15 to left,
16 #23074d,
17 #6800ed
18 ); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
19 transform-origin: right;
20 opacity: 0.85;
21}
22.divider.reverse .bar.horizontal {
23 transform-origin: left;
24}
25
26.bar-container {
27 display: flex;
28 align-items: flex-end;
29 height: 100vh;
30}

CSS Variables

We defined the variables we need:

1:root {
2 --mapBass: 0;
3 --mapTremble: 0;
4 --mapLowMid: 0;
5 --mapMid: 0;
6 --mapHighMid: 0;
7}

Animating CSS variables

With JavaScript you can update CSS variables to certain values. These values can then be used in CSS like this:

1.bar.horizontal:nth-child(1) {
2 transform: scaleX(var(--mapBass));
3}
4.bar.horizontal:nth-child(2) {
5 transform: scaleX(var(--mapTremble));
6}
7.bar.horizontal:nth-child(3) {
8 transform: scaleX(var(--mapLowMid));
9}
10.bar.horizontal:nth-child(4) {
11 transform: scaleX(var(--mapMid));
12}
13.bar.horizontal:nth-child(5) {
14 transform: scaleX(var(--mapHighMid));
15}

Because we defined a transition on the bar, the bars will animate when the variables are updated.

Updating CSS variables with JavaScript

1this.root.style.setProperty('--mapBass', p.map(bass, 0, 255, 0, 2.0));
2this.root.style.setProperty('--mapTremble', p.map(treble, 0, 255, 0, 2.0));
3this.root.style.setProperty('--mapLowMid', p.map(lowMid, 0, 255, 0, 2.0));
4this.root.style.setProperty('--mapMid', p.map(mid, 0, 255, 0, 2.0));
5this.root.style.setProperty('--mapHighMid', p.map(highMid, 0, 255, 0, 2.0));

Gathering the audio values with P5js

1class Audio {
2 constructor() {
3 this.mapBass = 0;
4 this.mapTremble = 0;
5 this.mapMid = 0;
6 this.mapLowMid = 0;
7 this.mapHighMid = 0;
8 this.bars = Array.from(document.querySelectorAll('.bar'));
9 this.root = document.documentElement;
10 }
11
12 init() {
13 const s = p => {
14 let audio, fft;
15 // Load the soundfile
16 p.preload = () => {
17 audio = p.loadSound('/src/jerry-island-free.mp3');
18
19 // Play/pause button
20 document.querySelector('#play').addEventListener('click', e => {
21 e.target.remove();
22 if (audio.isPlaying()) {
23 audio.pause();
24 } else {
25 audio.loop();
26 }
27 });
28 };
29
30 p.setup = () => {
31 fft = new p5.FFT();
32 };
33
34 p.draw = () => {
35 fft.analyze();
36
37 // Get sound values
38 const bass = fft.getEnergy('bass');
39 const treble = fft.getEnergy('treble');
40 const lowMid = fft.getEnergy('lowMid');
41 const mid = fft.getEnergy('mid');
42 const highMid = fft.getEnergy('highMid');
43
44 // Set CSS variables to a value between 0 and 2
45 this.root.style.setProperty('--mapBass', p.map(bass, 0, 255, 0, 2.0));
46 this.root.style.setProperty(
47 '--mapTremble',
48 p.map(treble, 0, 255, 0, 2.0)
49 );
50 this.root.style.setProperty(
51 '--mapLowMid',
52 p.map(lowMid, 0, 255, 0, 2.0)
53 );
54 this.root.style.setProperty('--mapMid', p.map(mid, 0, 255, 0, 2.0));
55 this.root.style.setProperty(
56 '--mapHighMid',
57 p.map(highMid, 0, 255, 0, 2.0)
58 );
59 };
60 };
61 new p5(s);
62 }
63}

Then start the program like this:

1const audio = new Audio();
2audio.init();

Demo

And we're done! Have a look at the demo to see how it all comes together.

PS.

Next week there will be an article about converting someones head to particles and then animating those particles to audio values. Sounds cool, right?


Thanks for reading!

I hope someone somewhere learned something via this post! If you did, please consider sharing the article.

by @

All rights reserved