带运动轨迹的简约时钟

156 阅读5分钟

这是我参与11月更文挑战的第7天,活动详情查看:11月更文挑战

1.gif

<main>
	<div class="clock" role="img" aria-label="Analog clock with the second hand showing a motion trail">
		<div class="clock__ticks">
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
			<div class="clock__tick"></div>
		</div>
		<div class="clock__hands">
			<div class="clock__hand clock__hand--hr"></div>
			<div class="clock__hand clock__hand--min"></div>
			<div class="clock__hand clock__hand--sec">
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
				<div class="clock__hand-trail"></div>
			</div>
		</div>
	</div>
</main>
* {
  border: 0;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --bg: #c7cad1;
  --fg: #17181c;
  --primary: #255ff4;
  font-size: calc(16px + (24 - 16) * (100vw - 320px) / (1280 - 320));
}

body {
  background-color: #1F1F1F;
  color: var(--fg);
  font: 1em/1.5 sans-serif;
  height: 100vh;
  display: grid;
  place-items: center;
}

main {
  padding: 1.5em 0;
}

.clock {
  background: linear-gradient(#e3e4e8, #c7cad1);
  border-radius: 50%;
  box-shadow: 0 0.125em 0.25em rgba(255, 255, 255, 0.3) inset, 0 -0.125em 0.25em rgba(0, 0, 0, 0.3) inset, 0 0 0.75em rgba(0, 0, 0, 0.25);
  position: relative;
  width: 15em;
  height: 15em;
}
.clock__ticks, .clock__tick, .clock__hands, .clock__hand, .clock__hand-trail {
  position: absolute;
}
.clock__ticks {
  top: 0.5em;
  left: 0.5em;
  width: 14em;
  height: 14em;
}
.clock__tick, .clock__hand {
  background: currentColor;
}
.clock__tick {
  box-shadow: 0 0 0.75em rgba(0, 0, 0, 0.3);
  bottom: 50%;
  left: calc(50% - 0.05em);
  width: 0.1em;
  height: 0.5em;
  transform-origin: 50% 100%;
}
.clock__tick:nth-child(5n+1) {
  left: calc(50% - 0.1em);
  width: 0.2em;
  height: 1em;
}
.clock__tick:nth-child(1) {
  transform: rotate(0deg) translateY(-6em);
}
.clock__tick:nth-child(2) {
  transform: rotate(6deg) translateY(-6.5em);
}
.clock__tick:nth-child(3) {
  transform: rotate(12deg) translateY(-6.5em);
}
.clock__tick:nth-child(4) {
  transform: rotate(18deg) translateY(-6.5em);
}
.clock__tick:nth-child(5) {
  transform: rotate(24deg) translateY(-6.5em);
}
.clock__tick:nth-child(6) {
  transform: rotate(30deg) translateY(-6em);
}
.clock__tick:nth-child(7) {
  transform: rotate(36deg) translateY(-6.5em);
}
.clock__tick:nth-child(8) {
  transform: rotate(42deg) translateY(-6.5em);
}
.clock__tick:nth-child(9) {
  transform: rotate(48deg) translateY(-6.5em);
}
.clock__tick:nth-child(10) {
  transform: rotate(54deg) translateY(-6.5em);
}
.clock__tick:nth-child(11) {
  transform: rotate(60deg) translateY(-6em);
}
.clock__tick:nth-child(12) {
  transform: rotate(66deg) translateY(-6.5em);
}
.clock__tick:nth-child(13) {
  transform: rotate(72deg) translateY(-6.5em);
}
.clock__tick:nth-child(14) {
  transform: rotate(78deg) translateY(-6.5em);
}
.clock__tick:nth-child(15) {
  transform: rotate(84deg) translateY(-6.5em);
}
.clock__tick:nth-child(16) {
  transform: rotate(90deg) translateY(-6em);
}
.clock__tick:nth-child(17) {
  transform: rotate(96deg) translateY(-6.5em);
}
.clock__tick:nth-child(18) {
  transform: rotate(102deg) translateY(-6.5em);
}
.clock__tick:nth-child(19) {
  transform: rotate(108deg) translateY(-6.5em);
}
.clock__tick:nth-child(20) {
  transform: rotate(114deg) translateY(-6.5em);
}
.clock__tick:nth-child(21) {
  transform: rotate(120deg) translateY(-6em);
}
.clock__tick:nth-child(22) {
  transform: rotate(126deg) translateY(-6.5em);
}
.clock__tick:nth-child(23) {
  transform: rotate(132deg) translateY(-6.5em);
}
.clock__tick:nth-child(24) {
  transform: rotate(138deg) translateY(-6.5em);
}
.clock__tick:nth-child(25) {
  transform: rotate(144deg) translateY(-6.5em);
}
.clock__tick:nth-child(26) {
  transform: rotate(150deg) translateY(-6em);
}
.clock__tick:nth-child(27) {
  transform: rotate(156deg) translateY(-6.5em);
}
.clock__tick:nth-child(28) {
  transform: rotate(162deg) translateY(-6.5em);
}
.clock__tick:nth-child(29) {
  transform: rotate(168deg) translateY(-6.5em);
}
.clock__tick:nth-child(30) {
  transform: rotate(174deg) translateY(-6.5em);
}
.clock__tick:nth-child(31) {
  transform: rotate(180deg) translateY(-6em);
}
.clock__tick:nth-child(32) {
  transform: rotate(186deg) translateY(-6.5em);
}
.clock__tick:nth-child(33) {
  transform: rotate(192deg) translateY(-6.5em);
}
.clock__tick:nth-child(34) {
  transform: rotate(198deg) translateY(-6.5em);
}
.clock__tick:nth-child(35) {
  transform: rotate(204deg) translateY(-6.5em);
}
.clock__tick:nth-child(36) {
  transform: rotate(210deg) translateY(-6em);
}
.clock__tick:nth-child(37) {
  transform: rotate(216deg) translateY(-6.5em);
}
.clock__tick:nth-child(38) {
  transform: rotate(222deg) translateY(-6.5em);
}
.clock__tick:nth-child(39) {
  transform: rotate(228deg) translateY(-6.5em);
}
.clock__tick:nth-child(40) {
  transform: rotate(234deg) translateY(-6.5em);
}
.clock__tick:nth-child(41) {
  transform: rotate(240deg) translateY(-6em);
}
.clock__tick:nth-child(42) {
  transform: rotate(246deg) translateY(-6.5em);
}
.clock__tick:nth-child(43) {
  transform: rotate(252deg) translateY(-6.5em);
}
.clock__tick:nth-child(44) {
  transform: rotate(258deg) translateY(-6.5em);
}
.clock__tick:nth-child(45) {
  transform: rotate(264deg) translateY(-6.5em);
}
.clock__tick:nth-child(46) {
  transform: rotate(270deg) translateY(-6em);
}
.clock__tick:nth-child(47) {
  transform: rotate(276deg) translateY(-6.5em);
}
.clock__tick:nth-child(48) {
  transform: rotate(282deg) translateY(-6.5em);
}
.clock__tick:nth-child(49) {
  transform: rotate(288deg) translateY(-6.5em);
}
.clock__tick:nth-child(50) {
  transform: rotate(294deg) translateY(-6.5em);
}
.clock__tick:nth-child(51) {
  transform: rotate(300deg) translateY(-6em);
}
.clock__tick:nth-child(52) {
  transform: rotate(306deg) translateY(-6.5em);
}
.clock__tick:nth-child(53) {
  transform: rotate(312deg) translateY(-6.5em);
}
.clock__tick:nth-child(54) {
  transform: rotate(318deg) translateY(-6.5em);
}
.clock__tick:nth-child(55) {
  transform: rotate(324deg) translateY(-6.5em);
}
.clock__tick:nth-child(56) {
  transform: rotate(330deg) translateY(-6em);
}
.clock__tick:nth-child(57) {
  transform: rotate(336deg) translateY(-6.5em);
}
.clock__tick:nth-child(58) {
  transform: rotate(342deg) translateY(-6.5em);
}
.clock__tick:nth-child(59) {
  transform: rotate(348deg) translateY(-6.5em);
}
.clock__tick:nth-child(60) {
  transform: rotate(354deg) translateY(-6.5em);
}
.clock__hands {
  top: 1.5em;
  left: 1.5em;
  width: 12em;
  height: 12em;
}
.clock__hand {
  bottom: calc(50% - 1em);
  left: calc(50% - 0.25em);
  width: 0.5em;
  transform-origin: 50% calc(100% - 1em);
}
.clock__hand--hr {
  animation: hr 86400s linear infinite;
  box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.5);
  height: 4.5em;
}
.clock__hand--min {
  animation: min 3600s linear infinite;
  box-shadow: 0 0 0.375em rgba(0, 0, 0, 0.45);
  height: 6.5em;
}
.clock__hand--sec {
  animation: sec 60s cubic-bezier(0.8, 0, 0.2, 1) infinite;
  background: var(--primary);
  box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.4);
  bottom: calc(50% - 1.5em);
  left: calc(50% - 0.125em);
  height: 7.5em;
  width: 0.25em;
  transform-origin: 50% calc(100% - 1.5em);
}
.clock__hand--sec:before {
  background-color: inherit;
  border-radius: 50%;
  content: "";
  display: block;
  position: absolute;
  bottom: 1.125em;
  left: calc(50% - 0.375em);
  width: 0.75em;
  height: 0.75em;
}
.clock__hand-trail {
  animation: secTrail1 1s cubic-bezier(0.8, 0, 0.2, 1) infinite;
  background-color: inherit;
  width: 100%;
  height: 100%;
  transform-origin: inherit;
}
.clock__hand-trail:nth-child(2) {
  animation-name: secTrail2;
}
.clock__hand-trail:nth-child(3) {
  animation-name: secTrail3;
}
.clock__hand-trail:nth-child(4) {
  animation-name: secTrail4;
}
.clock__hand-trail:nth-child(5) {
  animation-name: secTrail5;
}
.clock__hand-trail:nth-child(6) {
  animation-name: secTrail6;
}
.clock__hand-trail:nth-child(7) {
  animation-name: secTrail7;
}
.clock__hand-trail:nth-child(8) {
  animation-name: secTrail8;
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #2e3138;
    --fg: #e3e4e8;
    --primary: #5583f6;
  }

  .clock {
    background: linear-gradient(#2e3138, #17181c);
    box-shadow: 0 0.1em 0.2em rgba(255, 255, 255, 0.1) inset, 0 -0.1em 0.2em rgba(0, 0, 0, 0.1) inset, 0 0 0.4em rgba(0, 0, 0, 0.3);
  }
  .clock__tick {
    box-shadow: 0 0 0.75em rgba(255, 255, 255, 0.3);
  }
}
/* Animations */
@keyframes hr {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(2turn);
  }
}
@keyframes min {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(1turn);
  }
}
@keyframes sec {
  0% {
    transform: rotate(0turn);
  }
  1.6666666667% {
    transform: rotate(0.0166666667turn);
  }
  3.3333333333% {
    transform: rotate(0.0333333333turn);
  }
  5% {
    transform: rotate(0.05turn);
  }
  6.6666666667% {
    transform: rotate(0.0666666667turn);
  }
  8.3333333333% {
    transform: rotate(0.0833333333turn);
  }
  10% {
    transform: rotate(0.1turn);
  }
  11.6666666667% {
    transform: rotate(0.1166666667turn);
  }
  13.3333333333% {
    transform: rotate(0.1333333333turn);
  }
  15% {
    transform: rotate(0.15turn);
  }
  16.6666666667% {
    transform: rotate(0.1666666667turn);
  }
  18.3333333333% {
    transform: rotate(0.1833333333turn);
  }
  20% {
    transform: rotate(0.2turn);
  }
  21.6666666667% {
    transform: rotate(0.2166666667turn);
  }
  23.3333333333% {
    transform: rotate(0.2333333333turn);
  }
  25% {
    transform: rotate(0.25turn);
  }
  26.6666666667% {
    transform: rotate(0.2666666667turn);
  }
  28.3333333333% {
    transform: rotate(0.2833333333turn);
  }
  30% {
    transform: rotate(0.3turn);
  }
  31.6666666667% {
    transform: rotate(0.3166666667turn);
  }
  33.3333333333% {
    transform: rotate(0.3333333333turn);
  }
  35% {
    transform: rotate(0.35turn);
  }
  36.6666666667% {
    transform: rotate(0.3666666667turn);
  }
  38.3333333333% {
    transform: rotate(0.3833333333turn);
  }
  40% {
    transform: rotate(0.4turn);
  }
  41.6666666667% {
    transform: rotate(0.4166666667turn);
  }
  43.3333333333% {
    transform: rotate(0.4333333333turn);
  }
  45% {
    transform: rotate(0.45turn);
  }
  46.6666666667% {
    transform: rotate(0.4666666667turn);
  }
  48.3333333333% {
    transform: rotate(0.4833333333turn);
  }
  50% {
    transform: rotate(0.5turn);
  }
  51.6666666667% {
    transform: rotate(0.5166666667turn);
  }
  53.3333333333% {
    transform: rotate(0.5333333333turn);
  }
  55% {
    transform: rotate(0.55turn);
  }
  56.6666666667% {
    transform: rotate(0.5666666667turn);
  }
  58.3333333333% {
    transform: rotate(0.5833333333turn);
  }
  60% {
    transform: rotate(0.6turn);
  }
  61.6666666667% {
    transform: rotate(0.6166666667turn);
  }
  63.3333333333% {
    transform: rotate(0.6333333333turn);
  }
  65% {
    transform: rotate(0.65turn);
  }
  66.6666666667% {
    transform: rotate(0.6666666667turn);
  }
  68.3333333333% {
    transform: rotate(0.6833333333turn);
  }
  70% {
    transform: rotate(0.7turn);
  }
  71.6666666667% {
    transform: rotate(0.7166666667turn);
  }
  73.3333333333% {
    transform: rotate(0.7333333333turn);
  }
  75% {
    transform: rotate(0.75turn);
  }
  76.6666666667% {
    transform: rotate(0.7666666667turn);
  }
  78.3333333333% {
    transform: rotate(0.7833333333turn);
  }
  80% {
    transform: rotate(0.8turn);
  }
  81.6666666667% {
    transform: rotate(0.8166666667turn);
  }
  83.3333333333% {
    transform: rotate(0.8333333333turn);
  }
  85% {
    transform: rotate(0.85turn);
  }
  86.6666666667% {
    transform: rotate(0.8666666667turn);
  }
  88.3333333333% {
    transform: rotate(0.8833333333turn);
  }
  90% {
    transform: rotate(0.9turn);
  }
  91.6666666667% {
    transform: rotate(0.9166666667turn);
  }
  93.3333333333% {
    transform: rotate(0.9333333333turn);
  }
  95% {
    transform: rotate(0.95turn);
  }
  96.6666666667% {
    transform: rotate(0.9666666667turn);
  }
  98.3333333333% {
    transform: rotate(0.9833333333turn);
  }
  100% {
    transform: rotate(1turn);
  }
}
@keyframes secTrail1 {
  0% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0166666667turn);
  }
}
@keyframes secTrail2 {
  0%, 12.49% {
    opacity: 0;
    transform: rotate(0);
  }
  12.5% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0145833333turn);
  }
}
@keyframes secTrail3 {
  0%, 24.99% {
    opacity: 0;
    transform: rotate(0);
  }
  25% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0125turn);
  }
}
@keyframes secTrail4 {
  0%, 37.49% {
    opacity: 0;
    transform: rotate(0);
  }
  37.5% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0104166667turn);
  }
}
@keyframes secTrail5 {
  0%, 49.99% {
    opacity: 0;
    transform: rotate(0);
  }
  50% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0083333333turn);
  }
}
@keyframes secTrail6 {
  0%, 62.49% {
    opacity: 0;
    transform: rotate(0);
  }
  62.5% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.00625turn);
  }
}
@keyframes secTrail7 {
  0%, 74.99% {
    opacity: 0;
    transform: rotate(0);
  }
  75% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0041666667turn);
  }
}
@keyframes secTrail8 {
  0%, 87.49% {
    opacity: 0;
    transform: rotate(0);
  }
  87.5% {
    opacity: 1;
    transform: rotate(0);
  }
  100% {
    opacity: 0;
    transform: rotate(-0.0020833333turn);
  }
}
window.addEventListener("DOMContentLoaded",() => {
	const clock = new MotionClock(".clock");
});

