9am / 9am.github.io

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

macOS Monterey Screensaver with CSS #13

Open 9am opened 1 year ago

9am commented 1 year ago
Try to build the simple but fantastic macOS screensaver with CSS perspective, clip-path, and gradient.
monterey hits
9am commented 1 year ago

Recently I was learning more about CSS perspective and how 3D works in CSS. With that knowledge, I built a 3D 9am-clock 404 page for this blog. Then the screensaver of Monterey MacOS came back to my mind, I remember used to think about how to do it with Javascript, viewport and stuff, then lose interest cuz there was nothing new for me. But the challenge is back: Can I do it with CSS?

404

Build the wireframe

The initial idea is pretty simple: A mountain slider along the Z-axis.

Line up a series of <li> with a different Z, those are the mountains. The skyline of the mountain can be mocked up with a simple polygon clip-path which we'll deal with later. Now add some control to the perspective-origin and Z of the camera, we got something to begin with.

key0

s1 Edit step-1

Make the camera move forward "seamlessly"

The first thought is to animate the translateZ() from 0 to Infinite, but that will need an infinite number of <li> which is impossible. And we don't want to move the camera back after the animation, so can't do animation-iteration-count: infinite;. So how to trick the eyes to believe there are "Infinite" mountains ahead? The answer is: Move the mountains out of viewport to the end of the list.

This is an old trick to make image sliders.

key1

Now we have Infinite mountains. But inevitably, The CSS animation will stop since there'll be an animation-duration, which leads to the essential idea of this animation: We don't do css animations. If every mountain's Z depends on the nth-child, it will transition to the Z every time we move the first one to the back.

s2 Edit step-2

This will work cuz we define the --n value with the nth-child. And since the counter-values() is still a draft. I used the idea of Kacper Kula to do this more efficiently, now we can remove the pre-defined CSS variable and create more mountains.

s3 Edit step-3

Curves the river and the skyline

In the origin screensaver, you'll notice that the mountains don't align in a straight line, because usually it takes a river to shape the gap between mountains, and the river curves randomly. Thanks to CSS function sin(), we can translate the X base on --n. Now the river is there.

key2

s4 Edit step-4

The mimic polygon clip-path isn't sophisticated for us now, with a little help of javascript, we can make it curvy and random like a real mountain.

Couldn't find a proper way to do this without JS, maybe there'll be a random function for CSS in the future.

s5 Edit step-5

Fill the color with gradient and transition

In CSS, background-color can be transitioned, so if we set the CSS color based on --n like the Z, we'll get a nice transition as the camera goes forward. Nevertheless, that's not true for gradient. But we can achieve this by moving a gradient background:

key3

s6 Edit step-6

Closing thoughts

I know this is not as perfect as the original screensaver, which has a rich landscape and a cloudy filter. But it is so much fun to do it with CSS. Hope there are better ways to deal with randomness and dom manipulation with limitations in the future CSS so I can get rid of the JS from this. Anyway, as a survivor of making polyfill CSS for IE, I'm amazed by the fantastic things we can do with CSS🎉

Well, hope you enjoy it. I'll see you next time.


@9am 🕘

9am commented 1 year ago

Find a way to get rid of the JS that generates the curve lines.

In SVG, we have <feTurbulence> to provide randomness without writing JS code, combined with a <feDisplacementMap> we'll have a filter that twists the source graphic with the channel that <feTurbulence> gives us. The only problem is that the FPS goes down after applying a filter to the mountain, maybe it's just my laptop. Anyway, worth a shot.

s-filter Edit curve-with-feTurbulence

filter live demo

<div class="parent">
  <section class="child"></section>
</div>

<svg>
  <filter id="curve">
    <feTurbulence baseFrequency="0.02 0.01" numOctaves="2" seed="2" />
    <feDisplacementMap in="SourceGraphic" scale="30" />
  </filter>
</svg>
.parent {
  filter: url(#curve);
}

.child {
  background: green;
  clip-path: polygon(
    0% 100%,
    0% 0%,
    20% 0%,
    40% 50%,
    60% 50%,
    80% 0%,
    100% 0%,
    100% 100%
  );
}