CSS로 스크롤 기반 애니메이션 시작하기

1. 시작하기 전에

스크롤 기반 애니메이션을 사용하면 스크롤 컨테이너의 스크롤 위치를 기반으로 애니메이션 재생을 제어할 수 있습니다. 즉, 위 또는 아래로 스크롤하면 애니메이션이 앞뒤로 스크러빙됩니다. 또한 스크롤 기반 애니메이션을 사용하면 스크롤 컨테이너 내의 요소 위치를 기반으로 애니메이션을 제어할 수도 있습니다. 이를 통해 시차 배경 이미지, 스크롤 진행률 표시줄, 시야에 들어올 때 드러나는 이미지와 같은 흥미로운 효과를 만들 수 있습니다.

Chrome 114에서는 선언적 스크롤 기반 애니메이션을 쉽게 만들 수 있는 JavaScript 클래스 및 CSS 속성 집합이 새로운 기능으로 지원됩니다. 이러한 새 API는 기존 웹 애니메이션 및 CSS Animations API와 함께 작동합니다.

이 Codelab에서는 CSS를 사용하여 스크롤 기반 애니메이션을 만드는 방법을 알아봅니다. 이 Codelab을 완료하면 scroll-timeline, view-timeline, animation-timeline, animation-range와 같이 흥미로운 기능과 함께 제공되는 많은 새로운 CSS 속성에 익숙해집니다.

학습할 내용

  • CSS에서 스크롤 타임라인으로 시차 배경 효과를 만드는 방법
  • CSS에서 스크롤 타임라인으로 진행률 표시줄을 만드는 방법
  • CSS에서 뷰 타임라인으로 이미지 표시 효과를 만드는 방법
  • CSS에서 뷰 타임라인의 다양한 범위 유형을 타겟팅하는 방법

필요한 항목

다음 기기 조합 중 하나가 필요합니다.

  • ChromeOS, macOS, Windows 기반의 최신 버전 Chrome(114 이상)
  • HTML에 관한 기본 이해
  • CSS(특히 CSS의 애니메이션)에 관한 기본 이해

2. 설정

이 프로젝트에 필요한 모든 코드는 GitHub 저장소에 제공됩니다. 시작하려면 코드를 클론하고 선호하는 개발 환경에서 여세요.

  1. 새 브라우저 탭을 열고 https://github.com/googlechromelabs/io23-scroll-driven-animations-codelab으로 이동합니다.
  2. 저장소를 클론합니다.
  3. 선호하는 IDE에서 코드를 엽니다.
  4. npm install을 실행하여 종속 항목을 설치합니다.
  5. npm start를 실행하고 http://localhost:3000/으로 이동합니다.
  6. 또는 npm을 설치하지 않은 경우 Chrome에서 src/index.html 파일을 엽니다.

3. 애니메이션 타임라인 알아보기

기본적으로 요소에 연결된 애니메이션은 문서 타임라인에서 실행됩니다. 따라서 페이지가 로드될 때 시간이 경과함에 따라 애니메이션이 진행됩니다. 이것이 기본 애니메이션 타임라인이며 지금까지 액세스할 수 있었던 유일한 애니메이션 타임라인이었습니다.

스크롤 기반 애니메이션을 사용하면 두 가지 새로운 유형의 타임라인에 액세스할 수 있습니다.

  • 스크롤 진행률 타임라인
  • 뷰 진행률 타임라인

CSS에서 이러한 타임라인은 animation-timeline 속성을 사용하여 애니메이션에 연결할 수 있습니다. 이러한 새로운 타임라인이 의미하는 바는 무엇이고 서로 어떻게 다른지 살펴보세요.

스크롤 진행률 타임라인

스크롤 진행률 타임라인은 특정 축을 따라 스크롤 컨테이너의 스크롤 위치(스크롤 포트 또는 스크롤러라고도 함)에서 진행률에 링크된 애니메이션 타임라인입니다. 스크롤 범위의 위치를 타임라인에 따른 진행률(%)로 변환합니다.

시작 스크롤 위치는 0% 진행률로 나타내고 종료 스크롤 위치는 100% 진행률로 나타냅니다. 다음 시각화에서 스크롤러를 아래로 스크롤하면 진행률이 0%에서 100%까지 증가합니다.

뷰 진행률 타임라인

이 유형의 타임라인은 스크롤 컨테이너 내 특정 요소의 상대적인 진행률에 링크됩니다. 스크롤 진행률 타임라인과 마찬가지로 스크롤러의 스크롤 오프셋이 추적됩니다. 스크롤 진행률 타임라인과 달리 진행률을 결정하는 것은 해당 스크롤러 내에서 대상의 상대 위치입니다. 이것은 스크롤러에서 요소가 얼마나 많이 보이는지를 추적하는 IntersectionObserver와 유사합니다. 스크롤러에 요소가 보이지 않으면 교차하지 않습니다. 가장 작은 부분이라도 스크롤러 내부에 보이면 교차합니다.

