[CSS] scroll-snap이 이상해요

thru·2024년 1월 30일
1
scroll-snap + transform = scroll-trap


서론

개인 프로젝트를 만들다가 사이드바에 스크롤 효과를 넣으면서 CSS에 scroll-snap을 처음 써보았다. 그런데 다른 CSS 속성 중 transition과 충돌해 제대로 작동하지 않는 이슈가 있었고 공유하고자 한다.


Scroll Snap

스크롤 스냅은 스크롤 컨테이너 내부에서 스크롤 위치를 정렬시키는 기능을 한다. 스크롤 동작이 정지하면 스크롤 위치를 하위요소에 맞게 자동으로 이동시켜준다.

CSS 속성에 따라 브라우저가 실행시키는 방식이므로 기존에 자바스크립트의 스크롤 이벤트와 요소 좌표를 이용해서 정렬하던 방식보다 편하게 추가할 수 있다.

또한, 스크롤을 발생시키는 이벤트들(wheel, touchmove, scrollTo 등)을 아울러서 snap을 적용하는 것을 목적으로 만들어졌기 때문에 개발자가 신경쓸 게 줄어들기도 한다.

이론적으로는.

설정 방법

간단하게 스크롤 컨테이너에는 scroll-snap-type을 지정하고, 스크롤 아이템에는 scroll-snap-align을 지정해주면 작동한다.

<div class="sidebar__inner">
  ${sidebarItems
    .map(({ name, icon }, index) =>
        `<div class="sidebar-item sidebar-item__${index}">
            ${name} ${icon}
        </div>`,
    )
    .join('')}
</div>
.sidebar__inner {
  width: 18rem;
  height: 100%;
  overflow-y: scroll;
  scroll-snap-type: y proximity;
}

.sidebar-item {
  scroll-snap-align: start;
}

컨테이너의 overflow 속성은 스크롤을 위해 autoscroll으로 지정되어있어야 한다.

사용처

scroll-snap 관련 속성을 처음 보아서 실제로 사용하는 지 궁금했다.

Carousel이나 Slider 기능을 제공하는 가장 유명한 라이브러리인 Swiper에서도 관련 옵션이 존재한다.

/* CSS Mode */
.swiper-css-mode {
  > .swiper-wrapper {
    overflow: auto;
    scrollbar-width: none; /* For Firefox */
    -ms-overflow-style: none; /* For Internet Explorer and Edge */
    &::-webkit-scrollbar {
      display: none;
    }
  }
  > .swiper-wrapper > .swiper-slide {
    scroll-snap-align: start start;
  }
  &.swiper-horizontal {
    > .swiper-wrapper {
      scroll-snap-type: x mandatory;
    }
  }
  &.swiper-vertical {
    > .swiper-wrapper {
      scroll-snap-type: y mandatory;
    }

위처럼 wrapper에는 scroll-snap-type이, slide엔 scroll-snap-align이 지정되어있다. 다만 CSS mode는 기본적으로 비활성이라서 실제로 Swiper를 사용할 때는 대부분 JS 기반으로 돌아갔을 것이다.

공식 문서에서는 CSS mode가 Swiper의 모든 기능에 호환되지는 않지만 성능에 높은 이점이 있다고 소개한다.


버그 발견

이벤트나 좌표 계산 처리없이 편하게 스크롤을 정렬할 수 있어서 사이드바 항목에 scroll-snap을 적용했다. 처음엔 정상적으로 작동했지만 이후 transition을 적용하면서 문제가 발생했다.

.sidebar-item {
  scroll-snap-align: start;
  transition: all 0.35s ease;
  transform: translateX(-15.7rem) scale(0.7);

  &__0 {
    transform: translateX(0rem) scale(1);
  }
  &__1 {
    transform: translateX(-10rem) scale(0.9);
  }
  &__2 {
    transform: translateX(-13rem) scale(0.8);
  }
  &__3 {
    transform: translateX(-14.4rem) scale(0.75);
  }
  &__4 {
    transform: translateX(-15rem) scale(0.72);
  }
  &__-1 {
    transform: translateX(-10rem) scale(0.9);
  }
}

/*
 * 스크롤이 변경될 때 viewport에 보이는 순서에 따라 class가 순서대로 toggle되도록 구현되어 있음
 */

스크롤이 scroll-snap-align이 설정된 요소에 갖혀 진행되지 못하고 있다.


우회법

transform 자식 요소로 이관

한 요소에 scroll-snap-aligntransform이 같이 붙어있는 게 원인이 아닐까 싶어서 분리했다.

${sidebarItems
  .map(
    ({ name, icon }, index) =>
      `<div 
        class="sidebar-item sidebar-item__${index}" >
          <a>${name} ${icon}</a>
      </div>`,
  )
  .join('')}
.sidebar-item {
  scroll-snap-align: start;

  > a {
    transition: all 0.35s ease;
    transform: translateX(-15.7rem) scale(0.7);
  }

  &__0 > a {
    transform: translateX(0rem) scale(1);
  }
  &__1 > a {
    transform: translateX(-10rem) scale(0.9);
  }
  // 이하 생략
}

정상적으로 작동하는 걸 확인할 수 있었다.

transform 방향

위 방법으로 문제가 해결된 것 같았는데 간헐적으로 문제가 다시 발생했다. 원인은 기존과 반대 방향으로 이동하는 translateX 속성이었다.

.sidebar-item {
  scroll-snap-align: start;

  > a {
    transition: all 0.35s ease;
    transform: translateX(-15.7rem) scale(0.7);
  }
  
  &__0 > a {
    transform: translateX(0rem) scale(1);

    &:hover {
      transform: translateX(1rem); /** @here! */
    }
  }
  &__1 > a {
    transform: translateX(-10rem) scale(0.9);
  }
  &__2 > a {
    transform: translateX(-13rem) scale(0.8);
  }
  // 이하 생략

hovertranslateX값을 다른 선택자들 처럼 음수로 바꾸니 해결되었다. hover를 아예 지우고 테스트해봐도 원인은 반대 방향 translateX 였다.

얘 때문에 어이없어서 이 글을 쓰기로 했다.

추측

신기하게도 translateX가 아니라 positionleft를 사용해서 같은 기능을 만들어도 left가 반대방향이 되면 똑같이 문제가 생긴다.

reflow가 원인이었다면 margin 설정이나 같은 방향 left라도 문제가 나타나야하는데 그렇진 않았다.

transform의 옵션 중 rotateskewX는 방향이 같아도 문제가 나타났다. scale은 영향이 없는 것으로 보였다.

일부 CSS 속성은 어떤 조건에서 브라우저 엔진의 스크롤 연관 프로세스를 초기화시키는 사이드 이펙트가 있는 게 아닐까 추측한다.


결론

scroll-snap은 복잡한 애니메이션이 없는 간단한 요소에만 사용하자.

위 내용은 모두 크로미움 기반인 웨일 브라우저 시점의 기록이다. 크롬은 몰라도 사파리나 모바일에선 어떻게 작동할 지 두렵다.


참조

profile
프론트 공부 중

0개의 댓글