게시: 2026년 2월 26일
스크롤 기반 애니메이션은 불안정한 메인 스레드 JavaScript 구현에서 스크롤 타임라인 및 뷰 타임라인과 같은 최신 CSS 및 UI 기능을 사용하여 원활하고 액세스 가능한 메인 스레드 외부 환경으로 발전했습니다. 이러한 변화를 통해 빠른 프로토타입 제작과 고성능 애니메이션을 지원할 수 있으며, 팀에서는 이 도움말에 설명된 대로 세련된 스크롤 텔링 페이지를 만들 수 있습니다.
NRK와 스토리텔링
NRK (노르웨이 방송공사)는 노르웨이의 공영 방송사입니다. 이 도움말에 설명된 구현을 담당하는 팀은 노르웨이어로 Visuelle Historier라고 하며, 이는 영어로 대략 시각적 스토리로 번역됩니다. 이 팀은 TV, 라디오, 웹의 편집 프로젝트를 위한 디자인, 그래픽, 개발을 담당하며 시각적 아이덴티티, 콘텐츠 그래픽, 특집 기사, 새로운 시각적 스토리텔링 형식을 개발합니다. 또한 NRK의 디자인 프로필 및 하위 브랜드와 협력하여 NRK의 브랜드 아이덴티티에 맞게 콘텐츠를 더 쉽게 게시할 수 있는 도구와 템플릿을 만듭니다.
NRK에서 스크롤 기반 애니메이션을 사용하는 방법
스크롤 기반 및 스크롤 트리거 애니메이션은 스토리텔링 기사를 더욱 양방향적이고 흥미롭게 만들고 기억에 남게 함으로써 기사의 효과를 높입니다. 이 접근 방식은 이미지가 거의 없거나 없는 논픽션 내러티브에 특히 유용합니다.
이러한 애니메이션은 극적인 요소를 강화하거나 만들고, 이야기를 진행하며, 텍스트와 일치하거나 텍스트를 보강하는 작은 시각적 내러티브를 개발하는 데 도움이 됩니다. 이러한 애니메이션은 스크롤을 기반으로 하므로 사용자가 스크롤을 통해 내러티브의 진행을 제어할 수 있습니다.
사용자 환경 개선
NRK의 사용자 통계에 따르면 이러한 애니메이션이 사용자의 관심을 유도하는 방식을 독자들이 좋아한다고 합니다. 스크롤할 때 텍스트나 애니메이션을 강조 표시하면 특히 스키밍할 때 사용자가 핵심 사항을 파악하고 기사의 가장 중요한 측면을 더 쉽게 이해할 수 있습니다.
또한 그래픽을 애니메이션 처리하면 복잡한 정보를 단순화하여 사용자가 시간 경과에 따른 관계와 변화를 더 쉽게 이해할 수 있습니다. NRK는 정보를 동적으로 빌드, 추가 또는 강조 표시하여 더 교육적이고 흥미로운 방식으로 콘텐츠를 제공할 수 있습니다.
분위기 설정
애니메이션은 스토리의 분위기를 설정하거나 향상하는 데 효과적인 도구가 될 수 있습니다. NRK는 애니메이션의 타이밍, 속도, 스타일을 조정하여 내러티브의 어조에 맞는 감정을 불러일으킬 수 있습니다.
텍스트를 나누고 시각적 완화를 제공합니다.
NRK는 종종 작은 애니메이션 삽화를 사용하여 긴 텍스트 블록을 간단한 딩크스나 작은 삽화의 형태로 나누어 독자가 스토리에서 잠시 쉬어갈 수 있도록 합니다. 많은 사용자는 텍스트를 나누고 더 쉽게 소화할 수 있게 해준다는 점에서 이러한 변화를 높이 평가합니다. 내레이션에 잠시 쉬어가는 시간을 제공한다고 생각합니다.
접근성 요구사항 및 사용자 환경설정 존중
NRK의 공개 페이지는 노르웨이의 모든 시민이 액세스할 수 있어야 합니다. 따라서 페이지는 모션 감소에 대한 사용자의 환경설정을 따라야 합니다. 이 브라우저 설정을 사용 설정한 사용자는 모든 페이지 콘텐츠를 사용할 수 있어야 합니다.
스크롤 기반 애니메이션 디자인
NRK는 새로운 스크롤 애니메이션 도구를 개발하고 Sanity 콘텐츠 관리 시스템 (CMS)에 직접 통합하여 디자인 워크플로를 간소화했습니다. 사이트와 CMS 솔루션을 개발하고 유지보수하는 팀 간의 공동작업으로 개발된 이 도구를 사용하면 디자이너가 애니메이션 요소의 시작 및 종료 위치에 대한 시각적 신호와 실시간으로 애니메이션을 미리 볼 수 있는 기능을 사용하여 스크롤 애니메이션을 쉽게 프로토타입으로 제작하고 구현할 수 있습니다. 이 혁신을 통해 디자이너는 CMS 내에서 직접 디자인 프로세스를 더 효과적으로 제어하고 속도를 높일 수 있습니다.

