당겨서 새로고침 및 오버플로 효과를 맞춤설정하여 스크롤을 관리하세요

에릭 비델만
마지드 발리푸르

요약

CSS overscroll-behavior 속성을 사용하면 개발자가 콘텐츠의 상단/하단에 도달했을 때 브라우저의 기본 오버플로 스크롤 동작을 재정의할 수 있습니다. 사용 사례로는 모바일에서 당겨서 새로고침 기능 사용 중지, 오버스크롤 발광 효과 및 러버밴딩 효과 제거, 모달/오버레이 아래에 있는 페이지 콘텐츠가 스크롤되지 않도록 하는 방법 등이 있습니다.

배경

스크롤 경계 및 스크롤 체이닝

Chrome Android의 스크롤 체인

스크롤은 페이지와 상호작용하는 가장 기본적인 방법 중 하나이지만 일부 UX 패턴은 브라우저의 기발한 기본 동작으로 인해 처리하기 어려울 수 있습니다. 사용자가 스크롤해야 할 수 있는 항목이 많은 앱 검색 창을 예로 들어보겠습니다. 하단에 도달하면 사용할 콘텐츠가 더 이상 없기 때문에 오버플로 컨테이너가 스크롤을 중지합니다. 즉, 사용자가 '스크롤 경계'에 도달한 것입니다. 하지만 사용자가 계속 스크롤하면 어떻게 되는지 살펴보세요. 뒤의 콘텐츠가 스크롤되기 시작합니다. 스크롤은 상위 컨테이너(이 예에서는 기본 페이지 자체)가 우선합니다.

이 동작을 스크롤 체이닝이라고 합니다. 이는 콘텐츠를 스크롤할 때 브라우저의 기본 동작입니다. 기본값은 꽤 괜찮지만 어떤 경우에는 바람직하지도 예기치 못한 상황도 아닙니다. 일부 앱에서는 사용자가 스크롤 경계에 도달하면 다른 사용자 환경을 제공하려고 할 수 있습니다.

당겨서 새로고침 효과

당겨서 새로고침은 Facebook 및 트위터와 같은 모바일 앱에서 널리 사용되는 직관적인 동작입니다. 소셜 피드를 아래로 내렸다가 해제하면 최근 게시물을 로드할 수 있는 새로운 공간이 만들어집니다. 실제로 이 특정 UX가 인기를 얻으면 Android의 Chrome과 같은 모바일 브라우저에서도 동일한 효과를 채택했습니다. 페이지 상단에서 아래로 스와이프하면 전체 페이지가 새로고침됩니다.

PWA에서 피드를 새로고침할 때
트위터의 맞춤 당겨서 새로고침
Chrome Android의 기본 당겨서 새로고침 작업은
전체 페이지를 새로고침합니다.

Twitter PWA와 같은 상황에서는 네이티브 당겨서 새로고침 작업을 사용 중지하는 것이 좋습니다. 왜냐하면 이 앱에서는 사용자가 실수로 페이지를 새로고침하는 것을 원하지 않을 것입니다. 이중 새로고침 애니메이션이 표시될 수도 있습니다. 또는 브라우저의 동작을 맞춤설정하여 사이트의 브랜딩에 더 가깝게 맞추는 것이 더 좋을 수도 있습니다. 안타깝게도 이러한 유형의 맞춤설정은 까다로웠습니다. 개발자는 결국 불필요한 자바스크립트를 작성하거나, 스크롤을 차단하는 비수동 터치 리스너를 추가하거나, 페이지 오버플로를 방지하기 위해 100vw/vh <div>로 전체 페이지를 고정합니다. 이러한 해결 방법은 스크롤 성능에 잘 문서화된 부정적인 영향을 미칩니다.

더 잘할 수 있어요!

overscroll-behavior 소개

overscroll-behavior 속성은 컨테이너 (페이지 자체 포함)를 오버스크롤할 때 발생하는 동작을 제어하는 새로운 CSS 기능입니다. 이를 사용하여 스크롤 체인을 취소하고, 당겨서 새로고침 작업을 사용 중지/맞춤설정하고, iOS에서 러버밴딩 효과를 사용 중지하는 등의 작업을 할 수 있습니다 (Safari에서 overscroll-behavior를 구현하는 경우). 가장 좋은 점은 overscroll-behavior를 사용해도 소개에서 언급한 꿀팁처럼 페이지 성능에 부정적인 영향을 미치지 않는다는 점입니다.

이 속성은 다음과 같은 3가지 값을 사용할 수 있습니다.

  1. auto - 기본값입니다. 요소에서 시작된 스크롤은 상위 요소로 전파될 수 있습니다.
  2. contain - 스크롤 체이닝을 방지합니다. 스크롤은 상위 항목으로 전파되지 않지만 노드 내의 로컬 효과가 표시됩니다. 예를 들어 Android의 오버스크롤 발광 효과 또는 iOS의 러버밴딩 효과는 사용자가 스크롤 경계에 도달했을 때 이를 사용자에게 알립니다. 참고: html 요소에서 overscroll-behavior: contain를 사용하면 오버스크롤 탐색 작업이 방지됩니다.
  3. none - contain와 같지만 노드 자체 내의 오버스크롤 효과 (예: Android 오버스크롤 발광 효과 또는 iOS 러버밴딩)도 방지합니다.