class MotionClock {
	constructor(qs) {
		const el = document.querySelector(qs);
		const msInSec = 1000;
		const msInMin = msInSec * 60;
		const msInHr = msInMin * 60;
		const msInDay = msInHr * 24;
		const date = new Date();

		let time = date.getHours() * msInHr;
		time += date.getMinutes() * msInMin;
		time += date.getSeconds() * msInSec;
		time += date.getMilliseconds();

		if (el) {
			const handCl = ".clock__hand";
			const hr = el.querySelector(`${handCl}--hr`);
			const min = el.querySelector(`${handCl}--min`);
			const sec = el.querySelector(`${handCl}--sec`);

			if (hr) {
				const hrDelay = (msInDay * ((time % msInDay) / msInDay)) / msInSec;
				hr.style.animationDelay = `${-hrDelay}s`;
			}
			if (min) {
				const minDelay = (msInHr * ((time % msInHr) / msInHr)) / msInSec;
				min.style.animationDelay = `${-minDelay}s`;
			}
			if (sec) {
				const secDelay = (msInMin * ((time % msInMin) / msInMin)) / msInSec;
				sec.style.animationDelay = `${-secDelay}s`;

				const trail = sec.querySelectorAll(`${handCl}-trail`);

				if (trail) {
					const msDelay = (time % msInSec) / msInSec;

					Array.from(trail).forEach(t => {
						t.style.animationDelay = `${-msDelay}s`;
					});
				}
			}
		}
	}
}