如图所示
直接上代码
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./clock.css">
</head>
<body>
<div id="clock" class="progress-clock">
<button class="progress-clock__time-date" data-group="d" type="button">
<small data-unit="w">Sunday</small><br>
<span data-unit="mo">January</span>
<span data-unit="d">1</span>
</button>
<button class="progress-clock__time-digit" data-unit="h" data-group="h" type="button">12</button><span class="progress-clock__time-colon">:</span><button class="progress-clock__time-digit" data-unit="m" data-group="m" type="button">00</button><span class="progress-clock__time-colon">:</span><button class="progress-clock__time-digit" data-unit="s" data-group="s" type="button">00</button>
<span class="progress-clock__time-ampm" data-unit="ap">AM</span>
<svg class="progress-clock__rings" width="256" height="256" viewBox="0 0 256 256">
<defs>
<linearGradient id="pc-red" x1="1" y1="0.5" x2="0" y2="0.5">
<stop offset="0%" stop-color="hsl(343,90%,55%)" />
<stop offset="100%" stop-color="hsl(323,90%,55%)" />
</linearGradient>
<linearGradient id="pc-yellow" x1="1" y1="0.5" x2="0" y2="0.5">
<stop offset="0%" stop-color="hsl(43,90%,55%)" />
<stop offset="100%" stop-color="hsl(23,90%,55%)" />
</linearGradient>
<linearGradient id="pc-blue" x1="1" y1="0.5" x2="0" y2="0.5">
<stop offset="0%" stop-color="hsl(223,90%,55%)" />
<stop offset="100%" stop-color="hsl(203,90%,55%)" />
</linearGradient>
<linearGradient id="pc-purple" x1="1" y1="0.5" x2="0" y2="0.5">
<stop offset="0%" stop-color="hsl(283,90%,55%)" />
<stop offset="100%" stop-color="hsl(263,90%,55%)" />
</linearGradient>
</defs>
<!-- Days of Month -->
<g data-units="d">
<circle class="progress-clock__ring" cx="128" cy="128" r="74" fill="none" opacity="0.1" stroke="url(#pc-red)" stroke-width="12" />
<circle class="progress-clock__ring-fill" data-ring="mo" cx="128" cy="128" r="74" fill="none" stroke="url(#pc-red)" stroke-width="12" stroke-dasharray="465 465" stroke-dashoffset="465" stroke-linecap="round" transform="rotate(-90,128,128)" />
</g>
<!-- Hours of Day -->
<g data-units="h">
<circle class="progress-clock__ring" cx="128" cy="128" r="90" fill="none" opacity="0.1" stroke="url(#pc-yellow)" stroke-width="12" />
<circle class="progress-clock__ring-fill" data-ring="d" cx="128" cy="128" r="90" fill="none" stroke="url(#pc-yellow)" stroke-width="12" stroke-dasharray="565.5 565.5" stroke-dashoffset="565.5" stroke-linecap="round" transform="rotate(-90,128,128)" />
</g>
<!-- Minutes of Hour -->
<g data-units="m">
<circle class="progress-clock__ring" cx="128" cy="128" r="106" fill="none" opacity="0.1" stroke="url(#pc-blue)" stroke-width="12" />
<circle class="progress-clock__ring-fill" data-ring="h" cx="128" cy="128" r="106" fill="none" stroke="url(#pc-blue)" stroke-width="12" stroke-dasharray="666 666" stroke-dashoffset="666" stroke-linecap="round" transform="rotate(-90,128,128)" />
</g>
<!-- Seconds of Minute -->
<g data-units="s">
<circle class="progress-clock__ring" cx="128" cy="128" r="122" fill="none" opacity="0.1" stroke="url(#pc-purple)" stroke-width="12" />
<circle class="progress-clock__ring-fill" data-ring="m" cx="128" cy="128" r="122" fill="none" stroke="url(#pc-purple)" stroke-width="12" stroke-dasharray="766.5 766.5" stroke-dashoffset="766.5" stroke-linecap="round" transform="rotate(-90,128,128)" />
</g>
</svg>
</div>
<script src="./clock.js"></script>
</body>
</html>
CSS
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--hue: 223;
--bg: hsl(var(--hue),10%,90%);
--fg: hsl(var(--hue),10%,10%);
font-size: calc(16px + (24 - 16) * (100vw - 320px) / (1280 - 320));
}
body, button {
color: var(--fg);
font: 1em/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
background-color: var(--bg);
height: 100vh;
display: grid;
place-items: center;
}
.progress-clock {
display: grid;
justify-content: center;
align-content: center;
position: relative;
text-align: center;
width: 16em;
height: 16em;
}
.progress-clock__time-date,
.progress-clock__time-digit,
.progress-clock__time-colon,
.progress-clock__time-ampm {
transition: color 0.2s linear;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.progress-clock__time-date,
.progress-clock__time-digit {
background: transparent;
}
.progress-clock__time-date,
.progress-clock__time-ampm {
grid-column: 1 / 6;
}
.progress-clock__time-date {
font-size: 0.75em;
line-height: 1.33;
}
.progress-clock__time-digit,
.progress-clock__time-colon {
font-size: 2em;
font-weight: 400;
grid-row: 2;
}
.progress-clock__time-colon {
line-height: 1.275;
}
.progress-clock__time-ampm {
cursor: default;
grid-row: 3;
}
.progress-clock__rings {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.progress-clock__ring {
opacity: 0.1;
}
.progress-clock__ring-fill {
transition:
opacity 0s 0.3s linear,
stroke-dashoffset 0.3s ease-in-out;
}
.progress-clock__ring-fill--360 {
opacity: 0;
stroke-dashoffset: 0;
transition-duration: 0.3s;
}
[data-group]:focus {
outline: transparent;
}
[data-units] {
transition: opacity 0.2s linear;
}
[data-group="d"]:focus,
[data-group="d"]:hover {
color: hsl(333,90%,55%);
}
[data-group="h"]:focus,
[data-group="h"]:hover {
color: hsl(33,90%,55%);
}
[data-group="m"]:focus,
[data-group="m"]:hover {
color: hsl(213,90%,55%);
}
[data-group="s"]:focus,
[data-group="s"]:hover {
color: hsl(273,90%,55%);
}
[data-group]:focus ~ .progress-clock__rings [data-units],
[data-group]:hover ~ .progress-clock__rings [data-units] {
opacity: 0.2;
}
[data-group="d"]:focus ~ .progress-clock__rings [data-units="d"],
[data-group="d"]:hover ~ .progress-clock__rings [data-units="d"],
[data-group="h"]:focus ~ .progress-clock__rings [data-units="h"],
[data-group="h"]:hover ~ .progress-clock__rings [data-units="h"],
[data-group="m"]:focus ~ .progress-clock__rings [data-units="m"],
[data-group="m"]:hover ~ .progress-clock__rings [data-units="m"],
[data-group="s"]:focus ~ .progress-clock__rings [data-units="s"],
[data-group="s"]:hover ~ .progress-clock__rings [data-units="s"] {
opacity: 1;
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(var(--hue),10%,10%);
--fg: hsl(var(--hue),10%,90%);
}
.progress-clock__ring {
opacity: 0.2;
}
}
JS
window.addEventListener("DOMContentLoaded",() => {
const clock = new ProgressClock("#clock");
});
class ProgressClock {
constructor(qs) {
this.el = document.querySelector(qs);
this.time = 0;
this.updateTimeout = null;
this.ringTimeouts = [];
this.update();
}
getDayOfWeek(day) {
switch (day) {
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
default:
return "Sunday";
}
}
getMonthInfo(mo,yr) {
switch (mo) {
case 1:
return { name: "February", days: yr % 4 === 0 ? 29 : 28 };
case 2:
return { name: "March", days: 31 };
case 3:
return { name: "April", days: 30 };
case 4:
return { name: "May", days: 31 };
case 5:
return { name: "June", days: 30 };
case 6:
return { name: "July", days: 31 };
case 7:
return { name: "August", days: 31 };
case 8:
return { name: "September", days: 30 };
case 9:
return { name: "October", days: 31 };
case 10:
return { name: "November", days: 30 };
case 11:
return { name: "December", days: 31 };
default:
return { name: "January", days: 31 };
}
}
update() {
this.time = new Date();
if (this.el) {
// date and time
const dayOfWeek = this.time.getDay();
const year = this.time.getFullYear();
const month = this.time.getMonth();
const day = this.time.getDate();
const hr = this.time.getHours();
const min = this.time.getMinutes();
const sec = this.time.getSeconds();
const dayOfWeekName = this.getDayOfWeek(dayOfWeek);
const monthInfo = this.getMonthInfo(month,year);
const m_progress = sec / 60;
const h_progress = (min + m_progress) / 60;
const d_progress = (hr + h_progress) / 24;
const mo_progress = ((day - 1) + d_progress) / monthInfo.days;
const units = [
{
label: "w",
value: dayOfWeekName
},
{
label: "mo",
value: monthInfo.name,
progress: mo_progress
},
{
label: "d",
value: day,
progress: d_progress
},
{
label: "h",
value: hr > 12 ? hr - 12 : hr,
progress: h_progress
},
{
label: "m",
value: min < 10 ? "0" + min : min,
progress: m_progress
},
{
label: "s",
value: sec < 10 ? "0" + sec : sec
},
{
label: "ap",
value: hr > 12 ? "PM" : "AM"
}
];
// flush out the timeouts
this.ringTimeouts.forEach(t => {
clearTimeout(t);
});
this.ringTimeouts = [];
// update the display
units.forEach(u => {
// rings
const ring = this.el.querySelector(`[data-ring="${u.label}"]`);
if (ring) {
const strokeDashArray = ring.getAttribute("stroke-dasharray");
const fill360 = "progress-clock__ring-fill--360";
if (strokeDashArray) {
// calculate the stroke
const circumference = +strokeDashArray.split(" ")[0];
const strokeDashOffsetPct = 1 - u.progress;
ring.setAttribute(
"stroke-dashoffset",
strokeDashOffsetPct * circumference
);
// add the fade-out transition, then remove it
if (strokeDashOffsetPct === 1) {
ring.classList.add(fill360);
this.ringTimeouts.push(
setTimeout(() => {
ring.classList.remove(fill360);
}, 600)
);
}
}
}
// digits
const unit = this.el.querySelector(`[data-unit="${u.label}"]`);
if (unit)
unit.innerText = u.value;
});
}
clearTimeout(this.updateTimeout);
this.updateTimeout = setTimeout(this.update.bind(this),1e3);
}
}