9am / 9am.github.io

9am Blog 🕘 ㍡
https://9am.github.io/
MIT License
3 stars 0 forks source link

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

Open 9am opened 5 months ago

9am commented 5 months ago
eye hits
9am commented 5 months ago

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`. ```

  1. ...
``` ```css :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: ```css :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](https://github.com/9am/9am.github.io/assets/1435457/ba4ca940-3f48-4126-91e7-92db6da26118)
### 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. ```html
    • ...
  1. ...
``` ```css /* 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)); } ``` ```css /* */ :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](https://github.com/9am/9am.github.io/assets/1435457/08075403-ac93-4e24-816e-85cd4a2cf669)
### 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](https://github.com/9am/9am.github.io/assets/1435457/cb464bb2-7933-4d53-b9e4-82b902e4b019) > [CodePen](https://codepen.io/9am/pen/YzMEprR)

𐄡


## 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 [``](https://github.com/9am/arc-ball) to help me adjust the object. It's a fun project, I'll talk about it someday. > ![gif-arc-ball](https://github.com/9am/9am.github.io/assets/1435457/11863e68-a362-4ee8-9fd7-f0c9ea4ccfdb) ### Staring at the mouse ```css .eye { transform-style: preserve-3d; transform: rotateZ(calc(var(--angle) - 90deg)) rotateY(calc(70deg - var(--j)* 15deg)); } ``` > ![gif-final](https://github.com/9am/9am.github.io/assets/1435457/191da596-ab47-4132-b877-4919fb341862) > [CodePen](https://codepen.io/9am/details/oNVKOKB)

𐄡


## 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 🕘 > * Read more [articles](https://9am.github.io) at [9am.github.io](https://9am.github.io) > * Find other [things](https://www.npmjs.com/search?q=%409am) I built on [GitHub](https://github.com/9am), [NPM](https://www.npmjs.com/search?q=%409am) and [CodePen](https://codepen.io/9am) > * Contact me via [email](mailto:tech.9am@gmail.com) > * [![Creative Commons License](https://i.creativecommons.org/l/by-nc-nd/4.0/80x15.png)](http://creativecommons.org/licenses/by-nc-nd/4.0/)