Nahorniak Templates
База референсів шаблонів і компонентів
Назад до шаблону · Consulo Creative Business Consulting Template
c68

why-choose-us-counters

Статистика·Шаблон: Consulo Creative Business Consulting Template·Складність анімації: medium·Адаптивний: Так
why-choose-us-counters

Файли-джерела

  • index.htmlmain .promotion.mt-100

Бібліотеки

aosbootstrap

Summary

Dark "Why Choose Us" promotion band combining a 7/12 narrative column (eyebrow + headline + paragraph + CTA) with a <counter-up> stats strip animating from 0 to target on intersection. The whole section binds --color-background to graphite so all child text inverts to white.

HTML structure (minimal)

<div class="promotion mt-100 section-padding">
  <div class="container">
    <div class="promotion-container">
      <div class="row">
        <div class="col-lg-7 col-12">
          <div class="promtion-content section-headings">
            <div class="subheading text-20 subheading-bg" data-aos="fade-right"><span>Why Choose Us</span></div>
            <h2 class="heading text-50" data-aos="fade-right">Smart strategies for sustainable business growth</h2>
            <div class="text text-18" data-aos="fade-right">Description paragraph…</div>
            <a href="about.html" class="button button--secondary">Learn More</a>
          </div>
        </div>
        <div class="col-lg-5 col-12"><!-- imagery / illustration --></div>
      </div>

      <counter-up class="counter-list">
        <div class="counter-item" data-aos="fade-up">
          <h2 class="heading text-50" data-target="500">0<span>+</span></h2>
          <div class="text text-18 fw-500">Happy clients</div>
        </div>
        <div class="counter-item" data-aos="fade-up" data-aos-delay="100">
          <h2 class="heading text-50" data-target="120">0<span>+</span></h2>
          <div class="text text-18 fw-500">Projects done</div>
        </div>
        <div class="counter-item" data-aos="fade-up" data-aos-delay="200">
          <h2 class="heading text-50" data-target="25">0<span>+</span></h2>
          <div class="text text-18 fw-500">Years experiences</div>
        </div>
      </counter-up>
    </div>
  </div>
</div>

Key SCSS tokens

.promotion {
  --color-background: rgba(32,40,45,1);
  --color-foreground: rgba(255,255,255,1);
  --color-foreground-heading: rgba(255,255,255,1);
  --color-foreground-subheading: rgba(255,255,255,1);
}
.promotion-container {
  background-color: var(--color-background);
  border-radius: 18px;
  padding: 80px;
  border: 1px solid var(--color-border);
}
.counter-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  margin-block-start: 60px;
}
.counter-item .heading {
  font-size: 50px;
  font-weight: 700;
}

Animation logic

class CounterUp extends HTMLElement {
  connectedCallback() { this.initObserver(); }
  initObserver() {
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => entry.isIntersecting && this.startCounters());
    }, { threshold: 0.5 });
    this.observer.observe(this);
  }
  startCounters() {
    const count = this.querySelectorAll(".counter-item .heading");
    count.forEach(c => {
      const target = parseInt(c.getAttribute("data-target"), 10);
      const speed = 100;
      let current = 0;
      const update = () => {
        current += target / speed;
        if (current < target) {
          c.childNodes[0].textContent = Math.ceil(current);
          requestAnimationFrame(update);
        } else {
          c.childNodes[0].textContent = target;
        }
      };
      update();
    });
  }
}
customElements.define("counter-up", CounterUp);

Notable details

  • Counter only fires when 50% of the strip is visible (threshold: 0.5) — counting doesn't race ahead while the band is still below the fold.
  • The increment formula target / 100 means small numbers (25 years) and big numbers (500 clients) all settle in roughly the same number of frames — the speed feels uniform.
  • c.childNodes[0] updates only the text node, leaving the <span>+</span> suffix intact.
  • The whole section's dark theme is achieved by overriding four --color-* custom properties on the .promotion root — no .dark modifier classes anywhere.

Use when

  • B2B / SaaS pages where the hero needs a "trust strip" with animated stats.
  • Sections that should switch to dark mode contextually without a global theme toggle.
  • Counter behavior that must wait until the user reads the section (rather than autoplaying as soon as the page loads).

Caveats

  • The counter restarts on every section re-entry only if you remove this.observer.disconnect() — current code keeps the observer alive but the function will start counters from 0 again on each crossing. Add once logic if undesired.
  • Negative or decimal data-target values aren't handled — parseInt will silently round.