Count-Up

Count Up Section with Progress bar

16 Jan 2023

Count Up Section with Progress bar
HTML
SCSS
PostCSS
JS
                            <section class="stats-section progress-animation">
  <div class="cont">
    <div class="stats-section__wrap">
      <h2>Key Stats</h2>
      <div class="stats-list">
        <div class="stats-list__item scroll-target">
          <div class="count-elem">
            <span class="count-up">90</span><span>%</span>
          </div>
          <div data-progress="90" class="progress-elem scroll-decor"></div>
          <div class="text-content">
            <p>
              of companies that utilize our services have become repeat
              customers
            </p>
          </div>
        </div>

        <div class="stats-list__item scroll-target">
          <div class="count-elem">
            <span data-intersection class="count-up">96</span><span>%</span>
          </div>
          <div data-progress="96" class="progress-elem scroll-decor"></div>
          <div class="text-content">
            <p>
              of shortlisted candidates were not actively pursuing other
              opportunities
            </p>
          </div>
        </div>

        <div class="stats-list__item scroll-target">
          <div class="count-elem">
            <span class="count-up">89</span><span>%</span>
          </div>
          <div data-progress="89" class="progress-elem scroll-decor"></div>
          <div class="text-content">
            <p>
              completion rate across ‘retained’ and ‘exclusive contingent
              search’ assignments
            </p>
          </div>
        </div>

        <div class="stats-list__item scroll-target">
          <div class="count-elem">
            <span class="count-up">93</span><span>%</span>
          </div>
          <div data-progress="93" class="progress-elem scroll-decor"></div>
          <div class="text-content">
            <p>
              of our introductions were still employed by our clients after 12
              months
            </p>
          </div>
        </div>

        <div class="stats-list__item scroll-target">
          <div class="count-elem">
            <span class="count-up">100</span><span>%</span>
          </div>
          <div data-progress="100" class="progress-elem scroll-decor"></div>
          <div class="text-content">
            <p>of our team focus on the U.S. construction market</p>
          </div>
        </div>

        <div class="stats-list__item scroll-target">
          <div class="count-elem">
            <span class="count-up">9000</span><span>+</span>
          </div>
          <div data-progress="9000" class="progress-elem"></div>
          <div class="text-content">
            <p>
              candidates spoken to across the U.S. construction sector with key
              data profiling accrued, ranging from key motivators to project
              completion dates
            </p>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>
                        
                            .stats-section {
  padding-top: ac(90px, 50px);
  padding-bottom: ac(50px, 40px);
  position: relative;
  z-index: 1;

  &__wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    position: relative;
    z-index: 1;
  }

  .title {
    text-align: center;
    max-width: 430px;
    padding-bottom: 30px;
  }

  .stats-list {
    padding-top: ac(50px, 40px);
    width: 100%;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-column-gap: ac(40px, 20px);
    grid-row-gap: ac(49px, 30px);

    &__item {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      justify-content: flex-start;

      .count-elem {
        font-family: var(--font-main);
        font-size: ac(50px, 24px);
        line-height: 116%;
        font-weight: 400;
        padding-bottom: 14px;
      }

      .progress-elem {
        background: blue;
        height: ac(20px, 15px);
        width: 100%;
        position: relative;
        border-radius: 5px;
        overflow: hidden;

        --widthBar: 0%;

        &:before {
          content: "";
          position: absolute;
          left: 0;
          top: 0;
          height: 100%;
          background: yellow;
          width: 0;
          transition: width 2s ease;
        }

        &.animation {
          &:before {
            width: var(--widthBar);
          }
        }
      }

      .text-content {
        padding-top: 24px;
        max-width: 80%;

        @include media(901) {
          max-width: 95%;
        }
      }

      &:nth-child(2) {
        .text-content {
          max-width: 80%;
        }
      }
    }

    @include media(651) {
      grid-template-columns: repeat(2, 1fr);
    }

    @include max-xs {
      grid-template-columns: repeat(1, 1fr);
    }
  }
}
                        
                            .stats-section {
  padding-top: ac(90px, 50px);
  padding-bottom: ac(50px, 40px);
  position: relative;
  z-index: 1;

  &__wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    position: relative;
    z-index: 1;
  }

  .title {
    text-align: center;
    max-width: 430px;
    padding-bottom: 30px;
  }

  .stats-list {
    padding-top: ac(50px, 40px);
    width: 100%;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-column-gap: ac(40px, 20px);
    grid-row-gap: ac(49px, 30px);

    &__item {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      justify-content: flex-start;
      .count-elem {
        font-family: var(--font-main);
        font-size: ac(50px, 24px);
        line-height: 116%;
        font-weight: 400;
        padding-bottom: 14px;
      }

      .progress-elem {
        background: blue;
        height: ac(20px, 15px);
        width: 100%;
        position: relative;
        border-radius: 5px;
        overflow: hidden;

        --widthBar: 0%;

        &:before {
          content: "";
          position: absolute;
          left: 0;
          top: 0;
          height: 100%;
          background: yellow;
          width: 0;
          transition: width 2s ease;
        }

        &.animation {
          &:before {
            width: var(--widthBar);
          }
        }
      }

      .text-content {
        padding-top: 24px;
        max-width: 80%;

        @mixin tab-md {
          max-width: 95%;
        }
      }

      &:nth-child(2) {
        .text-content {
          max-width: 80%;
        }
      }
    }

    @mixin mob-xl {
      grid-template-columns: repeat(2, 1fr);
    }

    @mixin mob-sm {
      grid-template-columns: repeat(1, 1fr);
    }
  }
}
                        
                            /* Треба замінити в meta-settings.js функцію для countUp */

