Let’s build a fully functioning and settable “analog” clock with CSS custom properties and the calc() function. Then we’ll convert it into a “digital” clock as well. All this with no JavaScript!
Here’s a quick look at the clocks we’ll make:
GitHub repoBrushing up on the calc() function
CSS preprocessors teased us forever with the ability to calculate numerical CSS values. The problem with pre-processors is that they lack knowledge of the context after the CSS code has compiled. This is why it’s impossible to say you want your element width to be 100% of the container minus 50 pixels. This produces an error in a preprocessor:
width: 100% - 50px; // error: Incompatible units: 'px' and '%'
Preprocessors, as their name suggests, preprocess your instructions, but their output is still just plain old CSS which is why they can’t reconcile different units in your arithmetic operations. Ana has gone into great detail on the conflicts between Sass and CSS features.
The good news is that native CSS calculations are not only possible, but we can even combine different units, like pixels and percentages with the calc() function:
width: calc(100% - 50px);
calc() can be used anywhere a length, frequency, angle, time, percentage, number, or integer is allowed. Check out the CSS Guide to CSS Functions for a complete overview.
What makes this even more powerful is the fact that you can combine calc() with CSS custom properties—something preprocessors are unable to handle.
The hands of the clock
Let’s lay out the foundations first with a few custom properties and the animation definition for the hands of the analog clock:
:root { --second: 1s; --minute: calc(var(--second) * 60); --hour: calc(var(--minute) * 60); } @keyframes rotate { from { transform: rotate(0); } to { transform: rotate(1turn); } }
Everything starts in the root context with the --second custom property where we defined that a second should be, well, one second (1s). All future values and timings will be derived from this.
This property is essentially the heart of our clock and controls how fast or slow all of the clock’s hands go. Setting --second to 1s makes the clock match real-life time but we could make it go at half speed by setting it to 2s, or even 100 times faster by setting it to 10ms.
The first property we are calculating is the --minute hand, which we want equal to 60 times one second. We can reference the value from the --second property and multiply it by 60 with the help of calc() :
--minute: calc(var(--second) * 60);
The --hour hand property is defined using the exact same principle but multiplied by the --minute hand value:
--hour: calc(var(--minute) * 60);
We want all three hands on the clock to rotate from 0 to 360 degrees—around the shape of the clock face! The difference between the three animations is how long it takes each to go all the way around. Instead of using 360deg as our full-circle value, we can use the perfectly valid CSS value of 1turn.
@keyframes rotate { from { transform: rotate(0); } to { transform: rotate(1turn); } }
These @keyframes simply tell the browser to turn the element around once during the animation. We have defined an animation named rotate and it is now ready to be assigned to the clock’s second hand:
.second.hand { animation: rotate steps(60) var(--minute) infinite; }
We’re using the animation shorthand property to define the details of the animation. We added the name of the animation (rotate), how long we want the animation to run (var(--minute), or 60 seconds) and how many times to run it (infinite, meaning it never stops running). steps(60) is the animation timing function which tells the browser to perform the 1-turn animation in 60 equal steps. This way, the seconds hand ticks at each second rather than rotating smoothly along the circle.
While we are discussing CSS animations, we can define an animation delay (animation-delay) if we want the animation to start later, and we can change whether the animation should play forwards or backwards using animation-direction. We can even pause and restart the animations with animation-play-state.
The animation on the minute and hour hands will work very much like on the second hand. The difference is that multiple steps are unnecessary here—these hands can rotate in a smooth, linear fashion.
The minute hand takes one hour to complete one full turn, so:
.minute.hand { animation: rotate linear var(--hour) infinite; }
On the other hand (pun intended) the hour hand takes twelve hours to go around the clock. We don’t have a separate custom property for this amount of time, like --half-day, so we will multiply --hour by twelve:
.hour.hand { animation: rotate linear calc(var(--hour) * 12) infinite; }
You probably get the idea of how the hands of the clock work by now. But it would not be a complete example if we didn’t actually build the clock.
The clock face
So far, we’ve only looked at the CSS aspect of the clock. We also need some HTML for all that to work. Here’s what I’m using:
<main> <div> <div></div> <div></div> <div></div> </div> </main>
Let’s see what we have to do to style our clock:
.clock { width: 300px; height: 300px; border-radius: 50%; background-color: var(--grey); margin: 0 auto; position: relative; }
We made the clock 300px tall and wide, made the background color grey (using a custom property, --grey, we can define later) and turned it into a circle with a 50% border radius.
There are three hands on the clock. Let’s first move these to the center of the clock face with absolute positioning:
.hand { position: absolute; left: 50%; top: 50%; }
Notice the name of this class (.hands) because all three hands use it for their base styles. That’s important because any changes to this class are applied to all three hands.
Let’s define the hand dimensions and color things up:
.hand { position: absolute; left: 50%; top: 50%; width: 10px; height: 150px; background-color: var(--blue); }
The hands are now all in place:
Getting proper rotation
Let’s hold off celebrating just a moment. We have a few issues and they might not be obvious when the clock hands are this thin. What if we change them to be 100px wide:
We can now see that if an element is positioned 50% from the left, it is aligned to the center of the parent element—but that’s not exactly what we need. To fix this, the left coordinate needs to be 50%, minus half the width of the hand, which is 50px in our case:
Working with multiple different measurements is a breeze for the calc() function:
.hand { position: absolute; left: calc(50% - 50px); top: 50%; width: 100px; height: 150px; background-color: var(--grey); }
This fixes our initial positioning, however, if we try to rotate the element we can see that the transform origin, which is the pivot point of the rotation, is at the center of the element:
We can use the transform-origin property to change the rotation origin point to be at the center of the x-axis and at the top on the y-axis:
.hand { position: absolute; left: calc(50% - 50px); top: 50%; width: 100px; height: 150px; background-color: var(--grey); transform-origin: center 0; }
This is great, but not perfect because our pixel values for the clock hands are hardcoded. What if we want our hands to have different widths and heights, and scale with the actual size of the clock? Yes, you guessed right: we need a few CSS custom properties!
.second { --width: 5px; --height: 140px; --color: var(--yellow); } .minute { --width: 10px; --height: 90px; --color: var(--blue); } .hour { --width: 10px; --height: 50px; --color: var(--dark-blue); }
With this, we’ve defined custom properties for the individual hands. What’s interesting here is that we gave these properties the same names: --width, --height, and --color. How is it possible that we gave them different values but they don’t overwrite each other? And which value will I get back if I call var(--width), var(--height) or var(--color)?
Let’s look at the hour hand:
<div></div>
We assigned new custom properties to the .hour class and they are locally scoped to the element, which includes the element and all its children. This means any CSS style applied to the element—or its children accessing the custom properties—will see and use the specific values that were set within their own scope. So if you call var(--width) inside the hour hand element or any ancestor elements inside that, the value returned from our example above is 10px. This also means that if we try using any of these properties outside these elements—for example inside the body element—they are inaccessible to those elements outside the scope.
Moving on to the hands for seconds and minutes, we enter a different scope. That means the custom properties can be redefined with different values.
.second { --width: 5px; --height: 140px; --color: var(--yellow); } .minute { --width: 10px; --height: 90px; --color: var(--blue); } .hour { --width: 10px; --height: 50px; --color: var(--dark-blue); } .hand { position: absolute; top: 50%; left: calc(50% - var(--width) / 2); width: var(--width); height: var(--height); background-color: var(--color); transform-origin: center 0; }
The great thing about this is that the .hand class (which we assigned to all three hand elements) can reference these properties for the calculations and declarations. And each hand will receive its own properties from its own scope. In a way, we’re personalizing the .hand class for each element to avoid unnecessary repetition in our code.
Our clock is up and running and all hands are moving at the correct speed:
We could stop here but let me suggest a few improvements. The hands on the clock start at 6 o’clock, but we could set their initial positions to 12 o’clock by rotating the clock 180 degrees. Let’s add this line to the .clock class:
.clock { /* same as before */ transform: rotate(180deg); }
The hands might look nice with rounded edges:
.hand { /* same as before */ border-radius: calc(var(--width) / 2); }
Our clock looks and works great! And all hands start from 12 o’clock, exactly how we want it!
Setting the clock
Even with all these awesome features, the clock is unusable as it fails terribly at telling the actual time. However, there are some hard limitations to what we can do about this. It’s simply not possible to access the local time with HTML and CSS to automatically set our clock. But we can prepare it for manual setup.
We can set the clock to start at a certain hour and minute and if we run the HTML at exactly that time it will keep the time accurately afterwards. This is basically how you set a real-world clock, so I think this is an acceptable solution. ?
Let’s add the time we want to start the clock as custom properties inside the .clock class:
.clock { --setTimeHour: 16; --setTimeMinute: 20; /* same as before */ }
The current time for me as I write is coming up to 16:20 (or 4:20) so the clock will be set to that time. All I need to do is refresh the page at 16:20 and it will keep the time accurately.
OK, but… how can we set the time to these positions and rotate the hands if a CSS animation is already controlling the rotation?
Ideally, we want to rotate and set the hands of the clock to a specific position when the animation starts at the very beginning. Say you want to set the hour hand to 90 degrees so it starts at 3:00 pm and initialize the rotation animation from this position:
/* this will not work */ .hour.hand { transform: rotate(90deg); animation: rotate linear var(--hour) infinite; }
Well, unfortunately, this will not work because the transform is immediately overridden by the animation, as it modifies the very same transform property. So, no matter what we set this to, it will go back to 0 degrees where the first keyframe of the animation starts.
We can set the rotation of the hand independently from the animation. For example, we could wrap the hand into an extra element. This extra parent element, the “setter,” would be responsible for setting the initial position, then the hand element inside could animate from 0 to 360 degrees independently. The starting 0-degree position would then be relative to what we set the parent setter element to.
This would work but luckily there’s a better option! Let me amaze you! ?✨✨
The animation-delay property is what we usually use to start the animation with some predefined delay.
The trick is to use a negative value, which starts the animation immediately, but from a specific point in the animation timeline!
To start 10 seconds into the animation:
animation-delay: -10s;
In case of the hour and minute hands, the actual value we need for the delay is calculated from the --setTimeHour and --setTimeMinute properties. To help with the calculation, let’s create two new properties with the amount of time shifting we need:
- The hour hand needs to be the hour we want to set the clock, multiplied by an hour.
- The minute hand shifts the minute we want to set the clock multiplied by a minute.
--setTimeHour: 16; --setTimeMinute: 20; --timeShiftHour: calc(var(--setTimeHour) * var(--hour)); --timeShiftMinute: calc(var(--setTimeMinute) * var(--minute));
These new properties contain the exact amount of time we need for the animation-delay property to set our clock. Let’s add these to our animation definitions:
.second.hand { animation: rotate steps(60) var(--minute) infinite; } .minute.hand { animation: rotate linear var(--hour) infinite; animation-delay: calc(var(--timeShiftMinute) * -1); } .hour.hand { animation: rotate linear calc(var(--hour) * 12) infinite; animation-delay: calc(var(--timeShiftHour) * -1); }
Notice how we multiplied these values by -1 to convert them to a negative number.
We have almost reached perfection with this, but there’s a slight issue: if we set the number of minutes to 30, for example, the hour hand needs to already be halfway through to the next hour. An even worse situation would be to set the minutes to 59 and the hour hand is still at the beginning of the hour.
fix this, all we need to do is add the minute shift and the hour shift values together for the hour hand:
.hour.hand { animation: rotate linear calc(var(--hour) * 12) infinite; animation-delay: calc( (var(--timeShiftHour) + var(--timeShiftMinute)) * -1 ); }
And I think with this we have fixed everything. Let’s admire our beautiful, pure CSS, settable, analog clock:
Let’s go digital
In principle, an analog and a digital clock both use the same calculations, the difference being the visual representation of the calculations.
Here’s my idea: we can create a digital clock by setting up tall, vertical columns of numbers and animate these instead of rotating the clock hands. Removing the overflow mask from the final version of the clock container reveals the trick:
The new HTML markup needs to contain all the numbers for all three sections of the clock from 00 to 59 on the second and minute sections and 00 to 23 on the hour section:
<main> <div> <div> <ul> <li>00</li> <li>01</li> <!-- etc. --> <li>22</li> <li>23</li> </ul> </div> <div> <ul> <li>00</li> <li>01</li> <!-- etc. --> <li>58</li> <li>59</li> </ul> </div> <div> <ul> <li>00</li> <li>01</li> <!-- etc. --> <li>58</li> <li>59</li> </ul> </div> </div> </main>
To make these numbers line up, we need to write some CSS, but to get started with the styling we can copy over the custom properties from the :root scope of the analog clock straight away, as time is universal:
:root { --second: 1s; --minute: calc(var(--second) * 60); --hour: calc(var(--minute) * 60); }
The outermost wrapper element, the .clock, still has the very same custom properties for setting the initial time. All that’s changed is that it becomes a flexbox container:
.clock { --setTimeHour: 14; --setTimeMinute: 01; --timeShiftHour: calc(var(--setTimeHour) * var(--hour)); --timeShiftMinute: calc(var(--setTimeMinute) * var(--minute)); width: 150px; height: 50px; background-color: var(--grey); margin: 0 auto; position: relative; display: flex; }
The three unordered lists and the list items inside them that hold the numbers don’t need any special treatment. The numbers will stack on top of each other if their horizontal space is limited. Let’s just make sure that there is no list styling to prevent bullet points and that we center things for consistent placement:
.section > ul { list-style: none; margin: 0; padding: 0; } .section > ul > li { width: 50px; height: 50px; font-size: 32px; text-align: center; padding-top: 2px; }
The layout is done!
Outside each unordered list is a
.section { position: relative; width: calc(100% / 3); overflow: hidden; }
The structure of the clock is done and the rails are now ready to be animated.
Animating the digital clock
The basic idea behind the whole animation is that the three strips of numbers can be moved up and down within their masks to show different numbers from 0 to 59 for seconds and minutes, and 0 to 23 for hours (for a 24-hour format).
We can do this by changing the translateY transition function in CSS for the individual strips of numbers from 0 to -100%. This is because 100% on the y-axis represents the height of the whole strip. A value of 0% will show the first number, and 100% will show the last number of the current strip.
Previously, our animation was based on rotating a hand from 0 to 360 degrees. We now have a different animation that moves the number strips from 0 to -100% on the y-axis:
@keyframes tick { from { transform: translateY(0); } to { transform: translateY(-100%); } }
Applying this animation to the seconds number strip can be done the same way as the analog clock. The only difference is the selector and the name of the animation that’s referenced:
.second > ul { animation: tick steps(60) var(--minute) infinite; }
With the step(60) setting, the animation ticks between numbers like we did for the second hand on the analog clock. We could change this to ease and then the numbers would smoothly slide up and down as if they were on a ribbon of paper.
Assigning the new tick animation to the minute and hour sections is just as straightforward:
.minute > ul { animation: tick steps(60) var(--hour) infinite; animation-delay: calc(var(--timeShiftMinute) * -1); } .hour > ul { animation: tick steps(24) calc(24 * var(--hour)) infinite; animation-delay: calc(var(--timeShiftHour) * -1); }
Again, the declarations are very similar, what’s different this time is the selector, the timing function, and the animation name.
The clock now ticks and keeps the correct time:
One more detail: The blinking colon (:)
Again we could stop here and call it a day. But there’s one last thing we can do to make our digital clock a little more realistic: make the colon separator between the minutes and seconds blink as each second passes.
We could add these colons in the HTML but they are not part of the content. We want them to be an enhancement to the appearance and style of the clock, so CSS is the right place to store this content. That’s what the content property is for and we can use it on the ::after pseudo-elements for the minutes and hours:
.minute::after, .hour::after { content: ":"; margin-left: 2px; position: absolute; top: 6px; left: 44px; font-size: 24px; }
That was easy! But how can we make the seconds colon blink too? We want it animated so we need to define a new animation? There are many ways to achieve this but I thought we should change the content property this time to demonstrate that, quite unexpectedly, it is possible to change its value during an animation:
@keyframes blink { from { content: ":"; } to { content: ""; } }
Animating the content property is not going to work in every browser, so you could just change that to opacity or visibility as a safe option…
The final step is to assign the blink animation to the pseudo-element of the minute section:
.minute::after { animation: blink var(--second) infinite; }
And with that, we are all done! The digital clock keeps the time accurately and we even managed to add a blinking separator between the numbers.
Book: All you need is HTML and CSS
This is just one example project from my new book, All you need is HTML and CSS. It’s available on Amazon in both the U.S and U.K.
If you are just getting started with web development, the ideas in the book will help you level up and build interactive, animated web interfaces without touching any JavaScript.
If you are a seasoned JavaScript developer, the book is a good reminder that many things can be built with HTML and CSS alone, especially now that we have a lot more powerful CSS tools and features, like the ones we covered in this article. There are many examples in the book pushing the limits including interactive carousels, accordions, calculating, counting, advanced input validation, state management, dismissible modal windows, and reacting to mouse and keyboard inputs. There’s even a fully working star rating widget and a shopping basket.
Thanks for spending the time to build clocks with me!
以上是當然,我們可以製作一個僅CSS的時鐘來告訴當前時間!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

文章討論了CSS FlexBox,這是一種佈局方法,用於有效地對齊和分佈響應設計中的空間。它說明了FlexBox用法,將其與CSS網格進行了比較,並詳細瀏覽了瀏覽器支持。

本文討論了使用CSS創建響應網站的技術,包括視口元標籤,靈活的網格,流體媒體,媒體查詢和相對單元。它還涵蓋了使用CSS網格和Flexbox一起使用,並推薦CSS框架

本文討論了CSS盒裝屬性,該屬性控制了元素維度的計算方式。它解釋了諸如Content-Box,Border-Box和Padding-Box之類的值,以及它們對佈局設計和形式對齊的影響。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver CS6
視覺化網頁開發工具