Enter Animation (forwards)

2026-07-18T221512

2026-07-18T214617

1
2
3
4
<h1 class="enter-animation">Aesthetics in Motion</h1>
<p class="enter-animation delay-1">Demonstrating the most recommended, performant ways to build modern web animations.</p>
<p class="enter-animation delay-2">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incid Anim ea incididunt quis incididunt ad nisi laborum duis. Commodo exercitation velit </p>
<div class="box enter-animation delay-3" style="margin-top: 2rem;">Card</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@keyframes slideUpFade {
    from{ opacity: 0; transform: translateY(-10); }
    to {  opacity: 1; transform: translateY(0);   }
}

.enter-animation {
    opacity: 0;
    transform: translateY(40px);
    animation: slideUpFade 3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* Staggered delays for enter animations */
.delay-1 { animation-delay: 0.5s; }
.delay-2 { animation-delay: 1.5s; }
.delay-3 { animation-delay: 2.0s; }

Loop Animation (infinite)

2026-07-18T221626

2026-07-18T215130

1
2
<div class="box loop-anim delay-3" style="margin-top: 2rem;">Looping</div>
<div class="scroll-indicator"></div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@keyframes float {
    0%, 100% { transform: translateY(0);     }
    50%      { transform: translateY(-20px); }
}
@keyframes bounce {
    0%, 20%, 50%, 80%, 100% { transform: translateY(0);     }
    40%                     { transform: translateY(-15px); }
    60%                     { transform: translateY(-7px);  }
}
.loop-anim {
    animation: float 3s ease-in-out infinite;
}
.scroll-indicator {
    animation: bounce 2s infinite;
}

Scroll Animation / View Animation

(animation-timeline:scroll() OR ``animation-timeline:view()`)

2026-07-18T221923

2026-07-18T220323

1
2
<div class="scroll-driven-progress"></div>
<div class="box scroll-driven-box" style="width: 250px; height: 250px; border-radius: 50%;">Linked to Scroll</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* Global progress bar linked to page scroll */
.scroll-driven-progress {
    animation:         scrollProgress linear;
    animation-timeline: scroll();
}

/* Element animation linked to its view timeline */
.scroll-driven-box {
    animation:          rotateFade linear forwards;
    animation-timeline: view();
    animation-range:    entry 10% cover 50%; /* Animates while crossing from 10% to 50% of the viewport */
}

Intersection-Triggered Animation (Intersection Observer)

2026-07-18T221800

2026-07-18T215544

(using JavaScript to toggle classname)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.scroll-trigger-element {
    opacity: 0;
    transform: scale(0.8) translateY(30px);
    transition: all 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.scroll-trigger-element.is-visible {
    opacity: 1;
    transform: scale(1) translateY(0);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const initScrollTriggers = () => {
    // Configuration for the observer
    const options = {
        root: null,       // Defaults to the browser viewport
        rootMargin: '0px',
        threshold: 0.2    // Trigger when 20% of the element is visible
    };

    const observer = new IntersectionObserver((entries, observer) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                // Add class to trigger the CSS transition
                entry.target.classList.add('is-visible');

                // Optional: Stop observing if you only want the animation to happen once
                // observer.unobserve(entry.target);
            } else {
                // Optional: Remove class so it animates again when scrolled back into view
                entry.target.classList.remove('is-visible');
            }
        });
    }, options);

    // Select all elements that need to be animated on scroll
    const elementsToAnimate = document.querySelectorAll('.scroll-trigger-element');

    // Start observing each element
    elementsToAnimate.forEach(el => observer.observe(el));
};

// Initialize when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initScrollTriggers);

If you want the intersection appear to only happen once, uncomment line-16 (OR remove line-19) from the above

1
2
+ observer.unobserve(entry.target);
- entry.target.classList.remove('is-visible');

Reference