뷰 진행률 타임라인은 대상이 스크롤러와 교차하기 시작하는 순간부터 시작되어 대상이 스크롤러와 교차하는 것을 멈출 때 끝납니다. 다음 시각화에서 진행률은 대상이 스크롤 컨테이너에 들어갈 때 0%부터 시작하여 스크롤 컨테이너를 벗어날 때 100%에 도달합니다.

기본적으로 뷰 진행률 타임라인에 링크된 애니메이션은 타임라인의 전체 범위에 연결됩니다. 대상이 스크롤 포트에 들어가는 순간부터 시작되어 대상이 스크롤 포트를 벗어날 때 끝납니다.

연결해야 하는 범위를 지정하여 뷰 진행률 타임라인의 특정 부분에 링크할 수도 있습니다. 예를 들어 대상이 스크롤러에 들어갈 때만 가능합니다. 다음 시각화에서 진행률은 대상이 스크롤 컨테이너에 들어갈 때 0%부터 시작하지만 완전히 교차하는 순간부터 이미 100%에 도달합니다.

타겟팅 가능한 뷰 타임라인 범위는 cover, contain, entry, exit, entry-crossing, exit-crossing입니다. 이 Codelab의 뒷부분에서 이 범위에 대해 설명하지만 지금 바로 알아보고 싶다면 https://goo.gle/view-timeline-range-tool에 있는 도구를 사용하여 각 범위가 무엇을 나타내는지 확인하세요.

4. 시차 배경 효과 만들기

페이지에 추가할 첫 번째 효과는 기본 배경 이미지에 대한 시차 배경 효과입니다. 페이지를 아래로 스크롤하면 속도는 다르더라도 배경 이미지가 이동해야 합니다. 이러한 효과를 얻기 위해 스크롤 진행률 타임라인을 사용합니다.

이 효과를 구현하려면 다음 두 단계를 진행합니다.

  1. 배경 이미지의 위치를 이동하는 애니메이션 만들기
  2. 애니메이션을 문서의 스크롤 진행률에 링크

애니메이션 만들기

  1. 일반 키프레임 세트를 사용하여 애니메이션을 만듭니다. 여기에서 배경 위치를 0%에서 100%로 세로로 이동합니다.

src/styles.css

@keyframes move-background {
  from {
    background-position: 50% 0%;
  }
  to {
    background-position: 50% 100%;
  }
}
  1. 이제 이러한 키프레임을 본문 요소에 연결합니다.

src/styles.css

body {
  animation: 1s linear move-background;
}

이 코드를 사용하면 move-background 애니메이션이 본문 요소에 추가됩니다. animation-duration 속성은 1초로 설정되며 linear 이징을 사용합니다.

스크롤 진행률 타임라인을 만드는 가장 쉬운 방법은 scroll() 함수를 사용하는 것입니다. 이 함수를 사용하면 animation-timeline 속성의 값으로 설정할 수 있는 익명의 스크롤 진행률 타임라인이 생성됩니다.

scroll() 함수는 <scroller><axis> 인수를 허용합니다.

<scroller> 인수에 허용되는 값은 다음과 같습니다.

  • nearest. 가장 가까운 상위 스크롤 컨테이너를 사용합니다(기본값).
  • root. 스크롤 컨테이너로 문서 표시 영역을 사용합니다.
  • self. 스크롤 컨테이너로 요소 자체를 사용합니다.

<axis> 인수에 허용되는 값은 다음과 같습니다.

  • block. 스크롤 컨테이너의 블록 축을 따라 진행률 측정을 사용합니다(기본값).
  • inline. 스크롤 컨테이너의 인라인 축을 따라 진행률 측정을 사용합니다.
  • y. 스크롤 컨테이너의 Y축을 따라 진행률 측정을 사용합니다.
  • x. 스크롤 컨테이너의 X축을 따라 진행률 측정을 사용합니다.

애니메이션을 블록 축에서 루트 스크롤러에 링크하기 위해 scroll()에 전달할 값은 rootblock: scroll(root block)입니다.

  • 본문에서 animation-timeline 속성 값으로 scroll(root block)을 설정합니다. 또한 초 단위로 표현된 animation-duration은 적절하지 않으므로 기간을 auto로 설정합니다. animation-duration을 지정하지 않으면 기본적으로 auto로 설정됩니다.

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll(root block);
}

루트 스크롤러는 본문 요소에 대해 가장 가까운 상위 스크롤러이기도 하므로 nearest 값을 사용할 수도 있습니다.

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll(nearest block);
}

