- Published on
Safari 스크롤 끊김 - 레이어·컴포지팅 점검법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버나 API가 느린 것도 아닌데 Safari에서만 스크롤이 유독 끊기는 경우가 있습니다. 특히 iOS Safari는 스크롤이 메인 스레드와 렌더링 파이프라인 영향을 크게 받기 때문에, 같은 코드라도 Chrome에서는 멀쩡하고 Safari에서만 프레임 드랍이 발생하기도 합니다.
이 글은 “스크롤 중에 무엇이 다시 그려지고(페인트), 무엇이 다시 배치되고(레이아웃), 무엇이 합성되는지(컴포지팅)”를 기준으로 Safari 스크롤 끊김을 진단하는 방법을 정리합니다. 결론적으로는 레이어 승격을 남발하기보다, 합성 경계를 안정적으로 만들고 스크롤 중 변하는 스타일을 줄이는 쪽이 재현성과 효과가 좋습니다.
관련해서 iOS Safari에 좀 더 초점을 둔 튜닝은 아래 글도 함께 참고하면 좋습니다.
Safari에서 특히 스크롤이 끊기는 전형적인 패턴
Safari 스크롤 끊김은 대체로 아래 패턴 중 하나로 수렴합니다.
- 스크롤 중 특정 요소가 계속 페인트됨
- 스크롤 중 레이아웃이 자주 발생함
- 컴포지팅 레이어가 불안정하게 생성·파괴됨
position: sticky또는 오버레이 요소가 합성 경계를 깨뜨림backdrop-filter,filter,mix-blend-mode같은 비싼 효과가 스크롤 중 계속 적용됨- 큰 이미지나 그림자(
box-shadow)가 스크롤 중 계속 다시 그려짐
여기서 핵심은 “스크롤은 가능한 한 합성(컴포지터) 단계에서만 처리되고, 메인 스레드는 놀게 만드는 것”입니다. Safari에서 스크롤이 끊긴다는 건, 스크롤 중 메인 스레드가 레이아웃·페인트·JS로 바빠졌다는 신호인 경우가 많습니다.
진단 1: 스크롤 중 페인트가 과도한지 확인
가장 흔한 원인은 “스크롤할 때마다 다시 칠해지는 영역이 넓다”입니다. 예를 들어 고정 헤더 위에 반투명 배경, 블러, 그림자, 큰 배경 이미지가 얹혀 있으면 스크롤 때마다 합성 비용이 튀거나 페인트가 반복될 수 있습니다.
체크리스트
- 고정 헤더에
backdrop-filter가 있는가 - 섹션 배경에 큰
box-shadow를 사용하고 있는가 - 스크롤 영역 위에 투명한 오버레이가 겹쳐 있는가
background-attachment: fixed를 쓰고 있는가 (Safari에서 특히 위험)
개선 예시: 블러를 “스크롤 영역 밖”으로 분리
backdrop-filter 자체가 나쁘다기보다, 스크롤되는 컨텐츠와 같은 합성 그룹에 묶이면 비용이 커집니다. 가능하면 블러 영역을 고정 레이어로 분리하고, 스크롤되는 컨텐츠가 그 아래에서만 움직이게 구성합니다.
.header {
position: sticky;
top: 0;
z-index: 100;
/* 비용이 큰 효과: 필요 범위를 최소화 */
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
/* 헤더 내부 레이아웃 변화를 최소화 */
contain: layout paint;
}
.main {
/* 스크롤 컨텐츠는 별도 영역 */
position: relative;
}
contain: layout paint 는 범위를 잘못 잡으면 오히려 부작용이 날 수 있지만, “헤더 내부에서 발생하는 레이아웃/페인트가 페이지 전체로 전파되는 것”을 막는 데 도움이 됩니다.
진단 2: 레이아웃 스래싱과 스크롤 핸들러 점검
Safari에서 스크롤 중 JS가 개입하면 프레임을 쉽게 잃습니다. 특히 스크롤 이벤트에서 매 프레임 DOM 측정(getBoundingClientRect)과 스타일 변경을 섞으면 레이아웃 스래싱이 발생합니다.
나쁜 예: 스크롤마다 측정하고 즉시 스타일 변경
window.addEventListener('scroll', () => {
const el = document.querySelector('.hero');
const rect = el.getBoundingClientRect();
// 측정 직후 스타일 변경은 레이아웃 스래싱을 유발할 수 있음
el.style.transform = `translate3d(0, ${rect.top * 0.2}px, 0)`;
});
개선 예: requestAnimationFrame 으로 배치
const el = document.querySelector('.hero');
let ticking = false;
window.addEventListener(
'scroll',
() => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
const y = window.scrollY || document.documentElement.scrollTop;
// 측정 최소화, transform 중심으로 변경
el.style.transform = `translate3d(0, ${y * 0.2}px, 0)`;
ticking = false;
});
},
{ passive: true }
);
포인트는 아래 3가지입니다.
- 스크롤 이벤트는
{ passive: true }로 브라우저 최적화를 방해하지 않기 - 측정과 변경을 한 프레임 안에 무분별하게 섞지 않기
- 변경은
top같은 레이아웃 유발 속성보다transform위주로 하기
진단 3: 레이어 승격이 과하거나 불안정한지 확인
Safari에서 “레이어가 너무 많아도” 문제가 됩니다. 흔히 will-change: transform 을 여기저기 붙여서 해결하려다, 오히려 메모리와 합성 비용이 증가해 스크롤이 더 끊깁니다.
will-change 는 수술 도구처럼 제한적으로
.card {
/* 기본은 승격하지 않는다 */
}
.card.is-animating {
/* 애니메이션 중에만 잠깐 승격 */
will-change: transform;
}
const card = document.querySelector('.card');
card.addEventListener('mouseenter', () => {
card.classList.add('is-animating');
});
card.addEventListener('mouseleave', () => {
// 애니메이션이 끝난 뒤 제거하는 방식도 고려
card.classList.remove('is-animating');
});
레이어 승격은 “스크롤 중 계속 움직이는 요소”나 “자주 변형되는 요소”에만 최소 적용하는 게 안전합니다.
transform: translate3d(0,0,0) 강제 승격은 최후의 수단
예전에는 translateZ(0) 같은 트릭이 자주 쓰였지만, Safari에서는 레이어가 늘어나는 부작용이 더 크게 나타날 수 있습니다. 진짜로 필요한 요소에만 적용하고, 적용 전후로 스크롤 프레임이 개선되는지 확인해야 합니다.
진단 4: position: sticky 와 오버레이 조합
Safari에서 position: sticky 는 편리하지만, 아래 조합에서 스크롤 끊김이 잘 발생합니다.
- sticky 요소 위에
backdrop-filter또는 큰 그림자 - sticky 요소의 자식이 자주 리렌더링되는 UI (예: 스크롤 위치에 따라 텍스트/아이콘이 계속 변경)
- sticky 요소가
overflow컨테이너 안에 있음
개선 방향
- sticky 내부의 DOM 변경을 최소화하고, 상태 변화는
opacity나transform중심으로 - sticky 컨테이너의
overflow구조를 단순화 - 그림자/블러는 면적을 줄이거나 정적인 이미지로 대체 고려
예를 들어 그림자를 실시간으로 그리는 대신, 얇은 그라데이션 보더를 쓰는 방식이 체감 성능에 큰 차이를 만들기도 합니다.
.sticky-header {
position: sticky;
top: 0;
z-index: 10;
/* box-shadow 대신 그라데이션으로 대체 */
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.95),
rgba(255, 255, 255, 0.85)
);
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
진단 5: 스크롤 컨테이너 overflow: auto 남발
iOS Safari는 중첩 스크롤 컨테이너가 많을수록 터치 스크롤과 합성에서 불리해지는 경우가 있습니다. 특히 모달, 드로어, 내부 리스트가 각각 overflow: auto 를 가지면, 스크롤 체인이 복잡해지고 repaint 영역이 커질 수 있습니다.
개선 팁
- 가능하면 “페이지 스크롤 1개”로 단순화
- 모달 내부 스크롤이 필요하면, 모달 외부 배경은 고정하고 내부만 스크롤되게 하되 레이어 경계를 명확히
.modal {
position: fixed;
inset: 0;
display: grid;
place-items: center;
}
.modal__panel {
width: min(720px, 92vw);
max-height: 80vh;
overflow: auto;
-webkit-overflow-scrolling: touch;
/* 내부 레이아웃 전파 최소화 */
contain: content;
}
-webkit-overflow-scrolling: touch 는 상황에 따라 이득이 있지만 만능은 아닙니다. 스크롤 끊김이 심한 케이스에서는 켜고 끄면서 실제 기기에서 비교하는 게 좋습니다.
진단 6: 이미지·리치 콘텐츠가 스크롤 중 디코딩을 유발
스크롤 중 갑자기 끊기는 구간이 “이미지 많은 섹션”과 겹친다면, 디코딩/리사이즈 비용이 튈 수 있습니다.
- 너무 큰 원본 이미지를 작은 영역에 표시
object-fit: cover로 큰 이미지를 계속 리샘플링- 스크롤 진입 시점에 한꺼번에 로딩
개선 예: content-visibility 와 지연 렌더링
Safari 지원이 완벽하진 않지만, 점진적으로 적용하거나 폴백을 두면 도움이 될 수 있습니다.
.section {
content-visibility: auto;
contain-intrinsic-size: 1px 800px;
}
또한 Next.js를 쓰는 경우 이미지 최적화가 성능에 직접 영향을 줄 수 있습니다. 스크롤 끊김이 “렌더링”이 아니라 “리소스 처리”에서 오는지 같이 확인해보세요.
Safari에서 효과가 좋은 “안전한” 최적화 우선순위
아래는 체감 개선이 잘 나오면서도 부작용이 상대적으로 적은 순서입니다.
- 스크롤 중 DOM 변경을 줄이고, 변경은
transform/opacity로 제한 - 스크롤 이벤트는
requestAnimationFrame+{ passive: true } - 고정 헤더/오버레이의 블러·그림자 면적 축소
contain으로 레이아웃/페인트 전파 범위 줄이기 (작은 컴포넌트부터)will-change는 애니메이션 중에만 제한적으로- 중첩 스크롤 컨테이너 단순화
여기서 4번과 5번은 “잘못 적용하면 더 느려질 수 있는” 영역이라, 적용 전후를 반드시 실제 Safari 기기에서 비교해야 합니다.
재현 기반으로 확인하는 최소 실험 방법
스크롤 끊김은 원인이 복합적인 경우가 많아, 한 번에 여러 최적화를 넣으면 무엇이 효과였는지 알기 어렵습니다. 아래처럼 실험 단위를 쪼개면 원인 규명이 빨라집니다.
실험 플래그로 CSS를 토글
html[data-perf='baseline'] .sticky-header {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
html[data-perf='no-blur'] .sticky-header {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
html[data-perf='contain'] .sticky-header {
contain: layout paint;
}
// URL 쿼리로 빠르게 전환
const params = new URLSearchParams(location.search);
const perf = params.get('perf') || 'baseline';
document.documentElement.dataset.perf = perf;
이렇게 하면 Safari에서 perf=no-blur, perf=contain 같은 조합으로 바로 비교할 수 있고, 팀 내 공유도 쉬워집니다.
마무리: “레이어를 늘리는 해결”보다 “변화를 줄이는 설계”
Safari 스크롤 끊김을 다룰 때 흔히 will-change 나 강제 3D 변환으로 레이어를 늘리는 접근을 먼저 시도하지만, 장기적으로는 유지보수와 재현성이 떨어집니다. 스크롤 중에 변하는 요소를 줄이고, 고정 요소의 페인트 면적을 줄이며, 레이아웃 전파를 차단하는 방향이 더 안정적입니다.
마지막으로, iOS Safari에서의 레이어·contain 중심 튜닝은 아래 글에서 더 구체적인 케이스를 다룹니다.