스크롤리텔링

스크롤리텔링은 흥미롭고 몰입도 높은 웹 환경을 만드는 데 사용되는 인기 있는 기법입니다. 사용자가 스크롤할 때 페이지의 요소를 애니메이션화하여 효과적으로 스토리를 전달하거나 내러티브를 통해 사용자를 안내합니다. CSS 스크롤 기반 애니메이션을 사용하면 JavaScript에 의존하지 않고도 CSS에서 직접 이러한 효과를 만들 수 있습니다. 애니메이션은 시간 기반 시계가 아닌 스크롤 위치로 제어되므로 항상 사용자의 스크롤과 동기화됩니다.

구현 방법

스크롤리텔링 환경을 만들려면 스크롤 위치를 추적하는 요소와 애니메이션을 적용할 요소라는 두 가지 요소 집합이 필요합니다.

먼저 추적하려는 요소에 이름이 지정된 view-timeline를 정의합니다. 이러한 상태는 애니메이션의 드라이버 역할을 합니다.

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

그런 다음 애니메이션을 적용할 요소에 애니메이션을 적용하고 animation-timeline 속성을 사용하여 방금 만든 타임라인에 연결합니다.

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

animation-timeline가 이름이 지정된 타임라인을 참조할 수 있으려면 동일한 범위에 있어야 합니다. 공통 상위 요소의 timeline-scope 속성을 사용하여 타임라인이 필요한 모든 요소에서 타임라인을 사용할 수 있도록 할 수 있습니다. 이 경우 :root 요소가 적합합니다.

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

마지막으로 animation-range 속성을 사용하여 애니메이션이 실행되는 타임라인의 정확한 범위를 지정할 수 있습니다. 이를 통해 애니메이션이 트리거되는 시점과 진행 방식을 세밀하게 제어할 수 있습니다.

#animated section {
  animation-range: entry 25% cover 50%, exit 50% exit 75%;
}

예시 코드

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

@keyframes animate-in {
  from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
  to { transform: rotateY(0deg); }
}
@keyframes animate-out {
  to { translate: 100% 0; opacity: 0; }
}

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
    backface-visibility: hidden;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

권장사항

스크롤 기반 애니메이션을 사용할 때는 원활하고 접근성 높은 환경을 보장하기 위해 몇 가지 권장사항을 따르는 것이 중요합니다.

  • 권장사항: 기능 감지를 포함합니다. 모든 브라우저가 스크롤 기반 애니메이션을 지원하는 것은 아닙니다. @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%))를 사용하여 지원 여부를 확인하고 지원하지 않는 브라우저에 대체 기능을 제공합니다.
    • 일부만 지원하는 브라우저를 필터링하려면 여기에 (animation-range: 0% 100%) 검사를 포함해야 합니다(MUST).
    • scroll-timeline-polyfill 패키지는 기능이 완전하지 않고 알려진 문제가 많으므로 대체 전략에 사용하지 마세요.
    • 애니메이션이 장식용으로만 간주되는 경우 점진적 개선을 선택하고 대체 기능을 제공하지 마세요.
  • 권장사항 사용자 환경설정 존중: 일부 사용자는 웹에서 모션이 적은 것을 선호합니다. prefers-reduced-motion 미디어 쿼리를 사용하여 이러한 사용자의 애니메이션을 사용 중지하거나 줄입니다.
  • 권장사항: 성능이 우수한 CSS 속성에만 애니메이션을 적용하세요. 가장 부드러운 애니메이션을 구현하려면 브라우저의 컴포지터 스레드에서 처리할 수 있는 속성(예: transform 및 opacity)에만 애니메이션을 적용하세요. width 또는 height와 같은 다른 속성에 애니메이션을 적용하면 성능 문제가 발생할 수 있습니다.
  • 권장사항: 올바른 선언 순서를 사용합니다. animation 약식 속성을 사용하는 경우 약식이 타임라인을 재설정하지 않도록 animation-timeline 및 animation-range를 뒤에 선언합니다.

view-timeline 속성을 사용하여 스크롤 기반 애니메이션을 만들 때 다음을 수행합니다.

  • 이름에 CSS <dashed-ident>를 사용합니다.
  • 선택사항 추적할 축을 명시적으로 지정: 기본 block 축을 타겟팅하지 않는 경우 (예: 가로 스크롤러) view-timeline-axis를 사용하여 추적할 축을 명시적으로 지정합니다.
  • 권장사항: 조회 범위가 작동하는지 확인합니다. view-timeline를 선언하는 요소가 애니메이션 요소의 플랫 트리 상위 요소가 아닌 경우 공유 상위 요소에서 timeline-scope를 사용하여 view-timeline 이름의 공개 상태를 호이스팅합니다.

대체 전략

스크롤 기반 애니메이션을 지원하지 않는 브라우저의 경우 대체 기능을 사용하여 시각 효과를 다시 만들 수 있습니다. 대체는 일반적으로 스크롤 리스너 (ScrollTimeline 효과의 경우) 또는 IntersectionObserver API (ViewTimeline 효과의 경우)를 사용하여 빌드됩니다.

스크롤 기반 애니메이션을 기본적으로 지원하는 브라우저에서는 항상 네이티브 CSS 구현을 사용하세요. 성능이 더 우수합니다.

대체 접근 방식을 사용하여 모든 효과를 다시 만들 수 있는 것은 아닙니다.

이 사용 사례의 경우 다음 스크립트는 스크롤 기반 애니메이션을 지원하지 않는 브라우저에 대체 기능을 적용합니다. IntersectionObserver를 사용하여 각 #tracked section 요소의 공개 상태를 추적하고 이에 따라 상응하는 #animated section의 transform 속성을 업데이트합니다.

const animatedSections = document.querySelectorAll('#animated section');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
    if (sectionIndex !== -1) {
      const animatedSection = animatedSections[sectionIndex];
      const ratio = entry.intersectionRatio;

      // Animate-in
      animatedSection.style.opacity = ratio;
      animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;

      // Animate-out
      if (ratio < 0.5) {
        animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
      } else {
        animatedSection.style.translate = '0 0';
      }
    }
  });
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });

document.querySelectorAll('#tracked section').forEach(section => {
  observer.observe(section);
});

함께 제공되는 CSS:

#animated section {
  opacity: 0;
  transform: scale(0.5)  rotateY(-180deg);
  backface-visibility: hidden;
}

/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    opacity: 1 !important;
    transform: none !important;
    translate: 0 0 !important;
  }
}

이 대체는 네이티브 CSS 기능을 지원하지 않는 브라우저에 더 정확한 스크롤 기반 애니메이션을 제공하여 모든 사용자에게 더 일관된 환경을 보장합니다. IntersectionObserver에 일련의 기준점을 사용하면 스크롤 위치를 더 정확하게 추적하고 더 부드러운 애니메이션을 만들 수 있습니다.