Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A Pure CSS 'mousemove' Detector with :has() #17

Open
9am opened this issue Nov 16, 2023 · 1 comment
Open

A Pure CSS 'mousemove' Detector with :has() #17

9am opened this issue Nov 16, 2023 · 1 comment
Assignees
Labels
animation Animation css CSS

Comments

@9am
Copy link
Owner

9am commented Nov 16, 2023

Build a 'mousemove' event listener with just CSS.
sensor

hits

@9am
Copy link
Owner Author

9am commented Nov 16, 2023

Table of contents


Preface

Let's take a moment to think about how to implement this on the web:

demo

It probably goes like this:

  1. Create a series of boxes with descending sizes in HTML.
  2. Place them concentrically with CSS, like position: absolute.
  3. Add a mousemove event listener with JavaScript, in the handler, and move those boxes according to the offset value to the center position.

But, is it possible to do it without JavaScript? In other words:

For an effect relying on the position of MouseEvent, is there a way to track the 'mouse' using just CSS?


𐄡


Build The Sensor

The MouseEvent in CSS

We're looking for something in CSS that can be changed by MouseEvent, which gives us only 1 option: Pseudo-classes, specifically User action pseudo-classes.

For example, the style under button:active kicks in when the user presses down the button.

When comes to the mousemove, :hover is what we need. Consider this: On top of the target, we can apply a sensor layer, which is composed of small sensor cells. While the cursor moves inside the target, the sensor cells under the cursor will be :hover-ed accordingly. If we can collect the index of the :hover-ed cell, it can be used to calculate the x y coordinates, like the MouseEvent could give us.

Let's build a 2*2 sensor:

<ol class="sensor">
    <li class="cell"></li>
    <li class="cell"></li>
    <li class="cell"></li>
    <li class="cell"></li>
</ol>
.sensor {
    --col: 2;
    --row: 2;
    display: grid;
    grid-template-columns: repeat(var(--col), 1fr);
    grid-template-rows: repeat(var(--row), 1fr);
}

step-1

Edit step-1


The Power of :has()

The problem for the solution is how to collect the index in CSS. The answer is :has(). Known as the 'parent selector', :has() provides the unique power of defining style by the children's behavior.

Suppose we have a 4-cell sensor, the index of the active cell can be collected with a custom variable --n.

.sensor:has(.cell:nth-child(1):hover) { --n: 0; }
.sensor:has(.cell:nth-child(2):hover) { --n: 1; }
.sensor:has(.cell:nth-child(3):hover) { --n: 2; }
.sensor:has(.cell:nth-child(4):hover) { --n: 3; }

.sensor {
    --n: /* will be the index of 'active' cell while the cursor moving around */
}

step-2

Edit step-2


Get The (x,y) Coordinates from Sensor

Now we have the --n, it's easy to get the (x, y) with --col.

--x: mod(var(--n), var(--col))
--y: round(down, var(--n) / var(--col))

Here we use CSS functions mod() and round(), since they are not supported widely, we'll consider replacing them with tricks that work like them:

@property --x {
    syntax: "<integer>";
    initial-value: 0;
    inherits: true;
}
@property --y {
    syntax: "<integer>";
    initial-value: 0;
    inherits: true;
}

--y: calc(var(--n) / var(--col) - 0.5);
--x: calc(var(--n) - var(--col) * var(--y));

step-3

Edit step-3


Put Them Together

Now we can increase the number of cells for a more accurate sensor, and test it with a blue ball placed in the middle of the active cell.

step-4

The 8*8 demo
Edit in Codepen


𐄡


Build The Effect

Now get back to the effect at the beginning. To replace step-3, instead of writing JavaScript, insert the sensor. Now we have the (--x,--y), throw some perspective-3d movement, and we can do it with pure CSS.

demo

Edit in Codepen


𐄡


Closing thoughts

The drawback of this solution is the redundancy of similar declarations for the sensor. There is no way to get rid of them currently unless something like counter-value() can be used with calc(), which has always been the feature I wanted the most for CSS. There is a discussion about this here.

But in cases we can not use JavaScript, and don't need it to be super accurate, it's always good to know we have options.



@9am 🕘

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
animation Animation css CSS
Projects
None yet
Development

No branches or pull requests

1 participant