스크롤로 실행되는 애니메이션이 CLS를 유발하는 이유

스크롤 시 숨겨지는 헤더 때문에 모르는 사이 CLS 평가에서 탈락하고 있을 수 있습니다. 그 원인과 해결 방법을 살펴봅니다.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-10-26

Cumulative Layout Shift 진단을 요청받으면 당연히 로딩 단계의 레이아웃 이동부터 살펴봅니다. 크기가 지정되지 않은 이미지, 주입된 광고, 폰트 교체(FOUT) 등을 확인합니다.

하지만 모든 CLS가 페이지 로드 중에 발생하는 것은 아닙니다. 스크롤 같은 상호작용으로 인해 발생하기도 합니다. Lighthouse는 스크롤을 하지 않으며 어디를 살펴봐야 할지 정확히 알지 못하면 잡아내기 어렵습니다.

2026년 3월 Arjen Karel 최종 검토

최근 진행한 진단에서 CoreDash RUM 데이터를 확인하던 중 의심스러운 패턴을 발견했습니다. #header 요소가 정상 범위를 훨씬 뛰어넘는 레이아웃 이동을 일으키고 있었습니다.

확인 결과 사용자가 아래로 스크롤하면 숨겨지고 위로 스크롤하면 다시 나타나는 고정 헤더 때문이었습니다. 실무에서 꽤 자주 접하는 패턴이며, 제대로 구현하지 않으면 스크롤 방향이 바뀔 때마다 레이아웃 이동이 발생합니다.

스크롤로 발생하는 레이아웃 이동이 다른 이유

일반적으로 상호작용 후에는 CLS가 발생하지 않습니다. CLS는 예기치 않은 레이아웃 이동을 측정하며, 상호작용 후에는 레이아웃 변경이 예상되기 때문입니다. 그래서 CLS 지표에는 500ms의 유예 기간이 자동으로 적용됩니다. 사용자 상호작용 후 500밀리초 이내에 발생하는 레이아웃 이동은 CLS 점수에서 제외됩니다. 브라우저는 이러한 이동에 hadRecentInput 플래그를 설정하고 확인하여 이를 처리합니다.

문제는 이 유예 기간이 클릭, 탭, 키 입력과 같은 불연속 이벤트에만 적용된다는 점입니다. Layout Instability 명세에 따르면 스크롤은 명시적으로 제외 대상 입력이 아닙니다. 드래그나 핀치 줌 제스처도 마찬가지입니다. 오직 불연속 이벤트에만 500ms 유예 기간이 주어집니다.

즉, 스크롤 이벤트로 발생하는 모든 레이아웃 이동은 CLS 점수에 그대로 반영됩니다.

2025 Web Almanac에 따르면, 모바일 페이지의 40%가 여전히 비합성(non-composited) 애니메이션을 사용합니다. 이는 top, height, margin처럼 매 프레임마다 레이아웃 재계산을 유발하는 속성에 적용된 애니메이션입니다. 스크롤 도중 이러한 애니메이션이 실행되면 매 프레임이 전부 CLS 점수에 합산됩니다.

스크롤 시 숨겨지는 헤더 문제

오늘 발견한 문제인 '스크롤 시 숨겨지는 헤더 문제'부터 살펴보겠습니다. 고정 헤더가 스크롤에 맞춰 JavaScript로 표시 여부를 전환하는 방식입니다. 구현 코드는 다음과 같습니다:

/* CSS */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* JavaScript */
window.addEventListener('scroll', () => {
  if (scrollingDown) {
    header.style.top = '-80px';  // 숨김
  } else {
    header.style.top = '0px';    // 표시
  }
});

이 레이아웃 이동은 transition: top 0.3s ease와 JavaScript에서의 header.style.top 변경 동작이 결합되어 발생합니다.

이유는 명확합니다. top은 레이아웃 속성입니다. 브라우저는 top 변경을 처리할 때 Style, Layout, Paint, Composite로 이어지는 전체 렌더링 파이프라인을 실행합니다. transition이 진행되는 동안 모든 애니메이션 프레임에서 브라우저가 레이아웃을 다시 계산하며, 재계산할 때마다 새로운 레이아웃 이동 항목이 생성됩니다.

사용자가 페이지를 이용하는 동안 스크롤 방향이 바뀔 때마다 CLS 수치는 계속 상승합니다.

진단 중이던 실제 사이트의 동작을 그대로 재현한 데모를 만들어 영상을 녹화했습니다. 스크롤을 몇 번 하자 총 CLS가 0.1을 넘어섰습니다. 물론 실환경에서 사용자가 이렇게까지 스크롤하는 경우는 드물겠지만, 작동 메커니즘은 동일합니다. 실제로 이런 레이아웃 이동 때문에 임계치를 넘어 전체 CLS 평가에서 탈락하는 사이트를 여럿 목격했습니다.

애니메이션 적용 시 레이아웃 이동을 유발하는 CSS 속성은?

애니메이션이 CLS를 일으키려면 Layout 단계를 트리거해야 합니다. 브라우저의 렌더링 파이프라인은 스타일 계산 후 Layout, Paint, Composite의 세 가지 주요 단계를 거칩니다. Composite 단계에만 영향을 미치는 속성은 레이아웃 이동을 전혀 일으키지 않습니다. CSS transition이 레이아웃 이동을 유발하는 원인에 대한 자세한 내용은 관련 글을 참고하십시오.

레이아웃을 트리거하는 속성(애니메이션 시 CLS 유발):

top, left, right, bottom, width, height, margin, padding, border-width, font-size

이 속성들은 모두 요소의 기하학적 형태나 위치를 변경합니다. 브라우저는 해당 요소의 위치(경우에 따라 다른 요소의 위치까지)를 재계산해야 합니다.

레이아웃 단계를 완전히 건너뛰는 속성(애니메이션에 안전):

transform과 opacity는 컴포지터 스레드에서 실행됩니다. 레이아웃과 페인트를 모두 건너뜁니다. 브라우저가 GPU에서 이를 처리하므로 main thread가 바쁜 상황에서도 애니메이션이 원활하게 작동합니다. 레이아웃 재계산을 전혀 일으키지 않기 때문에 레이아웃 이동 항목도 발생하지 않습니다.

레이아웃 속성을 transform으로 대체하기

스크롤 시 숨겨지는 헤더의 해결책은 간단합니다. top 애니메이션을 transform: translateY()로 대체하십시오.

/* CSS - 변경 전 (CLS 발생) */
#header {
  position: fixed;
  top: 0;
  transition: top 0.3s ease;
}

/* CSS - 변경 후 (CLS 없음) */
#header {
  position: fixed;
  top: 0;
  transition: transform 0.3s ease;
}

/* JavaScript - 변경 전 */
header.style.top = '-80px';

/* JavaScript - 변경 후 */
header.style.transform = 'translateY(-80px)';

결과는 동일합니다. 헤더가 위로 밀려 올라가며 사라집니다. 하지만 transform: translateY()는 전적으로 컴포지터에서 실행되므로 레이아웃 이동을 전혀 일으키지 않습니다.

스크롤로 실행되는 모든 애니메이션에 동일한 원칙이 적용됩니다:

  • 슬라이딩 패널: left나 right 대신 transform: translateX()를 사용하십시오.
  • 확장되는 요소: height 대신 transform: scaleY()를 사용하십시오.
  • 페이드 요소: 높이 변경과 결합된 visibility 대신 opacity를 사용하십시오.

CLS를 유발하는 다른 스크롤 패턴

스크롤 시 숨겨지는 헤더는 자주 접하는 사례 중 하나일 뿐입니다. 스크롤 이벤트에 반응하여 레이아웃 속성을 변경하는 JavaScript는 예외 없이 레이아웃 이동을 유발합니다. 몇 가지 예를 들면 다음과 같습니다:

top이나 margin-top을 사용한 패럴랙스 효과. transform: translateY()로 대체하거나 순수 CSS 패럴랙스를 위해 CSS perspective 및 translateZ 방식을 사용하십시오.

스크롤 시 높이가 변하는 Sticky 내비게이션. 사용자가 기준선 이상으로 스크롤할 때 80px에서 50px로 줄어드는 내비게이션 바가 이에 해당합니다. 높이 변화에 transition: height 애니메이션을 적용했다면 transform: scaleY()로 대체하십시오.

width를 사용하는 스크롤 연동 진행률 표시줄. width 애니메이션을 통해 왼쪽에서 오른쪽으로 채워지는 읽기 진행률 표시기입니다. 대신 transform-origin: left와 함께 transform: scaleX()를 사용하십시오.