nearestblock은 기본값이므로 생략해도 됩니다. 이 경우 코드를 다음과 같이 간단하게 작성할 수 있습니다.

src/styles.css

body {
  animation: linear move-background;
  animation-duration: auto;
  animation-timeline: scroll();
}

변경사항 확인

아무 문제가 없으면 이제 다음과 같아야 합니다.

그렇지 않은 경우 코드의 solution-step-1 브랜치를 확인하세요.

5. 이미지 갤러리의 진행률 표시줄 만들기

페이지에는 현재 보고 있는 사진을 나타내기 위해 진행률 표시줄이 필요한 가로 캐러셀이 있습니다.

캐러셀의 마크업은 다음과 같습니다.

src/index.html

<div class="gallery">
  <div class="gallery__scrollcontainer" style="--num-images: 3;">
    <div class="gallery__progress"></div>
    <div class="gallery__entry">
      ...
    </div>
    <div class="gallery__entry">
      ...
    </div>
    <div class="gallery__entry">
      ...
    </div>
  </div>
</div>

진행률 표시줄의 키프레임은 이미 준비되어 있으며 다음과 같습니다.

src/styles.css

@keyframes adjust-progress {
  from {
    transform: scaleX(calc(1 / var(--num-images)));
  }
  to {
    transform: scaleX(1);
  }
}

이 애니메이션은 스크롤 진행률 타임라인과 함께gallery__progress 요소에 연결되어야 합니다. 이전 단계에서 본 바와 같이 scroll() 함수를 사용하여 익명의 스크롤 진행률 타임라인을 생성하여 이 작업을 수행할 수 있습니다.

src/styles.css

.gallery__progress {
  animation: linear adjust-progress;
  animation-duration: auto;
  animation-timeline: scroll(nearest inline);
}

스크롤 진행률 타임라인을 정의하는 다른 방법은 이름이 지정된 스크롤 진행률 타임라인을 사용하는 것입니다. 좀 더 세부적이지만 상위 스크롤러 또는 루트 스크롤러를 타겟팅하지 않거나 페이지에서 여러 타임라인을 사용할 때 이 방법이 유용할 수 있습니다. 이렇게 하면 이름을 지정하여 스크롤 진행률 타임라인을 식별할 수 있습니다.

요소에 대해 이름이 지정된 스크롤 진행률 타임라인을 만들려면 스크롤 컨테이너의 scroll-timeline-name CSS 속성을 원하는 값으로 설정합니다.

갤러리가 가로로 스크롤되므로 scroll-timeline-axis 속성도 설정해야 합니다. 허용되는 값은 scroll()<axis> 인수와 동일합니다.

마지막으로 애니메이션을 스크롤 진행률 타임라인에 링크하려면 애니메이션을 적용해야 하는 요소의 animation-timeline 속성을 scroll-timeline-name에 사용된 식별자와 동일한 값으로 설정합니다.

  • 다음을 포함하도록 styles.css 파일을 변경합니다.

src/styles.css

.gallery__scrollcontainer {
  /* Create the gallery-is-scrolling timeline */
  scroll-timeline-name: gallery-is-scrolling;
  scroll-timeline-axis: inline;
}

.gallery__progress {
  animation: linear adjust-progress;
  animation-duration: auto;
  /* Set gallery-is-scrolling as the timeline */
  animation-timeline: gallery-is-scrolling;
}

변경사항 확인

아무 문제가 없으면 이제 다음과 같아야 합니다.

그렇지 않은 경우 코드의 solution-step-2 브랜치를 확인하세요.

6. 갤러리 이미지가 스크롤 포트에 들어가거나 나올 때 애니메이션 적용

익명의 뷰 진행률 타임라인 설정

추가할 수 있는 좋은 효과는 갤러리 이미지가 시야에 들어올 때 페이드인하는 것입니다. 이 효과를 위해 뷰 진행률 타임라인을 사용할 수 있습니다.

view() 함수를 사용해 뷰 진행률 타임라인을 만들 수 있습니다. 허용되는 인수는 <axis><view-timeline-inset>입니다.

  • <axis>는 스크롤 진행률 타임라인과 동일하며 추적할 축을 정의합니다.
  • <view-timeline-inset>을 사용하면 요소가 시야에 있거나 없는 것으로 간주될 때 경계를 조정하기 위해 오프셋(양수 또는 음수)을 지정할 수 있습니다.
  • 키프레임이 이미 준비되었으므로 연결하기만 하면 됩니다. 이렇게 하려면 각 .gallery__entry 요소에 대해 뷰 진행률 타임라인을 만듭니다.

src/styles.css

