CSS draws a clock

XboxYan 發表於 2022-11-24
CSS
Welcome to WeChat public account: front-end detective

A good way to practice CSS is to draw all kinds of UI, such as a clock like this?

Kapture 2022-03-27 at 14.10.51

You can also visit this CSS clock to see it in action

There are several difficulties with CSS drawing such a layout:

  1. Scales arranged in a ring
  2. Circularly distributed numbers
  3. Autorun pointer

Let's implement it one by one, I believe you can learn a lot of CSS drawing and animation skills

1. Scales arranged in a ring

When it comes to "ring", conic gradient conic -gradient comes to mind. Suppose there is such a container

 <clock></clock>

Add a little taper gradient

 clock{
  width: 300px;
  height: 300px;
  background: conic-gradient(#333 0 15deg, #cddc39 0deg 30deg);
}

can get this effect

image-20220327143656931

How to make a staggered effect? You can try repeating-conic-gradient

 clock{
  /**/
  background: repeating-conic-gradient(#333 0 15deg, #cddc39 0deg 30deg);
}

The effect is as follows

image-20220327143943041

Still can't see anything to do with the scale? It doesn't matter, let's make the angle of the black part smaller

 clock{
  /**/
  background: repeating-conic-gradient(#333 0 1deg, #cddc39 0deg 30deg);
}

The effect is as follows

image-20220327144256400

Do the lines drawn in this way just correspond to the scale of the clock?

Then turn the entire shape into a ring, which can be achieved with MASK, as follows

 clock{
  /**/
  border-radius: 50%;
  -webkit-mask: radial-gradient(transparent 145px, red 0);
}

The effect is as follows

image-20220327144635739

In fact, there is a small detail here. The black part is not centered and needs to be corrected (you can change the starting angle and specify from). Then, change this grass green to transparent, the complete code is as follows

 clock{
  /**/
  background: repeating-conic-gradient(from -.5deg, #333 0 1deg, transparent 0deg 30deg);
  border-radius: 50%;
  -webkit-mask: radial-gradient(transparent 145px, red 0);
}

final effect

image-20220327145225829

The same is true for the minute scale, because there are 60 scales in total, so the minimum angle is 6 degrees (360 / 60), which is implemented as follows

 clock{
  /**/
  background: repeating-conic-gradient(#333 0 1deg, #cddc39 0deg 6deg);
}

image-20220327145530128

Using the feature that CSS backgrounds can be infinitely superimposed, these two backgrounds can be drawn under the same element, so the complete code is as follows

 clock{
  /**/
  background: repeating-conic-gradient(from -.5deg, #333 0 1deg, transparent 0deg 30deg), 
    repeating-conic-gradient(from -.5deg, #ccc 0 1deg, transparent 0deg 6deg);
  border-radius: 50%;
  -webkit-mask: radial-gradient(transparent 145px, red 0);
}

The final dial scale effect is as follows

image-20220327145858277

2. Circular distribution of numbers

When I saw this layout, my first reaction was actually textPath . This SVG element allows text to be arranged along a specified path, such as the example on MDN below.

 <svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path id="MyPath" fill="none" stroke="red"
        d="M10,90 Q90,90 90,45 Q90,10 50,10 Q10,10 10,40 Q10,70 45,70 Q70,70 75,50" />
  <text>
    <textPath href="#MyPath">
      Quick brown fox jumps over the lazy dog.
    </textPath>
  </text>
</svg>

The effect is as follows

image-20220327151311685

However, there is a flaw in this method, the angle of the text cannot be changed, it can only follow the vertical direction of the path, and the digital direction of the clock is normal.

After some pondering, I found that there is another way to have a similar layout along the path, that is offset-path ! Here is a demo effect on MDN

Kapture 2022-03-27 at 15.19.43

So what does it have to do with arranging numbers in a circle? Suppose there is such a layout

 <clock-pane>
    <num>1</num>
</clock-pane>

Then assign this number to a circular path (currently only paths are supported)

 num{
  offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
}

The effect is as follows (the starting point follows the path path)

image-20220327152419525

Then, the position of the element on the path can be changed by offset-distance , and percentages are supported

 num{
  offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
  offset-distance: 100%
}

Below is the change from 0 to 100%

Kapture 2022-03-27 at 15.27.23

By default, the angle of the element is also adaptively perpendicular to the path, similar to textPath . But we can manually specify a fixed angle, we need offset-rotate , just specify 0deg

 num{
  offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
  offset-rotate: 0deg;
  offset-distance: 100%
}

The effect is as follows, the angle has been completely corrected

Kapture 2022-03-27 at 15.33.07

If you have similar layout requirements, can you refer to this case?

Next, we automatically reset the 12 numbers to the specified position through CSS variables

 <clock-pane>
  <num style="--i:1">1</num>
  <num style="--i:2">2</num>
  <num style="--i:3">3</num>
  <num style="--i:4">4</num>
  <num style="--i:5">5</num>
  <num style="--i:6">6</num>
  <num style="--i:7">7</num>
  <num style="--i:8">8</num>
  <num style="--i:9">9</num>
  <num style="--i:10">10</num>
  <num style="--i:11">11</num>
  <num style="--i:12">12</num>
</clock-pane>

With calc calculation, the complete code is as follows

 num{
  position: absolute;
  offset-path: path('M250 125c0 69.036-55.964 125-125 125S0 194.036 0 125 55.964 0 125 0s125 55.964 125 125z');
  offset-distance: calc( var(--i) * 10% / 1.2 - 25%);
  offset-rotate: 0deg;
}

The effect is as follows

image-20220327153707225

3. The pointer of automatic operation

The drawing of the three pointers should not be too difficult, assuming the structure is as follows

 <hour></hour>
<min></min>
<sec></sec>

Need to pay attention to the center of rotation

 hour{
  position: absolute;
  width: 4px;
  height: 60px;
  background: #333;
  transform-origin: center bottom;
  transform: translateY(-50%) rotate(30deg);
}
min{
  position: absolute;
  width: 4px;
  height: 90px;
  background: #333;
  transform-origin: center bottom;
  transform: translateY(-50%) rotate(60deg);
}
sec{
  position: absolute;
  width: 2px;
  height: 120px;
  background: red;
  transform-origin: center bottom;
  transform: translateY(-50%) rotate(90deg);
}
sec::after{
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  left: 50%;
  bottom: 0;
  background: #fff;
  border: 4px solid #333;
  transform: translate(-50%, 50%);
}

The effect is as follows

image-20220327154441471

At this point, the static layout is complete, so how does it work?

Did you still use timers earlier in this article? CSS can also implement electronic clocks . It is explained in detail that it can be achieved with CSS animations without the help of timers!

Back here, the principle of operation is very simple, it is an infinite loop CSS animation, as follows

 @keyframes clock {
  to {
    transform: translateY(-50%) rotate(360deg);
  }
}

The difference is that the cycles of the hour hand, minute hand and second hand are different. One rotation of the hour hand is 12 hours, the minute hand is 60 minutes, and the second hand is 60 seconds. Each needs to be converted into seconds (CSS units only support seconds and milliseconds ). For convenience Test, here the speed is increased by 60s → 6s

The code implementation is ( --step is one minute)

 hour{
  /**/
  transform: translateY(-50%) rotate(0);
  animation: clock calc(var(--step) * 60 * 12) infinite;
}
min{
  /**/
  transform: translateY(-50%) rotate(0);
  animation: clock calc(var(--step) * 60) infinite;
}
sec{
  /**/
  transform: translateY(-50%) rotate(0);
  animation: clock var(--step) infinite;
}

The effect is as follows

Kapture 2022-03-27 at 15.56.33

Is it a little strange? When the second hand rotates, it first becomes faster, and then slowly becomes slower. This is because the default animation function is ease , so it needs to be changed to linear

 sec{
  /**/
  animation: clock var(--step) infinite linear;
}

Kapture 2022-03-27 at 16.02.56

So much better. However, the clocks and second hands that are usually seen usually walk and stop, and there is a sense of "tick-tick" rhythm, not this seamless. In CSS animation, is it a bit like a ladder? Yes, you can use the steps function of CSS. If you don’t understand this, you can refer to this article by Mr. Zhang: The steps function in the CSS3 animation property is introduced in depth , and the implementation is as follows

 sec{
  /**/
  animation: clock var(--step) infinite steps(60);
}

The effect is as follows

Kapture 2022-03-27 at 16.10.25

Next you need to initialize the time via JS, that's all. It should be noted that when an offset is added, such as 12:30, the minute hand is actually 12.5, and so on. The code is implemented as follows

 const d = new Date()
const h = d.getHours();
const m = d.getMinutes();
const s = d.getSeconds();
clock.style.setProperty('--ds', s)
clock.style.setProperty('--dm', m + s/60)
clock.style.setProperty('--dh', h + m/60 + s/3600)

Then in CSS, you can specify the starting position of the animation by animation-delay

 hour{
  /**/
  animation: clock calc(var(--step) * 60 * 12) infinite linear;
  animation-delay: calc( -1 * var(--step) * var(--dh) * 60);
}
min{
  /**/
  animation: clock calc(var(--step) * 60) infinite linear;
  animation-delay: calc( -1 * var(--step) * var(--dm));
}
sec{
  /**/
  animation: clock var(--step) infinite steps(60);
  animation-delay: calc( -1 * var(--step) * var(--ds) / 60 );
}

Then add some outline decoration to achieve the effect at the beginning of the article

Kapture 2022-03-27 at 14.10.51

The complete code can be viewed in CSS clock

Fourth, a brief summary

The above is the whole process of CSS drawing clock. This article focuses more on the drawing process, especially the circular layout skills. Here is a brief summary.

  1. When you encounter a ring pattern, you can think of conic-gradient
  2. The principle of circular scale drawing is conic-gradient and MASK clipping
  3. Text can be arranged along the path using textPath
  4. textPath does not support changing the text angle
  5. offset-path allows elements to be laid out along a specified path
  6. offset-path can set the offset and angle of the element on the path
  7. The principle of automatic clock running is CSS animation
  8. The difference between clock, minutes and seconds is the animation duration
  9. The "tick-tock" effect can be achieved with steps
  10. Time initialization can be achieved by animation delay

Of course, the focus of this article is not only on the realization of a clock, but on the accumulation and mastery of CSS drawing skills. With the relevant accumulation, when you encounter a similar layout in the future, filter it in your mind, and you can find a solution immediately. Finally, if you think it's good and helpful to you, please like, bookmark, and forward ❤❤❤

Welcome to WeChat public account: front-end detective