24 Scroll Animations You Use Often, Learned by Moving Them Yourself
From fade-ins to scroll spy, we've organized 24 commonly used scroll animations by name and made them interactive right inside the post. You can also view the code.
Contents
When you run into a unique scroll effect on a landing page, it's often hard to search for related info or ask AI about it without knowing its name. So we've sorted 24 scroll animations frequently used in practice by name, and built this post so you can check out and experience each effect directly.
Appearance Effects 01–08
This effect plays once when the element enters the screen. It typically uses IntersectionObserver to detect whether the element is visible and implements the animation with CSS transitions.
01 Fade-in · Scroll-triggered fade-in
When the element enters the screen, it gently rises from the bottom to the top while fading in.
We recommend using IntersectionObserver to detect when an element enters the viewport, adding an is-visible class, and handling the animation with CSS transitions (opacity and transform). This doesn't check the scroll event on every frame, so the performance cost is low, and it works well for many areas such as landing pages and blog cards. However, if a JavaScript error occurs, the element may never appear, so it's more reliable to define the initial hidden styles under a class (.js) that applies only when JS is enabled.
02 Slide-in · Slide-in
The element slides in from outside the left or right edge of the screen.
Apply a different translateX initial value for each direction, then use IntersectionObserver to add a class when the element enters the screen so it returns to its place (transform: none). Alternating left and right placement can add visual rhythm to timelines or feature sections. However, large travel distances can cause a horizontal scrollbar, so setting overflow-x: clip on the parent element is recommended.
03 Stagger · Stagger animation
Instead of appearing all at once, the group of cards shows up one by one in sequence.
Assign each card an index with the CSS variable --i, and set transition-delay: calc(var(--i) * 80ms) to configure the delay. Since a class is added to the parent when the whole grid enters the viewport, the code stays concise. However, with many items the accumulated delay can push back when the last card appears, so it's recommended to adjust the spacing so the total duration is around 1 second, or to set an upper limit.
04 Text Reveal · Text reveal (split text)
Split the title into individual characters so they appear one after another, rising from below.
Using JavaScript, wrap each character of the string in a span tag, then animate them with staggered delays inside a container set to overflow: hidden, moving from translateY(100%) to 0. This technique suits emphasis text in hero sections. However, splitting text causes screen readers to read it character by character, so set the full sentence as aria-label and apply aria-hidden="true" to each span to keep it accessible.
05 Mask Reveal · Clip-path reveal
The image is revealed from one side with clip-path, like a curtain being pulled back.
Apply clip-path: inset(0 100% 0 0) to fully hide the right side, then switch to inset(0 0 0 0) when it enters the screen so it reveals from the left. Because the image itself isn't transformed or moved, layout stability is high, making it well suited for portfolio thumbnails and article cover images. To keep interpolation smooth, note that the start and end values must use the same function shape (such as between inset values, or the same number of polygon vertices).
06 Blur-in Reveal · Blur-in reveal
A blurry element comes into focus and appears sharp.
Start with filter: blur(12px) and opacity 0, then transition to blur(0) and opacity 1 when it enters the screen. This can add a dreamy effect to photos or large typography, and it can also be used for placeholder transitions during lazy image loading. However, blur requires per-pixel re-rendering, so applying it to many large elements may cause performance drops on low-end devices. Please use it carefully, considering the number and size of elements.
07 Typewriter · Typewriter effect
Text appears one character at a time, as if being typed, with a blinking cursor.
We suggest using setTimeout to output the string one character at a time at set intervals, and implementing the cursor blink separately with CSS keyframes. This suits hero sections and terminal-style text effects. If you split Korean text with Array.from, it's handled in complete-character units, which prevents garbling of surrogate-pair characters such as emoji. Also, be sure to clear any previous timers when restarting to avoid overlapping text.
08 Count-up · Count-up animation
When the number enters the screen, it smoothly counts up from 0 to the target value.
Use requestAnimationFrame to calculate the elapsed time ratio (0 to 1) per frame, apply an easing function such as easeOutCubic to get the current value, and then format it with toLocaleString('ko-KR') to add thousand separators. Because it's calculated based on time, it runs at a consistent speed regardless of monitor refresh rate. To prevent layout shifts caused by changes in number width, we recommend applying font-variant-numeric: tabular-nums.
Scroll-linked Effects 09–16
These are scroll-based interaction techniques. They convert the scroll position into a progress value between 0 and 1 and use it to compute size, position, and color. With modern browsers' CSS Scroll-driven Animations (animation-timeline), you can build these without JavaScript.
09 Scroll-linked · Scroll-linked animation
A progress bar at the top fills up as you read.
This syncs the animation progress with the scroll position (0 to 1). In CSS, you can link keyframes to scrolling using animation-timeline: scroll(root), and for browsers that don't support it, compute scaleX from scrollY / (scrollHeight - innerHeight). This suits reading progress indicators and step-display UI. For performance, use the transform property rather than changing width, to avoid layout recalculation.
10 Parallax · Parallax scrolling
Distant layers move slowly and near layers move quickly, creating a sense of depth.
Multiple layers are stacked, and each layer multiplies the scroll progress by a different weight before moving it with translateY. Distant background layers get a low factor to slow them down, while near foreground layers get a high factor to add a sense of speed. This suits hero sections and storytelling pages. However, excessive motion can cause user fatigue, so it's recommended to disable the feature when prefers-reduced-motion is set.
11 Scale on Scroll · スクロールでズーム
An image in a small card grows to fill the screen as you scroll.
Inside a stage pinned with sticky, scale and border-radius are interpolated based on progress. Use transform: scale instead of width/height for the size change, so the layout isn't recalculated and the motion stays smooth. It's used for 'immersive' effects in product introductions or section transitions. Text inside the enlarged element also scales up, so it's better to keep text in a separate layer.
12 Scroll-Scrubbed Image Sequence · スクロール連動画像シーケンス
Sixty pre-drawn frames play a sunrise scene as you move through the scroll position.
Load the frame images in advance (rendering 60 frames directly to a canvas), then compute the frame to show as 'progress × (total frames − 1)' and draw it onto the canvas with drawImage. This responds more instantly than controlling a video's currentTime, and reverse playback is smooth too, which makes it effective for product rotation effects. In practice, more frames mean a heavier image payload, so preloading optimization and resolution control are the keys to implementation.
13 SVG Line Drawing · SVG線画アニメーション
As you scroll, the SVG line continues as if being drawn with a pen.
Get the path's total length with getTotalLength(), put it into stroke-dasharray, and reduce stroke-dashoffset to length × (1 − progress) so the line appears to be drawn. It works well for path, signature, and diagram effects. If you stretch an SVG with a viewBox, length calculations may go off depending on settings like non-scaling-stroke; in that case, using pathLength="1" is simpler.
14 Text Fill on Scroll · スクロールで文字を塗る
Words in a paragraph fill with color one by one, matching the reading pace.
Split the sentence into word-level spans, and add an active class to the leading words according to progress × word count to fill in the color. It's often used on landing pages to let key messages be read slowly. Since splitting words into spans can make screen readers read in fragments, keep the original text in an aria-label as well.
15 Background Color Transition · 背景色の切り替え
As you pass each section, the page background blends into the color of the next section.
Give each section a data-bg color, calculate which two sections the viewport center sits between, and put the linearly interpolated value of those two colors into the body background. Because section boundaries don't cut off sharply, it suits brand stories and portfolios well. When the background gets darker, the text color must change too, or contrast breaks down, so interpolate the foreground color as well.
16 Marquee, Scroll Velocity · スクロール速度連動マーキー
An endless band of text flows faster as you scroll and reverses direction with the scroll direction.
In a requestAnimationFrame loop, move the band by adding the 'scroll delta' to the base speed, and wrap the position with the remainder of dividing by half the band's width so it continues endlessly. When the scroll direction changes, flip the sign so the flow reverses too. If you don't gradually decay the velocity value each frame, it keeps running fast even after stopping, so be careful.
Pinning, Snapping & Navigation 17–24 · 固定・スナップ・ナビゲーション 17–24
These techniques pin part of the screen, move through one screen at a time, or show where you are reading. Many of them can be done with CSS alone, such as position: sticky and scroll-snap.
17 Sticky · position: sticky
A section title stays pinned at the top of the screen while its section passes by.
position: sticky flows like a normal element until it reaches the top value you set, then stays fixed only within its parent block. When the parent ends, the next title pushes out and replaces it, so it fits list headers and table headers well. No JavaScript is needed at all. If an ancestor has overflow: hidden/auto, that element becomes the scroll container and sticky appears not to work, so be careful.
18 Pin + Scrub · ピン留めとスクラブ
The stage stays fixed on screen, and the animation inside plays forward and backward as you scroll.
Placing a position: sticky element inside a tall wrapper (for example, 300vh) pins that area while you pass through it. Use the wrapper's getBoundingClientRect().top value to compute a progress from 0 to 1, then feed it directly into transform and similar properties, and the scrollbar acts like a playback head (scrub). This is the same principle as pin/scrub in GSAP ScrollTrigger. For performance, use requestAnimationFrame on scroll events to consolidate layout reads. If the wrapper is short, the animation may end quickly, so set a height suited to the content steps.
19 Scrollytelling · スクロールテリング
When a text step on the left reaches the middle of the screen, the fixed graphic on the right changes to match that step.
Pin the graphic area with the position: sticky property, and apply an IntersectionObserver to each content step. Setting rootMargin to '-50% 0px -50% 0px' activates only the single step passing through the center of the screen, so state changes are possible without any separate scroll calculations. This works well for layouts that combine explanation and visuals, such as data journalism or product introductions. However, if steps are spaced too closely, states may switch frequently, so give each step enough height.
20 Stacking Cards · カードスタック
Cards stick to the top and pile up neatly, and the cards underneath shrink slightly.
Give each card position: sticky with slightly different top values, and the next card rises over the previous one and stacks. Add depth with JS: calculate how far the next card has approached and reduce the scale of the card beneath. The cards must be siblings inside one parent, and when the parent's height ends, the whole stack scrolls up together. If you wrap each card in its own wrapper, the sticky range shrinks to within the wrapper and the cards won't stack, so be careful.
21 Scroll Snap · CSS scroll snap · CSSスクロールスナップ
One scroll gesture makes the panels stop neatly, one screen at a time.
Set scroll-snap-type: y mandatory on the scroll container and scroll-snap-align: start on each panel, and the browser stops at the nearest panel when scrolling ends. You can build full-page sections and image carousels without JS, and touch and keyboard work as-is. With mandatory, if a panel is taller than the screen, the content inside may be skipped, so consider proximity for long panels. Only the dot indicators use JS, reading the current panel to update them.
22 Horizontal Scroll · 横スクロール
Scrolling vertically moves the track sideways inside a fixed stage.
It has the same structure as pin + scrub. Place a horizontal track inside a sticky stage within a tall wrapper, and multiply the progress p by (track width − screen width) to move it with translateX. If you set the wrapper height to 'horizontal travel distance + screen height,' the vertical scroll speed and the horizontal movement speed match, so it feels natural. Instead of overflow: hidden on the track, use overflow: clip or hidden on the stage, and be careful not to apply it to any ancestor of the sticky element itself.
23 Hide-on-scroll Header · Hide-on-scroll header
When you scroll down, the header hides; scroll up even a little and it reappears.
In the scroll event, compare the previous scrollY with the current value to get the direction. When scrolling down, add a class that applies translateY(-100%) to the header. Since it only changes transform, layout isn't recalculated, so it stays smooth. It's useful on narrow mobile screens where you want more room for reading. Set a threshold of a few pixels so small jitters don't cause flickering, and always keep the header visible near the very top of the page.
24 Scrollspy · Scrollspy
The table of contents beside the article highlights the section you're currently reading as you scroll.
Apply an IntersectionObserver to each content section. For the section that enters the zone defined by the viewport top (rootMargin '0px 0px -70% 0px'), add an active class to the table-of-contents link with the matching ID. Pin the table of contents to the side using position: sticky, a common approach for the right-side TOC in documentation and blogs. In case a short section is skipped over the detection zone, keep a reference to the last section that entered so the active state doesn't get cleared.
When asking AI to build it
The more specifically you name the approach and the implementation, the more accurate and polished the result will be.
- Pure CSS: "Make a scroll progress bar using only CSS animation-timeline"
- With GSAP ScrollTrigger: "Pin the sections and create step-by-step animations with scrub"
- With Framer Motion: "Make cards fade in with a stagger using useInView"
Finally, restraint in motion gives a more refined impression. Limit each page to two or three animation effects, and for users who have enabled the prefers-reduced-motion setting, show only the final state without movement. The demos in this post were also built following this principle.
Comments 0
- Loading comments.