몇 가지 예를 통해 overscroll-behavior를 사용하는 방법을 알아보겠습니다.

스크롤이 고정된 위치 요소를 이스케이프하지 않도록 방지

채팅 상자 시나리오

채팅 창 아래의 콘텐츠도 스크롤됩니다. :(

채팅 상자가 페이지 하단에 고정되어 있다고 생각해 보세요. 채팅 상자가 독립적인 구성요소이며 그 뒤에 있는 콘텐츠와 별도로 스크롤되도록 하기 위한 것입니다. 그러나 스크롤 체인으로 인해 사용자가 채팅 기록에서 마지막 메시지를 클릭하는 즉시 문서가 스크롤되기 시작합니다.

이 앱의 경우 챗박스 내에서 발생한 스크롤이 채팅 내에 유지되도록 하는 것이 더 좋습니다. 채팅 메시지가 포함된 요소에 overscroll-behavior: contain를 추가하면 됩니다.

#chat .msgs {
  overflow: auto;
  overscroll-behavior: contain;
  height: 300px;
}

기본적으로 채팅 상자의 스크롤 컨텍스트와 기본 페이지를 논리적으로 분리합니다. 결과적으로 사용자가 채팅 기록의 상단/하단에 도달했을 때 기본 페이지는 그대로 유지됩니다. 챗박스에서 시작하는 스크롤은 전파되지 않습니다.

페이지 오버레이 시나리오

'언더스크롤' 시나리오의 또 다른 변형은 콘텐츠가 고정 위치 오버레이 뒤에서 스크롤되는 경우입니다. 이제 overscroll-behavior이(가) 준비되었습니다. 브라우저는 유용한 정보를 제공하려고 하지만 사이트에 버그가 있어 보입니다.

: overscroll-behavior: contain가 있는 모달과 없는 모달:

변경 전: 페이지 콘텐츠가 오버레이 아래로 스크롤됩니다.
이후: 페이지 콘텐츠가 오버레이 아래로 스크롤되지 않습니다.

당겨서 새로고침 사용 중지

당겨서 새로고침 작업을 사용 중지하는 것은 CSS에서 한 줄로 이루어집니다. 전체 표시 영역을 정의하는 요소에서 스크롤 체인을 방지하기만 하면 됩니다. 대부분의 경우 <html> 또는 <body>입니다.

body {
  /* Disables pull-to-refresh but allows overscroll glow effects. */
  overscroll-behavior-y: contain;
}

이 간단한 추가 방법으로 채팅 상자 데모에서 이중 당겨서 새로고침 애니메이션을 수정했으며 대신 깔끔한 로드 애니메이션을 사용하는 맞춤 효과를 구현할 수 있습니다. 받은편지함이 새로고침되면 받은편지함 전체도 흐리게 표시됩니다.

이전
이후

다음은 전체 코드의 스니펫입니다.

<style>
  body.refreshing #inbox {
    filter: blur(1px);
    touch-action: none; /* prevent scrolling */
  }
  body.refreshing .refresher {
    transform: translate3d(0,150%,0) scale(1);
    z-index: 1;
  }
  .refresher {
    --refresh-width: 55px;
    pointer-events: none;
    width: var(--refresh-width);
    height: var(--refresh-width);
    border-radius: 50%;
    position: absolute;
    transition: all 300ms cubic-bezier(0,0,0.2,1);
    will-change: transform, opacity;
    ...
  }
</style>

<div class="refresher">
  <div class="loading-bar"></div>
  <div class="loading-bar"></div>
  <div class="loading-bar"></div>
  <div class="loading-bar"></div>
</div>

<section id="inbox"><!-- msgs --></section>

<script>
  let _startY;
  const inbox = document.querySelector('#inbox');

  inbox.addEventListener('touchstart', e => {
    _startY = e.touches[0].pageY;
  }, {passive: true});

  inbox.addEventListener('touchmove', e => {
    const y = e.touches[0].pageY;
    // Activate custom pull-to-refresh effects when at the top of the container
    // and user is scrolling up.
    if (document.scrollingElement.scrollTop === 0 && y > _startY &&
        !document.body.classList.contains('refreshing')) {
      // refresh inbox.
    }
  }, {passive: true});
</script>

오버스크롤 발광 효과 및 러버밴딩 효과 사용 중지

스크롤 경계에 도달할 때 바운스 효과를 사용 중지하려면 overscroll-behavior-y: none를 사용합니다.

body {
  /* Disables pull-to-refresh and overscroll glow effect.
     Still keeps swipe navigations. */
  overscroll-behavior-y: none;
}
변경 전: 스크롤 경계를 누르면 발광 효과가 표시됩니다.
이후: 발광 효과가 사용 중지되었습니다.

전체 데모

전체 챗박스 데모에서는 overscroll-behavior를 사용하여 당겨서 새로고침 애니메이션을 만들고 스크롤이 채팅 상자 위젯을 이스케이프하지 못하도록 합니다. 이는 CSS overscroll-behavior가 없었다면 달성하기 어려웠을 최적의 사용자 환경을 제공합니다.

데모 보기 | 소스