March 1st, 2021

Let’s look at how to get the user’s mouse position and map it into CSS custom properties: `--positionX`

and `--positionY`

.

We could do this in JavaScript. If we did, we could do things like make make an element draggable or move a background. But actually, we can still do similar things, but not use any JavaScript!

I’ve used this method as a part of a demo I’ve made for getting a ‘Click and drag’ effect with pure CSS. I used the `perspective`

tips from my pervious article. It’s a pretty neat effect to get this done entirely in CSS, which might have wider use than my demo, so let’s take a look.

### The setup

Our first demo is going to use `--positionX`

and `--positionY`

custom properties to set the `width`

and `height`

of an element.

### Adding values

We want to use `.cell`

to set the `--positionX`

and `--positionY`

values.

When we hover over a `.cell`

that is in the first (left) column, the value of `--positionX`

should be `0`

. When we hover over a `.cell`

in the second column, the value should be `1`

. It should be `2`

in the third column, and so on.

The same goes for the y-axis. When we hover over a `.cell`

that is in the first (top) row, `--positionY`

should be `0`

, and when we hover over a `cell`

in the second row, the value should be `1`

, and so on.

The numbers in this image represent the numbers of the cell elements in the grid. If we take just a single `.cell`

as an example — let’s say cell 42 — we can select it using `:nth-child()`

:

.cell:nth-child(42) { }

But we need to remember a couple of things:

- We only want this selector to work when we hover over the cell, so we will attach
`:hover`

to it. - We want to select the
`.content`

instead of the cell itself, so we’ll use the General Sibling Combinator (`~`

) to do that.

So now, to set `--positionX`

to `1`

and `--positionY`

to `3`

for `.content`

when the 42nd `cell`

is hovered, we need to do something like this:

.cell:nth-child(42):hover ~ .content { --positionX: 1; --positionY: 3; }

But who wants to do that 100 times!? There are a few approaches to make things easier:

- Use a Sass
`@for`

loop to go through all 100 cells, and do some math to set the proper`--positionX`

and`--positionY`

values for each iteration. - Separate the x- and y-axes to select each row and each column individually with
`:nth-child`

’s functional notation. - Combine those two approaches and use a Sass
`@for`

loop*and*`:nth-child`

functional notation.

I thought long and hard about what would be the best and easiest approach to take, and while all of then have pros and cons, I landed on the third approach. The amount of code to write, the quality of the compiled code, and the math complexity all went into my thinking. Don’t agree? Tell my why in the comments!

### Setting values with a `@for`

loop

```
@for $i from 0 to 10 {
.cell:nth-child(???):hover ~ .content {
--positionX: #{$i};
}
.cell:nth-child(???):hover ~ .content {
--positionY: #{$i};
}
}
```

This is the basic loop. We’re going over it 10 times since we have 10 rows and 10 columns. And we’ve separated the x- and y-axes to set `--positionX`

for each column, and the `--positionY`

for each row. All we need to do now is to replace those `???`

things with the proper notation to select each row and column.

#### Let’s start with the x-axis

Going back to our grid image (the one with numbers), We can see that the numbers of all the cells in the second column are multiples of 10, plus 2. The cells in the third column are multiples of 10 plus 3. And so on.

Now let’s ‘translate’ it into the `:nth-child`

functional notation. Here’s how that looks for the second column:

`:nth-child(10n + 2)`

`10n`

selects every multiple of 10.`2`

is the column number.

And for our loop, we will replace the column number with `#{$i + 1}`

to iterate sequentially:

.cell:nth-child(10n + #{$i + 1}):hover ~ .content { --positionX: #{$i}; }

#### Now lets deal with the y-axis

Look again at the grid image and focus on the fourth row. The cell numbers are somewhere between 41 and 50. The cells in the fifth row are between 51 to 60, and so on. To select each row, we’ll need to define its range. For example, the range for the fourth row is:

.cell:nth-child(n + 41):nth-child(-n + 50)

`(n + 41)`

is the start of the range.`(-n + 50)`

is the end of the range.

Now we’ll replace the numbers with some math on the `$i`

value. For the start of the range, we get `(n + #{10 * $i + 1})`

, and for the end of the range we get `(-n + #{10 * ($i + 1)})`

.

So the final `@for`

loop is:

@for $i from 0 to 10 { .cell:nth-child(10n + #{$i + 1}):hover ~ .content { --positionX: #{$i}; } .cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content { --positionY: #{$i}; } }

The mapping is done! When we hover over elements, `--positionX`

and `--positionY`

change according to the mouse position. That means we can use them to control the elements inside the `.content`

.

### Handling the custom properties

OK, so now we have the mouse position mapped to two custom-properties, The next thing is to use them to control the `.square`

element’s `width`

and `height`

values.

Let’s start with the `width`

and say that we want the minimum `width`

of the `.square`

to be `100px`

(i.e. when the mouse cursor is at the left side of the screen), and we want it to grow `20px`

for each step the mouse cursor moves to the right.

Using `calc()`

, we can do that:

.square { width: calc(100px + var(--positionX) * 20px); }

And of course, we’ll do the same for the `height`

, but with `--positionY`

instead:

.square { width: calc(100px + var(--positionX) * 20px); height: calc(100px + var(--positionY) * 20px); }

That’s it! Now we have a simple `.square`

element, with a `width`

and `height`

that is controlled by the mouse position. Move your mouse cursor over the window, and see how the `square`

changes its `width`

and `height`

accordingly.

I added a small `transition`

to make the effect smoother. That’s not required, of course. And I’ve also commented out on the `.cell`

border.

### Let’s try an alternative method

There might be a case where you want to ‘bypass’ `--positionX`

and `--positionY`

, and set the end value directly inside the `@for`

loop. So, for our example it would look like this:

@for $i from 0 to 10 { .cell:nth-child(10n + #{$i + 1}):hover ~ .content { --squareWidth: #{100 + $i * 20}px; } .cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content { --squareHeight: #{100 + $i * 20}px;: #{$i}; } }

Then the `.square`

would use the custom properties like this:

.square { width: var(--squareWidth); height: var(--squareHeight); }

This method is a little more flexible because it allows for more advanced Sass math (and string) functions. That said, the general principle is absolutely the same as what we already covered.

### What’s next?

Well, the rest is up to you — and the possibilities are endless! How do you think you’d use it? Can you take it further? Try using this trick in your CSS, and share your work in the comments or let me know about it on Twitter. It will be great to see a collection of these come together.

Here are a few examples to get your hamster wheel turning:

The original post How to Map Mouse Position in CSS.