[마이 프로젝트] 블로그 꾸미기: 화면은 멈추고 내용만 바뀌는 효과

스크롤해도 화면은 멈춰 있고 내용만 바뀌는 효과를 나타낸 대표 이미지
화면은 제자리인데 안의 내용만 바뀝니다.

스크롤을 내려도 화면은 제자리에 멈춰 있고, 안의 내용만 하나씩 바뀌는 효과가 있습니다. 앞 글에서 다룬 책 넘김과 같은 페이지에 들어간 두 번째 효과입니다. 원리는 생각보다 단순합니다.

긴 영역을 만들고 화면을 붙잡는다

이 효과는 두 겹으로 되어 있습니다. 바깥에 아주 긴 영역을 만들고, 그 안에 화면 높이만큼의 칸을 하나 넣어 position: sticky로 붙잡습니다.

.story {  height: 470svh;      /* 스크롤 거리를 만드는 영역. 높이가 곧 슬라이드 개수 */  position: relative;}.story-sticky {  position: sticky;  top: 0;              /* 화면 맨 위에 붙어서 안 움직인다 */  height: 100svh;  overflow: hidden;}

바깥 영역의 높이가 곧 슬라이드 개수입니다. 470svh면 화면 높이의 약 4.7배만큼 스크롤하는 동안 내용이 바뀝니다. 슬라이드를 늘리려면 이 숫자만 키우면 됩니다.

svh 단위를 쓴 이유

예시의 470svh와 100svh에 쓰인 svh는 “작은 뷰포트” 기준 높이입니다. MDN 문서는 모바일에서 주소창과 도구 막대가 펼쳐졌다 접히기 때문에 화면 크기가 달라진다고 설명하고, 세 가지 단위의 차이를 다음과 같이 구분합니다.

단위기준특징(MDN 문서)
svh작은 뷰포트브라우저 도구 막대가 펼쳐진 가장 작은 화면 기준. 값이 안정적이라 내용이 도구 막대에 가려지지 않습니다. 대신 도구 막대가 접히면 요소 주변에 여백이 생길 수 있습니다.
lvh큰 뷰포트도구 막대가 접힌 가장 큰 화면 기준. 도구 막대가 펼쳐지면 내용 일부가 가려질 수 있습니다.
dvh동적 뷰포트도구 막대 상태에 맞춰 계속 바뀜. 스크롤하는 동안 크기가 변해 화면이 흔들리고 성능에 부담이 될 수 있다고 경고합니다.
vh기본 뷰포트브라우저가 정하며, MDN은 vh가 lvh와 같다고 설명합니다.

스크롤하는 동안 높이가 흔들리면 슬라이드 경계 계산이 어긋나기 쉬우므로, 값이 고정되는 svh를 고른 이 글의 선택은 문서의 설명과 맞습니다. 슬라이드 개수만큼 높이를 키울 때는 “화면 높이의 몇 배”를 이 작은 화면 기준으로 센다는 점을 기억해 두세요.

바깥 영역과 붙잡힌 화면의 두 겹 구조를 나타낸 이미지
바깥은 길고, 안은 화면 크기로 고정됩니다.

sticky가 붙는 조건

MDN 문서에 따르면 position: sticky 요소는 먼저 평소 위치에 놓였다가, 가장 가까운 스크롤 조상과 부모 칸을 기준으로 움직임이 정해집니다. 그래서 코드가 맞는데도 안 붙는다면 아래 표의 조건부터 확인하는 것이 빠릅니다.

조건MDN 문서에 적힌 내용이 효과에서 뜻하는 것
기준값이 있어야 함top·bottom 같은 값이 하나도 없으면 해당 방향에서는 relative처럼 움직입니다.top: 0이 빠지면 화면에 붙지 않습니다.
붙는 시점기준값에 닿기 전까지는 평소처럼 움직이다가, 닿는 순간부터 그 간격에 고정됩니다.바깥 영역이 화면 맨 위에 닿을 때 안쪽 칸이 붙기 시작합니다.
부모 칸을 벗어나지 못함붙어 있는 요소는 부모 칸의 반대쪽 끝에 닿으면 더 이상 따라가지 못합니다.바깥 영역이 끝나면 안쪽 칸도 함께 사라지며 다음 내용으로 넘어갑니다.
조상의 overflowoverflow가 hidden·scroll·auto인 조상이 있으면 그 조상에 붙고, 그 조상이 스크롤되지 않으면 붙지 않습니다.바깥 영역 위쪽 어딘가에 overflow: hidden이 걸려 있는지 먼저 봅니다.

같은 문서는 스크롤하는 고정·sticky 요소가 화면을 다시 그리는 부담을 줄 수 있고, will-change: transform이 도움이 될 수 있다고도 적습니다. 다만 이 글의 예시는 그 설정을 쓰지 않았으므로 필요할 때만 검토하시면 됩니다.

스크롤한 거리를 순서로 바꾼다

