AI CSS Animations

Race Lap Timer with Sectors

A race timing tower: the lap clock runs from 1:18 to 1:23.46 and stops, sector bars fill and turn purple, green and yellow, and a green -0.21 delta lands. Two registered integers print the time.

Also known as: lap timer css, timer animation css, racing timer text, sector times animation.

HTML

<div class="txt-race-lap-timer" aria-label="Lap 23, 1:23.46, personal best by 0.21 seconds">
  <span class="txt-race-lap-timer-head" aria-hidden="true"><b>LAP 23</b><span class="txt-race-lap-timer-delta">-0.21</span></span>
  <span class="txt-race-lap-timer-time" aria-hidden="true"></span>
  <span class="txt-race-lap-timer-sectors" aria-hidden="true"><i></i><i></i><i></i></span>
</div>

CSS

@property --txt-race-lap-timer-s {
  syntax: "<integer>";
  initial-value: 23;
  inherits: false;
}
@property --txt-race-lap-timer-h {
  syntax: "<integer>";
  initial-value: 46;
  inherits: false;
}
.txt-race-lap-timer {
  --c: var(--txt-color, #3b82f6);
  font-size: calc(var(--txt-size, 32px) * 0.9);
  font-family: system-ui, sans-serif;
  font-weight: 700;
  line-height: 1.2;
  display: inline-grid;
  gap: 0.3em;
  padding: 0.55em 0.75em 0.65em;
  border-radius: 0.5em;
  color: #f5f5f5;
  background: linear-gradient(#17191f, #0e0f13);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.07), 0 0.5em 1.2em -0.6em rgb(0 0 0 / 0.5);
  --t: 8.4s;
  --purple: #b26bff;
  --green: #2fd36b;
  --yellow: #f2c94c;}
.txt-race-lap-timer-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.42em;
  font-weight: 700;
  letter-spacing: 0.1em;
}
.txt-race-lap-timer-head b { opacity: 0.6; font-weight: 700; }
.txt-race-lap-timer-delta {
  padding: 0.15em 0.5em;
  border-radius: 0.3em;
  color: #0b2614;
  background: var(--green);
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  animation: txt-race-lap-timer-delta var(--t) infinite;
}
.txt-race-lap-timer-time {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.45em;
  font-weight: 600;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  counter-reset: txt-race-lap-timer-s var(--txt-race-lap-timer-s) txt-race-lap-timer-h var(--txt-race-lap-timer-h);
  animation:
    txt-race-lap-timer-s var(--t) step-end infinite,
    txt-race-lap-timer-h var(--t) linear infinite,
    txt-race-lap-timer-done var(--t) infinite;
}
.txt-race-lap-timer-time::before {
  content: "1:" counter(txt-race-lap-timer-s) "." counter(txt-race-lap-timer-h, decimal-leading-zero);
}
.txt-race-lap-timer-sectors {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.12em;
}
.txt-race-lap-timer-sectors i {
  position: relative;
  height: 0.2em;
  border-radius: 0.1em;
  background: rgb(255 255 255 / 0.1);
  overflow: hidden;
}
.txt-race-lap-timer-sectors i::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--purple);
  transform-origin: left;
}
.txt-race-lap-timer-sectors i:nth-child(2)::after { animation: txt-race-lap-timer-s2 var(--t) infinite; }
.txt-race-lap-timer-sectors i:nth-child(3)::after { animation: txt-race-lap-timer-s3 var(--t) infinite; }
@keyframes txt-race-lap-timer-s {
  0% { --txt-race-lap-timer-s: 18; }
  11.9% { --txt-race-lap-timer-s: 19; }
  23.81% { --txt-race-lap-timer-s: 20; }
  35.71% { --txt-race-lap-timer-s: 21; }
  47.62% { --txt-race-lap-timer-s: 22; }
  59.52% { --txt-race-lap-timer-s: 23; }
  100% { --txt-race-lap-timer-s: 23; }
}
@keyframes txt-race-lap-timer-h {
  0% { --txt-race-lap-timer-h: 0; }
  11.79% { --txt-race-lap-timer-h: 99; }
  11.9% { --txt-race-lap-timer-h: 0; }
  23.69% { --txt-race-lap-timer-h: 99; }
  23.81% { --txt-race-lap-timer-h: 0; }
  35.6% { --txt-race-lap-timer-h: 99; }
  35.71% { --txt-race-lap-timer-h: 0; }
  47.5% { --txt-race-lap-timer-h: 99; }
  47.62% { --txt-race-lap-timer-h: 0; }
  59.4% { --txt-race-lap-timer-h: 99; }
  59.52% { --txt-race-lap-timer-h: 0; }
  65%, 100% { --txt-race-lap-timer-h: 46; }
}
@keyframes txt-race-lap-timer-done {
  0%, 65% { color: #f5f5f5; }
  66.19% { color: var(--green); }
  79.29%, 100% { color: #f5f5f5; }
}
@keyframes txt-race-lap-timer-s2 {
  0% { transform: scaleX(0); background: rgb(255 255 255 / 0.55); }
  32.14% { transform: scaleX(1); background: rgb(255 255 255 / 0.55); }
  34.52%, 100% { transform: scaleX(1); background: var(--green); }
}
@keyframes txt-race-lap-timer-s3 {
  0%, 32.14% { transform: scaleX(0); background: rgb(255 255 255 / 0.55); }
  65% { transform: scaleX(1); background: rgb(255 255 255 / 0.55); }
  67.38%, 100% { transform: scaleX(1); background: var(--yellow); }
}
@keyframes txt-race-lap-timer-delta {
  0%, 65% { opacity: 0; transform: translateX(0.4em); }
  69.17%, 96.43% { opacity: 1; transform: none; }
  100% { opacity: 0; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .txt-race-lap-timer, .txt-race-lap-timer *, .txt-race-lap-timer *::before, .txt-race-lap-timer *::after { animation: none !important; }
  .txt-race-lap-timer-sectors i:nth-child(2)::after { background: var(--green); }
  .txt-race-lap-timer-sectors i:nth-child(3)::after { background: var(--yellow); }
}

@media (prefers-reduced-motion: reduce) {
  .txt-race-lap-timer, .txt-race-lap-timer *, .txt-race-lap-timer::before, .txt-race-lap-timer::after, .txt-race-lap-timer *::before, .txt-race-lap-timer *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Customizing this snippet

  • Color: set --txt-color on the element or a parent. Currently previewing #3b82f6.
  • Font size: set --txt-size. Every other dimension is in em, so the whole thing scales together. Currently previewing 40px.
  • Speed: change the duration on the transition or animation shorthand. Staggered delays are relative, so they keep their rhythm.
  • Reduced motion: the rule at the end of the CSS turns the motion off for people who asked their OS for less of it. Keep it.

Similar text animations