Christian Heilmann

Sharing data between CSS and JavaScript using custom properties

Monday, February 8th, 2021 at 10:20 pm

One of the big battles we see in the web development world is still CSS vs. JavaScript. Both have their merits, their own syntax and ideas and it can be tough to get your head around them.

This is why I love that we have ways to make the two communicate and use each for what it is best at. For one thing, I always found it annoying to manipulate the styles object of a DOM element. It meant accessing the element and setting the various style properties. In the end, it resulted in an inline style attribute you’d never write by hand.

A much cleaner way to me is to use CSS custom properties. These are commonly called “CSS variables” and you define them in CSS using the—syntax.

:root {
  --pagebackground: powderblue;
}
body {
  background: var(--pagebackground);
}

Being “variables”, you can re-use them throughout your styles document.

The fun begins when you use JavaScript to manipulate them. In the case of this example, the CSS custom property is set on the root element of the document. So you can read it with JavaScript using the following.

let bg = getComputedStyle(document.documentElement).
  getPropertyValue('--pagebackground');

And you can set it with JavaScript by accessing the style of the root element (or any other element with custom properties) and setting a property.

document.documentElement.style.setProperty('--pagebackground', 'firebrick');

You can try this live on codepen:


See the Pen
Testing CSS Custom properties interaction
by Christian Heilmann (@codepo8)
on CodePen.


The great thing about that is that you can use the power of JavaScript to give CSS things it can’t access. For example, CSS can’t read the coordinate of the mouse cursor, but JavaScript can.

In our CSS, we can define two properties as 0:

:root {
  --mouse-x: 0;
  --mouse-y: 0;
}

And in JavaScript, we add a mousemove handler to the document and manipulate these two properties:

let root =  document.documentElement
document.addEventListener('mousemove', e => {
  root.style.setProperty('--mouse-x', e.x);
  root.style.setProperty('--mouse-y', e.y);
});

And that’s all the JavaScript we need. As CSS custom properties are live and change their value, we can now, for example, show a circle where he mouse cursor is in CSS using the following.

Our HTML:

<div id="ball"></div>

The CSS:

:root {
  --mouse-x: 0;
  --mouse-y: 0;
}
#ball {
 width: 20px;
 height: 20px;
 background: white;
 border-radius: 100%; 
 transform: translate(
 calc(calc(var(--mouse-x) - 10) * 1px), 
 calc(calc(var(--mouse-y) - 10) * 1px)
 );
}

Some information on the CSS here:

  • We set the width and height of the ball DIV to 20 pixels and the background to white.
  • Adding a border-radius of 100% makes sure we get a circle and not a square.
  • We then use transform: translate to position the circle on the screen. This could be something like transform:translate(200px, 300px) to position our ball at 200 pixels horizontal and 300 pixels vertical.
  • As JavaScript sets the CSS custom property to a numeric value, we need to convert it to pixels by multiplying it with “1px”.
  • And as the ball is 20 pixels big, we can’t just place it at—mouse-x and—mouse-y but we need to subtract 10 from it to centre it on the cursor.

This trick allows us to do complex calculations, read out browser state and interaction state in JavaScript and still keep all the look and feel in CSS. To me, that’s a win.

If you want to see it in action, you can try this codepen. I also added a background effect to show how you can re-use the mouse x and y data:


See the Pen
Mouse position as custom CSS property
by Christian Heilmann (@codepo8)
on CodePen.


Share on Mastodon (needs instance)

Share on Twitter

Newsletter

Check out the Dev Digest Newsletter I write every week for WeAreDevelopers. Latest issues:

Dev Digest 146: 🥱 React fatigue 📊 Query anything with SQL 🧠 AI News

Why it may not be needed to learn React, why Deepfake masks will be a big problem and your spirit animal in body fat! 

Dev Digest 147: Free Copilot! Panel: AI and devs! RTO is bad! Pi plays!

Free Copilot! Experts discuss what AI means for devs. Don't trust containers. Mandated RTO means brain drain. And Pi plays Pokemon!

Dev Digest 148: Behind the scenes of Dev Digest & end of the year reports.

In 50 editions of Dev Digest we gave you 2081 resources. Join us in looking back and learn about all the trends this year.

Dev Digest 149: Wordpress break, VW tracking leak, ChatGPT vs Google.

Slowly starting 2025 we look at ChatGPT vs Google, Copilot vs. Cursor and the state of AI crawlers to replace web search…

Dev Digest 150: Shifting manually to AI.

Manual coding is becoming less of a skill. How can we ensure the quality of generated code? Also, unpacking an APK can get you an AI model.

My other work: