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

How to build a 3D eye staring at the mouse with only CSS #19

Open
9am opened this issue Apr 16, 2024 · 1 comment
Open

How to build a 3D eye staring at the mouse with only CSS #19

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

Comments

@9am
Copy link
Owner

9am commented Apr 16, 2024

eye

hits

@9am
Copy link
Owner Author

9am commented Apr 16, 2024

Table of contents


Preface

After I found a way to build a 'mousemove' sensor with CSS, the demo became one of my most-liked CodePen. And it opens my mind, I start to think about all the possibilities with :has() & counter(). So the other day when I rotated something in the Browser dev tools, it occurred to me that I can squse more from the sensor: Can it be built with only CSS?

ss-dev-tool

The sensor I've built is a linear one whose output is a [x, y] coordinates, maybe it also works for a radial sensor, ideally it will give me a [angle, dist] tuple. I managed to do that finally and this is a demo to show how to take advantage of the new sensor (CSS only):

gif-final
CodePen


𐄡


Build the Sensor

Get the angle

Compared to the linear version, we'll need sensor cells aligned as pizza slices. Well, the layout can be easily done with rotate() and of course a hard code counter(), but how to cut pizza slices from the block element. Luckily, we have clip-path.

<ol class="sensor">
    <li></li>
    <li></li>
    ...
</ol>
:root {
  --num-i: 12; /* radial sensor cell number */
  --partial-i: calc(1turn / var(--num-i)); /* partial angle */
  --r: 50%; /* sensor size */
}

li {
  --n: /* counter value 1,2,3... */
  --x: calc(50% + sin(var(--partial-i)) * var(--r));
  --y: calc(50% - cos(var(--partial-i)) * var(--r));
  clip-path: polygon(50% 50%, 50% 0%, var(--x) var(--y));
  transform: rotate(calc(var(--partial-i) * var(--n)));
}

Now apply our old trick :has, we'll know the angle when the mouse lands on the sensor:

:root:has(ol > li:nth-child(1):hover) {--i: 0;}
:root:has(ol > li:nth-child(2):hover) {--i: 1;}
...

:root {
    /* angle of line L(mouse, center) */
    --angle: calc(var(--partial-i) * var(--i));
}
ss-sensor-angle

gif-angle-sensor


Get the distance

We'll need more sensor cells to achieve this, for each 'slice' of the pizza, cut them into smaller circular sectors. Several ways to do this, we'll stick with clip-path for this demo.

<!-- add more sectors to the slice -->
<ol class="sensor">
    <li>
        <ul>
            <li></li>
            <li></li>
            ...
        </ul>
    </li>
    ...
</ol>
/* cut circular sectors*/
:root {
  --num-j: 4; /* distance sensor cell number */
  --partial-j: calc(1turn / var(--num-i)); /* partial dist */
}

li li {
  --n: /* counter value 1,2,3... */
  --size: calc((100% - var(--partial-j) * var(--n)) / 2);
  clip-path: circle(var(--size));
}
/* */
:root:has(ul > li:nth-child(1):hover) {--j: 0;}
:root:has(ul > li:nth-child(2):hover) {--j: 1;}
...

:root {
    /* dist between mouse and center in percentage */
    --dist: calc(100% - var(--partial-j) * var(--j));
}
ss-sensor-linear

gif-radial-sensor


Test the Sensor

We'll use a stick to test our sensor, it works like a hand on a watch, the angle of the hand will follow the mouse, and the length of the hand will indicate the distance between the mouse position and the center of the sensor. If we want it more sensitive, just add more sensor cells to it. Here is a 16 * 4 version:

gif-test-sensor
CodePen


𐄡


The Eye

Once got the sensor, we could build a really fancy demo with it. Like this 3D eyeball, it will not only follow the mouse in the right direction but also stare while the mouse moves away from the center since we have [angle, dist] data.

The 3D eyeball

It's possible to build simple 3D objects with CSS now, with the help of perspective-style: preserve-3d and 3D transform. An eyeball can be simulated with sliced circles piled up together.

To make this easier, I built a simple Web Component <arc-ball> to help me adjust the object. It's a fun project, I'll talk about it someday.

gif-arc-ball

Staring at the mouse

.eye {
    transform-style: preserve-3d;
    transform: rotateZ(calc(var(--angle) - 90deg)) rotateY(calc(70deg - var(--j)* 15deg));
}

gif-final
CodePen


𐄡


Closing thoughts

Really need a native counter-value() for CSS, it would save a lot of the hard code *:nth-child(n) {--index: n} for this kind of experiment. Come on W3C!

Hope you enjoy this, I'll see you next time.



@9am 🕘

@9am 9am self-assigned this Apr 16, 2024
@9am 9am added css CSS animation Animation labels Apr 16, 2024
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