Time told with circular progress bars. Hover or focus on the date, hour, minute, and second to see them individually!
A Pen by Jack-Adam-Mwanzo on CodePen.
| <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> |
Time told with circular progress bars. Hover or focus on the date, hour, minute, and second to see them individually!
A Pen by Jack-Adam-Mwanzo on CodePen.
| 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); | |
| } | |
| } |
| * { | |
| 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; | |
| } | |
| } |