브라우저의 스크롤 기반 애니메이션
스토리 기반 애니메이션
아파트에서 9년 동안 시체로 발견된 남성에 관한 이 기사는 다른 시각적 요소가 없어 일러스트레이션에 크게 의존해야 했습니다. 스크롤을 통해 애니메이션을 적용하여 스토리를 강조했습니다. 예를 들어 밤이 되면 고층 건물의 불이 점차 켜지다가 아파트 한 채만 불이 켜진 채로 남아 있는 애니메이션이 있습니다. 이 애니메이션은 NRK의 자체 스크롤 기반 애니메이션 도구를 사용하여 제작되었습니다.
텍스트 페이드 애니메이션
영구동토.
이 도움말은 영화의 시작 시퀀스를 반영하는 간단한 소개로 시작합니다. 간결한 텍스트와 전체 화면 시각화를 함께 사용하여 기사의 내용을 암시하고 독자가 전체 기사를 탐색하도록 기대감을 높이도록 디자인했습니다. 제목 페이지는 영화 포스터와 비슷하게 제작되었으며, 스크롤 기반 애니메이션을 사용하여 텍스트를 부드럽게 위로 그리고 밖으로 애니메이션 처리하여 이러한 느낌을 강화했습니다.
.article-section {
animation: fade-up linear;
animation-timeline: view();
animation-range: entry 100% exit 100%;
}
스크롤 애니메이션 서체
병가 도움말 제목에 애니메이션이 적용된 서체
NRK는 'Sjukt sjuke' (대략 'Sickly sick'으로 번역됨)라는 제목으로 노르웨이의 증가하는 병가율에 관한 기사에 독자의 관심을 끌고자 했습니다. 이 제목은 독자에게 일반적인 지루한 숫자 중심의 기사가 아님을 암시하는 시선을 사로잡는 제목이었습니다. NRK팀은 텍스트와 일러스트레이션이 작품의 테마를 반영하도록 하고 서체와 스크롤 기반 애니메이션을 사용하여 이를 강화하고자 했습니다. 이 도움말에서는 NRK 뉴스의 새로운 글꼴 및 디자인 프로필을 활용합니다.
<h1 aria-label="sjuke">
<span>s</span><span>j</span><span>u</span><span>k</span><span>e</span>
<h1>
h1 span {
display: inline-block;
}
if (window.matchMedia('print, (prefers-reduced-motion: reduce)').matches) {
return;
}
const heading = document.querySelector("h1");
const letters = heading.querySelectorAll("span");
const timeline = new ViewTimeline({ subject: heading });
const scales = [/**/];
const rotations = [/**/];
for ([index, el] of letters.entries()) {
el.animate(
{
scale: ["1", scales[index]],
rotate: ["0deg", rotations[index]]
},
{
timeline,
fill: "both",
rangeStart: "contain 30%",
rangeEnd: "contain 70%",
easing: "ease-out"
}
);
}
스크롤 시 고정된 항목 강조 표시
기사를 읽은 독자는 동일한 문제에 대해 자세히 알아보려는 경우가 많습니다. NRK는 시설에서 약물을 남용하는 청소년에 관한 기사에서 다음으로 읽을 기사를 하나 추천하는 동시에 독자가 원하는 경우 다른 여러 기사를 선택할 수 있는 옵션을 제공하고자 했습니다. 솔루션은 스크롤 스냅 및 스크롤 기반 애니메이션으로 구현된 스와이프 가능한 탐색입니다. 애니메이션을 통해 활성 요소에 포커스가 맞춰지고 나머지 요소는 어두워졌습니다.
for (let item of items) {
const timeline = new ViewTimeline({ subject: item, axis: "inline" });
const animation = new Animation(effect, timeline);
item.animate(
{
opacity: [0.3, 1, 0.3]
},
{ timeline, easing: "ease-in-out", fill: "both" }
);
animation.rangeStart = "cover calc(50% - 100px)";
animation.rangeEnd = "cover calc(50% + 100px)";
}
스크롤 애니메이션이 일반 애니메이션을 트리거함
노르웨이 국가 예산에 관한 이 기사에서 NRK는 숫자 기반의 무겁고 지루한 스토리를 더 쉽게 접근하고 맞춤설정할 수 있도록 만들고자 했습니다. 이 기사의 목표는 이해하기 어려운 거대한 예산을 분류하고 독자에게 세금이 어디에 쓰이고 있는지 개인적으로 파악할 수 있도록 하는 것이었습니다. 각 하위 섹션은 국가 예산의 특정 항목에 초점을 맞췄습니다. 독자의 총 세금 기여도는 파란색 막대로 표시되었으며, 이 막대는 개별 항목에 대한 독자의 기여도를 나타내기 위해 분할되었습니다. 전환은 개별 항목의 애니메이션을 트리거하는 스크롤 기반 애니메이션으로 이루어졌습니다.
const timeline = new ViewTimeline({
subject: containerElement
});
// Setup scroll-driven animation
const scrollAnimation = containerElement.animate(
{
"--cover-color": ["blue", "lightblue"],
scale: ["1 0.2", "1 3"]
},
{
timeline,
easing: "cubic-bezier(1, 0, 0, 0)",
rangeStart: "cover 0%",
rangeEnd: "cover 50%"
}
);
// Wait for scroll-driven animation to complete
await scrollAnimation.finished;
scrollAnimation.cancel();
// Trigger time-driven animations
for (let [index, postElement] of postElements.entries()) {
const animation = postElement?.animate(
{ scale: ["1 3", "1 1"] },
{
duration: 200,
delay: index * 33,
easing: "ease-out",
fill: "backwards"
}
);
}
"오랫동안 스크롤 기반 애니메이션을 사용해 왔습니다. Web Animations API가 존재하기 전에는 스크롤 이벤트를 사용해야 했으며 나중에 Intersection Observer API와 결합했습니다. 이 작업은 종종 매우 시간이 많이 걸렸지만 이제 웹 애니메이션 및 스크롤 기반 애니메이션 API를 사용하면 간단하게 처리할 수 있습니다."—Helge Silset, NRK의 프런트엔드 개발자
NRK에는 ScrollAnimationDriver
(<scroll-animation-driver>
)라는 맞춤 요소 중 하나에 연결하여 다음 애니메이션을 지원할 수 있는 다양한 웹 구성요소가 있습니다.
[KeyframeEffects](https://developer.mozilla.org/docs/Web/API/KeyframeEffect)
이 있는 레이어- Lottie 애니메이션
- mp4
- three.js
<canvas>
다음 예에서는 KeyframeEffects
와 함께 레이어를 사용합니다.
<scroll-animation-driver data-range-start='entry-crossing 50%' data-range-end='exit-crossing 50%'>
<layered-animation-effect>
<picture>
<source />
<img />
</picture>
<picture>
<source />
<img />
</picture>
<picture>
<source />
<img />
</picture>
</layered-animation-effect>
</scroll-animation-driver>
NRK의 <scroll-animation-driver>
맞춤 요소에 관한 JavaScript 구현:
export default class ScrollAnimationDriver extends HTMLElement {
#timeline
connectedCallback() {
this.#timeline = new ViewTimeline({subject: this})
for (const child of this.children) {
for (const effect of child.effects ?? []) {
this.#setupAnimationEffect(effect)
}
}
}
#setupAnimationEffect(effect) {
const animation = new Animation(effect, this.#timeline)
animation.rangeStart = this.rangeStart
animation.rangeEnd = this.rangeEnd
if (this.prefersReducedMotion) {
animation.currentTime = CSS.percent(this.defaultProgress * 100)
} else {
animation.play()
}
}
}
export default class LayeredAnimationEffect extends HTMLElement {
get effects() {
return this.layers.flatMap(layer => toKeyframeEffects(layer))
}
}
스크롤 성능
NRK는 스크롤 기반 애니메이션을 사용하기 전에 성능이 매우 우수한 JavaScript 구현을 보유하고 있었지만, 이제 스크롤 기반 애니메이션을 사용하면 저전력 기기에서도 스크롤 버벅거림을 걱정하지 않고도 더 나은 성능을 얻을 수 있습니다.
- SDA 외 태스크 기간: 1ms
- SDA 태스크 시간: 0.16ms

JavaScript 구현과 스크롤 기반 애니메이션 간의 스크롤 성능 차이에 관한 자세한 내용은 스크롤 기반 애니메이션 성능에 관한 케이스 스터디를 참고하세요.
접근성 및 UX 고려사항
NRK의 공개 페이지는 다양한 상황에서 모든 노르웨이 시민이 액세스할 수 있어야 하므로 접근성이 매우 중요합니다. NRK는 다음과 같은 몇 가지 방법으로 스크롤 애니메이션에 액세스할 수 있도록 합니다.
- 모션 감소에 대한 사용자의 환경설정 준수: 미디어 쿼리
screen and (prefers-reduced-motion: no-preference)
를 사용하여 애니메이션을 점진적 개선으로 적용합니다. 인쇄 스타일도 동시에 처리하는 것이 좋습니다. - 다양한 기기와 다양한 스크롤 입력 정밀도 고려: 일부 사용자는 단계적으로 스크롤 (스페이스바 또는 위/아래 키, 스크린 리더를 사용하여 랜드마크로 이동)하여 전체 애니메이션을 보지 못할 수 있습니다. 중요한 정보를 놓치지 않도록 하세요.
- 콘텐츠를 표시하거나 숨기는 애니메이션에 주의: 운영체제 (OS) 확대/축소를 사용하는 사용자의 경우 스크롤할 때 숨겨진 콘텐츠가 표시된다는 것을 알아차리기 어려울 수 있습니다. 사용자가 검색하도록 하지 마세요. 콘텐츠를 숨기거나 표시해야 하는 경우 콘텐츠가 표시되고 사라지는 위치가 일관되도록 합니다.
- 애니메이션에서 밝기 또는 대비의 큰 변화 방지: 스크롤 기반 애니메이션은 사용자 제어에 종속되므로 갑작스러운 밝기 변화가 깜박임으로 표시되어 일부 사용자의 발작을 유발할 수 있습니다.
@media (prefers-reduced-motion: no-preference) {
.article-image {
opacity: 0;
transition: opacity 1s ease-in-out;
}
.article-image.visible {
opacity: 1;
}
}
브라우저 지원
ScrollTimeline 및 ViewTimeline의 더 광범위한 브라우저 지원을 위해 NRK는 적극적인 커뮤니티가 참여하는 오픈소스 폴리필을 사용합니다.
현재 polyfill은 ScrollTimeline
를 사용할 수 없고 CSS 지원 없이 폴리필의 축소된 버전을 사용하는 경우에 조건부로 로드됩니다.
if (!('ScrollTimeline' in window)) {
await import('scroll-timeline.js')
}
CSS에서 브라우저 지원 감지 및 처리:
@supports not (animation-timeline: view()) {
.article-section {
translate: 0 calc(-15vh * var(--fallback-progress));
opacity: var(--fallback-progress);
}
}
@supports (animation-timeline: view()) {
.article-section {
animation: --fade-up linear;
animation-timeline: view();
animation-range: entry 100% exit 100%;
}
}
지원되지 않는 브라우저의 이전 예에서 NRK는 translate
및 opacity
속성의 애니메이션 타임라인을 제어하는 대체 수단으로 CSS 변수 --fallback-progress
를 사용하고 있습니다.
그러면 --fallback-progress
CSS 변수가 JavaScript의 scroll
이벤트 리스너 및 requestAnimationFrame
로 업데이트됩니다.
function updateProgress() {
const end = el.offsetTop + el.offsetHeight;
const start = end - window.innerHeight;
const scrollTop = document.scrollingElement.scrollTop;
const progress = (scrollTop - start) / (end - start);
document.body.style.setProperty('--fallback-progress', clamp(progress, 0, 1));
}
if (!CSS.supports("animation-timeline: view()")) {
document.addEventListener('scroll', () => {
if (!visible || updating) {
return;
}
window.requestAnimationFrame(() => {
updateProgress();
updating = false;
});
updating = true;
});
}
리소스
- 스크롤 기반 애니메이션 우수사례
- 데모: 스크롤 기반 애니메이션
- 스크롤 기반 애니메이션으로 스크롤 시 요소에 애니메이션 적용하기
- Codelab: CSS로 스크롤 기반 애니메이션 시작하기
- Chrome 확장 프로그램: 스크롤 기반 애니메이션 디버거
- 스크롤 타임라인 폴리필
- 버그 또는 새 기능을 신고하려면 어떻게 해야 하나요? 여러분의 의견을 기다립니다.
이 작업에 중요한 기여를 해 주신 Google의 한나 반 오프스탈, 브라무스, 앤드루 케인 관, NRK의 잉그리드 레이메에게 감사의 인사를 전합니다.