@keyframes animate-in {
  from {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
  to {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
}

.gallery__entry {
  animation: linear animate-in;
  animation-duration: auto;
  animation-timeline: view(inline);
}

뷰 진행률 타임라인 범위 제한

CSS를 저장하고 페이지를 로드하면 요소가 페이드인되는 것을 볼 수 있지만 뭔가 이상해 보입니다. 완전히 시야에서 벗어났을 때 불투명도 0에서 시작되며 완전히 나갔을 때만 불투명도 1에서 끝납니다.

뷰 진행률 타임라인의 기본 범위가 전체 범위이기 때문입니다. 이것을 cover 범위라고 합니다.

  1. 대상의 entry 범위만 타겟팅하려면 애니메이션이 실행되는 시기를 제한하는 animation-range CSS 속성을 사용합니다.

src/styles.css

.gallery__entry {
  animation: linear fade-in;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry 0% entry 100%;
}

이제 애니메이션은 entry 0%(대상이 스크롤러에 들어가기 직전)부터 entry 100%(대상이 스크롤러에 완전히 들어감)까지 실행됩니다.

가능한 뷰 타임라인 범위는 다음과 같습니다.

  • cover. 뷰 진행률 타임라인의 전체 범위를 나타냅니다.
  • entry. 주 상자가 뷰 진행률 가시성 범위에 들어가는 범위를 나타냅니다.
  • exit. 주 상자가 뷰 진행률 가시성 범위를 벗어나는 범위를 나타냅니다.
  • entry-crossing. 주 상자가 끝 테두리 가장자리를 교차하는 범위를 나타냅니다.
  • exit-crossing. 주 상자가 시작 테두리 가장자리를 교차하는 범위를 나타냅니다.
  • contain. 주 상자가 스크롤 포트 내의 뷰 진행률 가시성 범위에 완전히 포함되거나 완전히 포함하는 범위를 나타냅니다. 대상이 스크롤러보다 길거나 짧은지에 따라 달라집니다.

https://goo.gle/view-timeline-range-tool에 있는 도구를 사용하여 각 범위가 나타내는 항목과 해당 비율이 시작 및 끝 위치에 미치는 영향을 확인하세요.

  1. 여기서는 시작 및 끝 범위가 동일하고 기본 오프셋이 사용되기 때문에 animation-range를 단일 애니메이션 범위 이름으로 간소화합니다.

src/styles.css

.gallery__entry {
  animation: linear animate-in;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry;
}
  • 이미지가 스크롤러를 나갈 때 페이드아웃하려면 페이드인 애니메이션과 동일하게 수행할 수 있습니다. 단, 다른 범위를 타겟팅해야 합니다.

src/styles.css

@keyframes animate-out {
  from {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
  to {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
}

.gallery__entry {
  animation: linear animate-in, linear animate-out;
  animation-duration: auto;
  animation-timeline: view(inline);
  animation-range: entry, exit;
}

animate-in 키프레임은 entry 범위에 적용되고 animate-out 키프레임은 exit 범위에 적용됩니다.

변경사항 확인

아무 문제가 없으면 이제 다음과 같아야 합니다.

그렇지 않은 경우 코드의 solution-step-3 브랜치를 확인하세요.

7. 갤러리 이미지가 스크롤 포트에 들어가거나 나올 때 하나의 키프레임 세트를 사용하여 애니메이션 적용

하나의 키프레임 세트 케이스

서로 다른 범위에 두 개의 애니메이션을 연결하는 대신 범위 정보가 이미 포함된 하나의 키프레임 세트를 만들 수 있습니다.

키프레임의 모양은 다음과 같습니다.

@keyframes keyframes-name {
  range-name range-offset {
    ...
  }
  range-name range-offset {
    ...
  }
}
  1. 페이드인 및 페이드아웃 키프레임을 결합한 모양은 다음과 같습니다.

src/styles.css

@keyframes animate-in-and-out {
  entry 0% {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
  entry 90% {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }

  exit 10% {
    opacity: 1;
    clip-path: inset(0% 0% 0% 0%);
  }
  exit 100% {
    opacity: 0;
    clip-path: inset(50% 0% 50% 0%);
  }
}
  1. 범위 정보가 키프레임에 있으면 더 이상 animation-range를 별도로 지정할 필요가 없습니다. 키프레임을 animation 속성으로 추가합니다.

src/styles.css

.gallery__entry {
  animation: linear animate-in-and-out both;
  animation-duration: auto;
  animation-timeline: view(inline);
}

변경사항 확인

아무 문제가 없으면 이전 단계와 동일한 결과가 나와야 합니다. 그렇지 않은 경우 코드의 solution-step-4 브랜치를 확인하세요.

8. 수고하셨습니다.

이 Codelab을 완료했으며 지금까지 CSS에서 스크롤 진행률 타임라인 및 뷰 진행률 타임라인을 만드는 방법을 알아보았습니다.

자세히 알아보기

리소스: