<div class="container">
<div class="content">
<p class="year">0</p>
<p class="message">
<span>H</span>
<span>A</span>
<span>P</span>
<span>P</span>
<span>Y</span>
<span> </span>
<span>N</span>
<span>E</span>
<span>W</span>
<span> </span>
<span>Y</span>
<span>E</span>
<span>A</span>
<span>R</span>
</p>
</div>
</div>
:root {
--ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.container {
width: 100%;
min-height: 100vh;
padding: 30px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #333;
font-smoothing: antialiased;
}
.year {
font-size: 36px;
text-align: center;
font-family: 'Roboto Mono', monospace;
}
.message {
font-size: 22px;
margin-top: 24px;
position: absolute;
width: 100%;
left: 0;
display: flex;
justify-content: center;
font-family: 'Poppins', sans-serif;
span {
opacity: 0;
transform: translateY(18px);
}
&.is-active {
span {
opacity: 1;
transform: translateY(0);
transition: opacity 200ms linear, transform 640ms var(--ease-out-back);
@for $i from 0 through 15 {
&:nth-child(#{$i}) {
transition-delay: #{500 + $i * 40}ms;
}
}
}
}
}
View Compiled
type Easing = (t: number) => number
type TweenOptions = {
from: number
to: number
duration: number
easing: Easing
onUpdate(value: number): void
}
function clamp(num: number, min: number, max: number) {
return Math.min(Math.max(num, min), max)
}
function tween({ from, to, duration, easing, onUpdate }: TweenOptions): Promise<void> {
return new Promise((resolve) => {
const startTime = performance.now()
const tick = () => {
const elapsedTime = performance.now() - startTime
const progress = clamp(elapsedTime / duration, 0, 1)
const value = from + (to - from) * easing(progress)
onUpdate(value)
if (progress === 1) {
resolve()
} else {
requestAnimationFrame(tick)
}
}
onUpdate(from)
requestAnimationFrame(tick)
})
}
const easeOutQuart = (t: number) => 1 - Math.pow(1 - t, 4)
async function countUp() {
const year = document.querySelector<HTMLElement>('.year')
const message = document.querySelector<HTMLElement>('.message')
if (year) {
await tween({
from: 0,
to: 2022,
duration: 3000,
easing: easeOutQuart,
onUpdate: val => {
year.textContent = Math.floor(val).toString()
}
})
message.classList.add('is-active')
}
}
countUp()
View Compiled
This Pen doesn't use any external JavaScript resources.