Number Card

0+

Projects Completed

0+

Happy Clients

0+

Awards Won

0+

Cups of Coffee Per Day

@use 'sprucecss/scss/spruce' as *;

.number-card {
  @include layout-stack('xs');
  border-bottom: 1px solid color('border');
  padding-block-end: spacer('l');

  &__data {
    color: color('primary');
    font-family: config('font-family-heading', $typography);
    font-size: responsive-font-size(5rem);
    font-weight: 700;
    line-height: 1;
  }
}
<div class="number-card">
    <p class="number-card__data">
        <span data-action="countup" data-target="325">0</span>+
    </p>
    <p class="number-card__caption">Projects Completed</p>
</div>
<div class="number-card">
    <p class="number-card__data">
        <span data-action="countup" data-target="150">0</span>+
    </p>
    <p class="number-card__caption">Happy Clients</p>
</div>
<div class="number-card">
    <p class="number-card__data">
        <span data-action="countup" data-target="25">0</span>+
    </p>
    <p class="number-card__caption">Awards Won</p>
</div>
<div class="number-card">
    <p class="number-card__data">
        <span data-action="countup" data-target="100">0</span>+
    </p>
    <p class="number-card__caption">Cups of Coffee Per Day</p>
</div>
(() => {
  const counters = document.querySelectorAll('[data-action="countup"]');
  const duration = 3500;

  const countUp = (element, target) => {
    let current = 0;
    let startTime = null;

    const updateCounter = (timestamp) => {
      if (!startTime) startTime = timestamp;
      const progress = timestamp - startTime;
      const easing = 1 - (1 - progress / duration) ** 3;
      current = Math.min(target, target * easing);

      element.textContent = Math.floor(current);

      if (progress < duration) {
        requestAnimationFrame(updateCounter);
      } else {
        element.textContent = target;
      }
    };

    requestAnimationFrame(updateCounter);
  };

  const observerInit = new IntersectionObserver((entries, observer) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const counter = entry.target;
        const target = Number(counter.getAttribute('data-target'));
        countUp(counter, target);
        observer.unobserve(counter);
      }
    });
  }, { threshold: 0.5 });

  counters.forEach((counter) => observerInit.observe(counter));
})();

Number Card

On this page

A JavaScript-based count-up number card where you can easily highlight your or your company's accomplishments.

Technical Details

  • Our count-up number card utilizes straightforward JavaScript code that includes an observer. This observer detects when the items are in the viewport.
  • Modify the duration in the JS file to configure the animation duration.

Documentation

Learn about Spruce CSS through our extensive documentation.

Components

Explore our extensive UI library built with Spruce CSS.

Blog

Read about front-end development and concepts of Spruce CSS.

Find us on GitHub

Did you find a bug? Have an idea or a question? Please open an issue to help us develop the project.