화면이 붙잡혀 있는 동안 지금 몇 번째 슬라이드인지를 계산해야 합니다. 스크롤한 거리를 0에서 1 사이 값으로 바꾸고, 거기에 슬라이드 개수를 곱합니다.

const travel = clamp01((progress - coverEnd) / (1 - coverEnd));const journey = travel * (slides.length - 1);const current = Math.floor(journey);              // 지금 슬라이드const next    = Math.min(slides.length - 1, current + 1);const phase   = journey - current;                // 이 슬라이드 안에서의 진행도// 앞뒤로 여백을 둬서 딱 붙지 않게 한다const turn   = smoothstep((phase - 0.06) / 0.88);// 절반을 넘어가야 다음 것을 '현재'로 친다const active = turn < 0.52 ? current : next;

여기서 0.52라는 숫자가 중요합니다. 정확히 0.5로 두면 경계에서 슬라이드가 덜덜 떨립니다. 살짝 넘겨 두면 한 번 바뀐 뒤에는 잘 안 돌아옵니다.

smoothstep은 시작과 끝을 부드럽게 만드는 계산입니다. 이게 없으면 바뀌는 순간이 뚝 끊깁니다.

바뀔 때 애니메이션을 붙인다

내용이 바뀌는 순간에 짧은 전환을 넣었습니다. 방법은 간단합니다. 같은 칸에 겹쳐 두고 key를 바꿔 요소를 새로 만들면, 새로 생긴 요소에 애니메이션이 자동으로 붙습니다.

@keyframes copy-swap {  from { opacity: 0; transform: translateY(7px); }  to   { opacity: 1; transform: none; }}.lesson-copy {  animation: copy-swap .3s cubic-bezier(.2,.7,.2,1) both;  grid-area: 1 / 1;    /* 같은 칸에 겹친다 */}

MDN 문서에 따르면 @keyframes의 from은 0%, to는 100%와 같은 뜻이고, 시작이나 끝 상태를 적지 않으면 브라우저가 요소의 현재 스타일을 그 자리에 씁니다. 위 예시에서 to의 transform: none이 “원래 자리로 돌아온다”는 뜻으로 읽히는 이유입니다.

스크롤 슬라이드 효과를 만드는 세 단계를 정리한 설명 이미지
세 단계가 각각 다른 문제를 해결합니다.

진행 표시도 같이 만들었다

지금 몇 번째인지 보여 주는 점을 아래에 뒀습니다. 현재 위치만 가로로 길어지게 했습니다. 점 다섯 개가 같은 모양이면 어디쯤인지 알기 어렵습니다.

움직임을 줄이는 사용자를 위한 설정

스크롤에 맞춰 내용이 바뀌고 움직이는 효과는 어떤 사람에게는 불편할 수 있습니다. MDN 문서는 prefers-reduced-motion 미디어 기능으로 사용자가 기기에서 “움직임 줄이기”를 켰는지 알 수 있고, 값이 reduce이면 애니메이션을 줄이거나 다른 방식으로 바꿀 수 있다고 안내합니다. 문서는 확대·이동하는 큰 움직임이 전정 기관 질환이 있는 사람에게 불편을 줄 수 있다고 설명합니다.

@media (prefers-reduced-motion: reduce) {
  .lesson-copy {
    animation: none;   /* 움직임 줄이기를 켠 사용자에게는 전환을 끈다 */
  }
}

Microsoft Edge 개발자 문서도 같은 방식으로 reduce일 때 애니메이션을 끄는 예시를 보여 줍니다. 운영체제 설정을 바꾸지 않고 시험하려면 개발자 도구를 열고 명령 메뉴(Windows는 Ctrl+Shift+P, macOS는 Command+Shift+P)에서 Emulate CSS prefers-reduced-motion을 고른 뒤 페이지를 새로고침해 애니메이션이 멈추는지 보면 됩니다.

증상먼저 볼 곳근거
화면이 안 붙고 같이 스크롤됨top 값이 있는지, 위쪽 조상에 overflow: hidden이 있는지MDN position 문서
모바일에서 아래가 주소창에 가림높이를 vh 대신 svh로 지정MDN 길이 단위 문서
슬라이드 수를 바꿨는데 끝이 어긋남바깥 영역 높이(470svh)를 개수에 맞게 수정이 글의 한계 항목
움직임이 불편하다는 사용자가 있음prefers-reduced-motion: reduce에서 전환 끄기MDN, Microsoft Learn

한계

스크롤 거리를 높이로 고정해 뒀습니다. 슬라이드 개수가 바뀌면 CSS의 470svh도 같이 고쳐야 합니다. 지금은 개수가 잘 안 바뀌어서 그대로 뒀지만, 자주 바뀐다면 자바스크립트로 계산해 넣는 게 맞습니다.

그리고 스크롤 방향을 가로채지는 않습니다. 사용자가 빠르게 훑어 내리면 중간 슬라이드는 그냥 지나갑니다. 이건 일부러 그렇게 뒀습니다. 스크롤을 붙잡는 페이지는 답답하다는 인상을 주기 쉽습니다.

확인 자료

확인 날짜: 2026-10-18