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 синхронізовано.