export const counts = Array.from(document.getElementsByClassName("count-up"));
export const countUpArr = [];
if (counts) {
  const defaultOptions = {
    // separator: "",
    enableScrollSpy: false,
    scrollSpyRunOnce: true,
  };

  let idNumber = 1;

  counts.forEach((count) => {
    const id = `count-up-${idNumber}`,
      value = parseFloat(count.innerHTML);

    let optionsFromDataAttr = $(count).data();

    for (const key in optionsFromDataAttr) {
      if (optionsFromDataAttr[key] === "") {
        optionsFromDataAttr[key] = true;
      }
    }

    count.id = id;
    countUpArr.push(
      new CountUp(
          id,
          value,
          Object.assign(Object.assign({}, defaultOptions), optionsFromDataAttr)
      )
    );
    idNumber++;
  });
}

/* Те що в main.js */

import countUpArr from "./meta-settings.js";

const scrollEvents = () => {
  const trueScrollTarget = document.getElementsByClassName("scroll-target")[0];

  if (trueScrollTarget) {
    const scrollTarget = document.querySelectorAll(".scroll-target");
    let progressArr = [];

    if (document.getElementsByClassName("progress-animation")[0]) {
      progressArr = document.querySelectorAll(
        ".progress-animation .stats-list__item"
      );
    }

    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry, i) => {
          if (entry.isIntersecting) {
            if (
              progressArr.length !== 0 &&
              entry.target.classList.contains("stats-list__item")
            ) {
              const elem = entry.target;
              countUpArr.forEach((countUp) => {
                if (countUp.el.id === elem.querySelector(".count-up").id) {
                  countUp.start();
                  const progressElem = elem.querySelector(".progress-elem");
                  const progress = Number(progressElem.dataset.progress);
                  progressElem.classList.add("animation");
                  progressElem.style.setProperty(
                    "--widthBar",
                    `${Math.min(progress, 100)}%`
                  );
                }
              });
            }
          }
        });
      },
      {
        threshold: 0,
        rootMargin: "0% 0% -10% 0%",
      }
    );

    scrollTarget.forEach((target, index) => {
      observer.observe(target);
    });
  }
};

scrollEvents();
                        

Секція для статистики, де Count Up був зв'язаний з прогрес баром. Виклик йде при скролі до кожного елементу. Для роботи треба замінити стандартну функцію meta-setings.js та імпортувати в main.js countUpArr.

Працює за допомогою IntersectionObserver.

Якщо значення елементу більше за 100, то буде прирівнюватись до 100. Через те, що при завантаженні сторінки count-up елементи мають значення 0, то треба в data атрибут для progress-elem (data-progress="90") ввести значення як для count-up.

Заповнення прогрес бару та count-up синхронізовано.

0