{ CF }

15 CSS Number Counter Animations

Reading Progress Counter

Warm parchment card with a thin red progress bar across the top — like a real reading indicator. Lora serif counts up words read, with pages, hours, and minutes remaining below. Twenty chapter dots show exact position. Built for e-reader apps and Substack-style blogs.

CSS + JS MIT licensed

Reading Progress Counter the 7th of 15 designs in the 15 CSS Number Counter Animations collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.

Live preview

Open in playground

The code

<div class="cnc-read">
  <div class="cnc-read-card">
    <div class="cnc-read-progress-bar"><div class="cnc-read-progress-fill"></div></div>
    <div class="cnc-read-body">
      <div class="cnc-read-chapter">Chapter 14 · The Inheritance</div>
      <div class="cnc-read-count-row">
        <span class="cnc-read-big" data-words data-target="74320">0</span>
        <span class="cnc-read-count-unit">words</span>
      </div>
      <div class="cnc-read-sublabel">read so far · <span data-pct data-target="73">0</span>% of this book</div>
      <hr class="cnc-read-rule">
      <div class="cnc-read-stats">
        <div class="cnc-read-stat"><div class="cnc-read-stat-n" data-pages data-target="248">0</div><div class="cnc-read-stat-l">Pages</div></div>
        <div class="cnc-read-stat"><div class="cnc-read-stat-n" data-hrs data-target="4.9">0</div><div class="cnc-read-stat-l">Hours</div></div>
        <div class="cnc-read-stat"><div class="cnc-read-stat-n" data-left data-target="162">0</div><div class="cnc-read-stat-l">Min left</div></div>
      </div>
      <div class="cnc-read-dots">
        <div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div>
        <div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div>
        <div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div>
        <div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div>
        <div class="cnc-read-dot cnc-read-dot-read"></div><div class="cnc-read-dot cnc-read-dot-read"></div>
        <div class="cnc-read-dot cnc-read-dot-current"></div>
        <div class="cnc-read-dot"></div><div class="cnc-read-dot"></div><div class="cnc-read-dot"></div>
        <div class="cnc-read-dot"></div><div class="cnc-read-dot"></div>
      </div>
    </div>
  </div>
</div>
.cnc-read { display: grid; place-items: center; padding: 32px 16px; background: #f0ebe3; font-family: 'Karla', sans-serif; }
.cnc-read *, .cnc-read *::before, .cnc-read *::after { box-sizing: border-box; }
.cnc-read-card { width: 340px; background: #faf8f4; border-radius: 4px; box-shadow: 0 2px 0 #d6cfc4, 0 12px 48px rgba(0,0,0,0.06); overflow: hidden; position: relative; }
.cnc-read-progress-bar { height: 3px; background: #e8e0d4; position: relative; }
.cnc-read-progress-fill { height: 100%; background: #c1440e; transform: scaleX(0); transform-origin: left; animation: cnc-read-progFill 2.4s cubic-bezier(0.22,1,0.36,1) 0.5s forwards; width: 73%; }
@keyframes cnc-read-progFill { to { transform: scaleX(1); } }
.cnc-read-body { padding: 40px 36px 36px; }
.cnc-read-chapter { font-size: 9px; letter-spacing: 4px; text-transform: uppercase; color: #b8a898; margin-bottom: 20px; font-weight: 400; opacity: 0; animation: cnc-read-fadeUp 0.5s ease 0.1s forwards; }
.cnc-read-count-row { display: flex; align-items: baseline; gap: 10px; margin-bottom: 4px; opacity: 0; animation: cnc-read-fadeUp 0.7s cubic-bezier(0.22,1,0.36,1) 0.2s forwards; }
.cnc-read-big { font-family: 'Lora', serif; font-size: 80px; font-weight: 600; color: #1a1410; line-height: 1; letter-spacing: -3px; }
.cnc-read-count-unit { font-family: 'Lora', serif; font-style: italic; font-size: 18px; color: #a09080; font-weight: 400; letter-spacing: 0; padding-bottom: 6px; }
.cnc-read-sublabel { font-size: 11px; color: #b8a898; font-weight: 300; letter-spacing: 0.3px; margin-bottom: 32px; opacity: 0; animation: cnc-read-fadeUp 0.5s ease 0.3s forwards; }
.cnc-read-rule { border: none; border-top: 1px solid #ede7de; margin-bottom: 24px; opacity: 0; animation: cnc-read-fadeUp 0.4s ease 0.35s forwards; }
.cnc-read-stats { display: flex; gap: 0; opacity: 0; animation: cnc-read-fadeUp 0.5s ease 0.4s forwards; }
.cnc-read-stat { flex: 1; border-right: 1px solid #ede7de; padding-right: 16px; margin-right: 16px; }
.cnc-read-stat:last-child { border-right: none; padding-right: 0; margin-right: 0; }
.cnc-read-stat-n { font-family: 'Lora', serif; font-size: 24px; font-weight: 600; color: #1a1410; letter-spacing: -0.5px; line-height: 1; }
.cnc-read-stat-l { font-size: 9px; letter-spacing: 2px; text-transform: uppercase; color: #b8a898; margin-top: 5px; font-weight: 300; }
.cnc-read-dots { display: flex; gap: 4px; margin-top: 28px; opacity: 0; animation: cnc-read-fadeUp 0.4s ease 0.5s forwards; flex-wrap: wrap; }
.cnc-read-dot { width: 6px; height: 6px; border-radius: 50%; background: #e0d8ce; }
.cnc-read-dot-read { background: #c1440e; }
.cnc-read-dot-current { background: #c1440e; box-shadow: 0 0 0 2px #faf8f4, 0 0 0 3px #c1440e; }
@keyframes cnc-read-fadeUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
  .cnc-read-progress-fill, .cnc-read-chapter, .cnc-read-count-row, .cnc-read-sublabel, .cnc-read-rule, .cnc-read-stats, .cnc-read-dots { animation: none; opacity: 1; transform: none; }
  .cnc-read-progress-fill { transform: scaleX(0.73); }
}
(function () {
  var root = document.querySelector('.cnc-read');
  if (!root) return;
  function ease(t) { return 1 - Math.pow(1 - t, 3); }
  setTimeout(function () {
    var start = performance.now(), dur = 2000;
    var wordsEl = root.querySelector('[data-words]');
    var pctEl   = root.querySelector('[data-pct]');
    var pagesEl = root.querySelector('[data-pages]');
    var hrsEl   = root.querySelector('[data-hrs]');
    var leftEl  = root.querySelector('[data-left]');
    var wordsTarget = parseFloat(wordsEl.dataset.target);
    var pctTarget   = parseFloat(pctEl.dataset.target);
    var pagesTarget = parseFloat(pagesEl.dataset.target);
    var hrsTarget   = parseFloat(hrsEl.dataset.target);
    var leftTarget  = parseFloat(leftEl.dataset.target);
    function tick(now) {
      var t = Math.min((now - start) / dur, 1), e = ease(t);
      wordsEl.textContent = Math.round(e * wordsTarget).toLocaleString();
      pctEl.textContent   = Math.round(e * pctTarget);
      pagesEl.textContent = Math.round(e * pagesTarget);
      hrsEl.textContent   = (e * hrsTarget).toFixed(1);
      leftEl.textContent  = Math.round((1 - e) * leftTarget);
      if (t < 1) requestAnimationFrame(tick);
    }
    requestAnimationFrame(tick);
  }, 400);
})();

Search CodeFronts

Loading…