최신 대안: CSS Scroll-Driven Animations. animation-timeline: scroll() API를 사용하면 JavaScript 없이도 스크롤 진행률에 따라 애니메이션을 제어할 수 있습니다. 이러한 애니메이션은 기본적으로 transform과 opacity를 사용하며 컴포지터에서 실행되므로 레이아웃 이동을 전혀 유발하지 않습니다. 브라우저 지원 현황은 Chrome 115+, Edge 115+ 이상이며 Safari는 아직 개발 중입니다. 패럴랙스, 진행률 표시줄, 요소 표시(reveal) 애니메이션 같은 스크롤 연동 효과에는 이 방법이 가장 깔끔한 해결책입니다. JavaScript 스크롤 방식을 완전히 걷어내고 싶다면 scroll-driven animations API가 나아갈 방향입니다.

Lighthouse가 이를 감지하지 못하는 이유

Lighthouse는 시뮬레이션된 페이지 로드만 수행합니다. 페이지를 스크롤하지 않으며, 로드 후 페이지와 상호작용하지도 않습니다. 즉, 스크롤로 인한 레이아웃 이동은 lab 테스트에서 전혀 감지되지 않습니다.

이러한 이동을 잡아내는 유일한 방법은 Real User Monitoring을 활용하는 것입니다. field data는 사용자가 스크롤, 클릭, 탐색하는 동안 발생하는 모든 레이아웃 이동을 포함해 전체 페이지 라이프사이클을 기록합니다. CrUX CLS 점수가 Lighthouse CLS 점수보다 나쁘다면 스크롤 트리거 애니메이션부터 가장 먼저 점검해야 합니다. CoreDash는 스크롤로 인한 이동을 포함해 실제 방문자의 CLS를 추적하므로 어떤 요소가 원인인지 정확히 파악할 수 있습니다.

스크롤로 인한 레이아웃 이동을 찾는 방법

Chrome DevTools를 엽니다. Performance 패널로 이동합니다. 녹화 버튼(원형 아이콘)을 누른 다음 페이지를 위아래로 여러 번 스크롤합니다. 녹화를 중지합니다. Layout Shifts 트랙에서 보라색 다이아몬드 아이콘을 찾으십시오. 스크롤 동작과 맞물려 레이아웃 이동이 집중되어 있다면 스크롤로 인한 CLS 문제입니다. Core Web Vitals Visualizer Chrome 확장 프로그램을 사용해 스크롤 중 실시간으로 CLS를 확인할 수도 있습니다.

기본 원칙

스크롤할 때 움직이는 요소라면 transform으로 애니메이션을 구현하십시오. 스크롤할 때 페이드되는 요소라면 opacity로 애니메이션을 구현하십시오. 이 속성들은 컴포지터 전용입니다.

JavaScript의 경우: 스크롤 이벤트 핸들러, IntersectionObserver 콜백, 스크롤 위치에 맞춰 요소 스타일을 변경하는 모든 JavaScript 코드를 점검하십시오. 만약 top, left, margin, width, height, padding을 사용하고 있다면 이에 상응하는 transform으로 대체하십시오.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

더 빠른 사이트가 필요하신가요?

저는 Arjen입니다. 17년 동안 느린 사이트를 고쳐 왔습니다. 직접 해결하기 어려운 문제가 있다면 URL을 보내 주세요. 편하게 이야기 나눠요.

URL 보내기
Arjen Karel
Arjen Karel, 2009년부터 활동한 웹 성능 컨설턴트

수정했는데 왜 효과가 없을까요?

때로는 CDN이 아직 이전 HTML을 제공하거나, 수정이 데스크톱 템플릿에만 들어가고 모바일은 다른 템플릿을 쓰는 경우가 있습니다. URL과 문제를 보내 주시면 가능한 시간대를 몇 가지 회신해 드립니다. 먼저 field data를 살펴보고, 사이트를 1시간 함께 본 뒤, 하루 안에 정리 노트를 서면으로 보내 드립니다. €350 (부가세 별도).

URL 보내기

수정을 구현할 개발자가 없으신가요? 사이트 복사본에서 제가 직접 작업합니다.

스크롤로 실행되는 애니메이션이 CLS를 유발하는 이유 Core Web Vitals 스크롤로 실행되는 애니메이션이 CLS를 유발하는 이유