Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. JavaScript
Code

완벽한 캐러셀(carousel) 제작하기: 1부

by
Difficulty:AdvancedLength:LongLanguages:
This post is part of a series called Create the Perfect Carousel.
Create the Perfect Carousel, Part 2

Korean (한국어) translation by Jin Ah Chon (you can also view the original English article)

캐러셀(carousel)은 스트리밍 사이트와 e-커머스 사이트에서 중요한 요소입니다. 아마존과 넷플릭스 모두 캐러셀을 중요한 내비게이션 도구로 사용하고 있습니다. 이 튜토리얼에서 두 사이트의 인터랙션 디자인을 평가하고, 그 평가 결과를 완벽한 캐러셀을 실행하는데 활용해 보겠습니다.

또한 이번 튜토리얼 시리즈에서 자바스크립트 엔진 중의 하나인 Popmotion의 일부 기능을 학습할 것입니다. Tween (페이지네이션에 유용함)과 포인터 트래킹(스크롤 하는 데 유용함), 스프링 물리(spring physics, 유쾌하게 작업을 마무리할 손길에 유용함)와 같은 애니메이션 도구를 제공합니다.

1부에서는 아마존과 넷플릭스에서 스크롤을 실행한 방법을 검토해 보겠습니다. 그 다음에는 터치로 스크롤할 수 있는 캐러셀을 구현하겠습니다.

이 학습 시리즈의 마지막에서 휠(wheel)과 터치패드 스크롤, 페이지네이션, 진행 바, 키보드 내비게이션, 그리고 스프링 물리(spring physics) 을 적용한 몇 가지 터치 방식을 구현할 것입니다. 더불어 기본적인 기능 구성을 일부 보게 될 것입니다.

완벽함이란?

무엇으로 캐러셀이 “완벽하게” 될까요? 아래의 요소들이 가능하게 해줍니다.

  • 마우스: 클릭하기 쉽고 콘텐츠를 불분명하게 하지 않는 이전 버튼과 다음 버튼이 있습니다.
  • 터치: 손가락을 따라가 그 손가락이 화면에서 떼어질 때와 같은 모멘텀으로 스크롤 됩니다.
  • 스크롤 휠: 자주 지나치지만, 애플 매직 마우스와 다수의 랩탑 트랙패드에서는 부드럽게 수평으로 스크롤되는 동작이 제공됩니다. 우리가 그런 기능을 활용할 겁니다!
  • 키보드: 많은 사용자들은 내비게이션을 이용하는 데 마우스 비사용을 선호하거나 마우스를 이용하지 못합니다. 그런 사용자들도 프로덕트를 사용할 수 있게 접근 가능한 캐러셀을 만드는 것은 중요합니다.

마지막으로 단계를 더 나아가 슬라이더가 끝에 왔을 때 스프링 물리을 이용해 캐러셀이 깔끔하고 본능적으로 반응하게 만듦으로써 확신있고 마음에 드는 UX 작품이 되게 할 것입니다.

준비

먼저 이 CodePen을 클릭해서 기본 캐러셀을 만드는 데 필요한 HTML과 CSS를 가져와 보죠.

Pen에서 CSS의 전처리 언어인 Sass와 ES6 자바스크립트의 트랜스파일러(transpiling)인 Babel로 설정되어 있습니다. 저는 거기에 더해 Popmotion을 넣었는데, window.popmotion을 이용해 접근할 수 있습니다.

여러분이 원하면 로컬 프로젝트에 코드를 복사하면 됩니다. 다만 여러분의 개발 환경이 Sass와 ES6를 지원하는지 확인해 봐야 합니다. 또한 npm install popmotion을 써서 Popmotion을 설치해 주세요.

새 캐러셀 제작

어떤 페이지에서나 캐러셀은 많이 있을 수 있습니다. 고로 각각의 상태와 기능성을 캡슐화할 방식이 필요합니다.

저는 class보다 factory 함수를 사용하겠습니다. Factory 함수로 자주 혼동되는 this 키워드를 사용할 필요성을 방지하고, 이 튜토리얼의 목적에 맞게 코드를 단순화시킬 것입니다.

여러분의 자바스크립트 편집기에서 아래의 간단한 함수를 추가해 주세요.

carousel 함수 안에 캐러셀에 특정화된 코드를 넣겠습니다.

스크롤링에 관한 방식과 이유

우리의 첫번째 과제는 캐러셀 스크롤을 제작하는 것입니다. 이것을 작업하는 데 두 가지 방식이 있습니다.

브라우저 자체 스크롤링

분명한 해결책은 아마도 슬라이더에서 overflow-x: scroll로 세팅해 놓는 것이겠지요. 터치 및 수평이동 마우스 휠 디바이스를 포함해 모든 브라우저에서 네이티브 스크롤링을 할 수 있으니까요.

그러나 이 접근 방식에는 아래와 같은 단점이 있습니다.

  • container 밖에 있는 콘텐츠가 눈에 보이지 않을 수 있습니다. 우리 디자인이 제한적일 수 있습니다.
  • 끝에 왔다는 것을 알게 하는 애니메이션 사용 방법에도 제한을 줍니다.
  • 데스크톱 브라우저에는 (접근 조차 되지 않는!) 보기 싫은 가로 스크롤바가 있습니다.

그 대안은 다음과 같습니다.

translateX 애니메이션

캐러셀의 translateX 속성을 애니메이션 할 수 있습니다. 우리가 좋아하는 디자인을 정확하게 실행할 수 있으므로 매우 융통성이 있을 것입니다. translateX는 CSS의 left 속성과 달리 디바이스의 GPU에 의해 처리되기 때문에 성능 기준에도 매우 적합합니다.

부정적인 측면에서 보면, 자바스크립트로 스크롤 기능을 재실행해야 합니다. 작업이 많아지고 코드가 늘겠지요.

아마존과 넷플릭스는 어떤 방식으로 스크롤하게 했을까요?

아마존과 넷플릭스 캐러셀 둘 다 이 문제에 관해 접근하는 데 서로 다른 타협을 보았습니다.

아마존은 “데스크톱” 모드일 때 캐러셀 left 속성을 애니메이션 했습니다. left 애니메이션은 대단히 안타까운 선택인데 왜냐하면 그 속성을 변경하면 레이아웃을 재계산해야 하기 때문입니다. 이는 CPU 집약적이라서 오래된 기계에서 60fps에 이르는 것은 힘겹게 됩니다.

translateX 대신에 left 애니메이션을 결정한 이는 멍청이인 게 분명합니다 (스포일러: 과거 2012년에 제가 멍청이었습니다. 그 시절에는 이정도로 알지 못 했었죠.)

캐러셀은 터치 디바이스를 인지할 때 브라우저 자체 스크롤을 사용합니다.  이것을 “모바일” 모드에서만 가능하게 하면 수평이동 스크롤 휠을 이용하는 데스크톱 사용자들을 고려하지 않게 되는 문제가 생깁니다. 캐러셀 밖에 있는 콘텐츠가 단절되 보인다는 의미이기도 하지요.

Screenshot of Amazon illustrating lack of design bleed

넷플릭스는 캐러셀의 translateX 속성을 정확하게 애니메이션 해서 모든 디바이스에서 동작합니다. 캐러셀 밖에서 잘리는 디자인을 살릴 수 있습니다.

Screenshot of Netflix carousel illustrating design bleed

이는 돌아가며 캐러셀의 x와 y 모서리 밖에 있는 아이템들이 커지는 화려한 디자인이 가능하게 하며, 그 주변의 아이템들이 밖으로 빠지도록 이동시켜 줍니다.

Screenshot of Netflix carousel illustrating enlarged item

안타깝게도 터치 디바이스에서 넷플릭스의 스크롤 재실행은 만족스럽지 못합니다. 제스처 기반의 페이지네이션 시스템을 사용하는데 속도가 느리고 다루기가 힘들기 때문이죠. 수평으로 스크롤하는 휠을 고려하지도 않습니다.

우리가 더 잘할 수 있어요. 코딩해봅시다!

전문가처럼 스크롤하기

첫 단계에서 .slider node를 정해줍니다. 여기에 초점을 맞추는 동안 슬라이더의 크기를 알아 보도록 이를 포함한 아이템을 이용해 봅시다.

캐로슬 크기 구하기

슬라이더의 너비를 측정하면 슬라이더에서 눈에 보이는 영역을 알 수 있습니다.

그 안에 들어 있는 모든 아이템의 전체 너비도 필요합니다. 상대적으로 깔끔한 carousel 함수를 유지하기 위해 파일 상단에 아래의 계산을 별도의 함수로 넣어 둡시다.

슬라이더의 첫 번째 아이템의 left offset과 마지막 아이템의 right offset을 측정할 getBoundingClientRect를 사용함으로써 모든 아이템의 전체 너비를 구하는 데 이 둘 사이의 차이를 이용하면 됩니다.

sliderVisibleWidth으로 측정한 후에 아래 코드를 적습니다.

이제는 캐러셀이 스크롤될 최대 거리를 알 수 있습니다. 모든 아이템의 총 너비에 눈에 보이는 슬라이더의 총 너비를  값입니다. 이렇게 하면 가장 오른쪽에 있는 아이템을 슬라이더의 오른쪽으로 정렬할 수 있는 수치가 나옵니다.

이렇게 측정값이 마련되었으므로 캐러셀의 스크롤을 시작할 준비가 되었습니다.

translateX 설정

Popmotion에는 CSS 속성을 간단하고 성능에 맞춰(performant) 설정해 주는 CSS renderer가 있습니다. value 함수도 있는데, 이는 숫자를 추적하는데(track) 쓰이고, 중요한 것은 (곧 보게 되겠으나), 속도를 묻는데 쓰일 수 있습니다.

자바스크립트 파일 상단에 이렇게 import를 해 주세요.

그다음에는 minXOffset을 설정한 다음 줄에 슬라이더의 CSS renderer를 생성합니다.

그리고 슬라이더의 x offset을 추적하는 value를 생성하고, 변화가 생길 때 슬라이더의 translateX 속성을 업데이트해 주세요.

자, 슬라이더를 수평으로 움직이는 것은 작성하는 것만큼이나 간단합니다.

직접 해 보세요!

터치 스크롤

우리는 사용자가 슬라이더를 수평으로 드래그할 때 캐러셀이 스크롤을 시작하고, 사용자가 스크린을 터치할 때 멈추기를 바랍니다. 이벤트 핸들러는 이와 같이 보일 것입니다.

startTouchScroll 함수에서 다음과 같은 내용이 필요합니다.

  • sliderX를 동작하게 하는 다른 모든 동작을 멈춥니다.
  • 터치 시작점을 찾습니다.
  • 사용자가 수직이나 수평으로 드래그를 시작할지 알아보는 다음 touchmove 이벤트를 listen하게 합니다.

document.addEventListener 다음에 아래 코드를 추가하세요.

이 코드는 이동하는 슬라이더에서 (stopTouchScroll로 실행할 물리적으로 동작하는 모멘텀 스크롤과 같은) 다른 모든 동작을 멈출 것입니다. 사용자는 클릭하려고 했던 아이템이나 제목이 스크롤 되어 지나쳤다면, 즉시 슬라이더를 "붙잡"을 수 있습니다.

다음은 터치 시작점을 저장해야 합니다. 그래야 우리는 사용자가 어디에서 손가락을 그다음에 움직였는지 파악할 수 있습니다. 만약에 수직 이동이었으면, 평소대로 페이지 스크롤이 되겠지요. 수평 이동이었으면, 대신에 슬라이더가 스크롤 될 것입니다.

이벤트 핸들러 사이에 이 touchOrigin을 공유해야 합니다. 고로 let action; 다음에 아래 코드를 추가해 주세요. 

startTouchScroll 핸들러로 되돌아가 아래 코드를 추가해 주세요.

이제는 이 touchOrigin을 기준으로 해서 드래그 방향을 결정할 documenttouchmove 이벤트 핸들러를 추가하면 됩니다.

determineDragDirection function은 다음 터치 위치를 측정하고, 실제로 움직였는지 확인하며, 움직였다면 수직인지 수평인지 결정하는 각도(angle)를 측정할 것입니다.

Popmotion에는 2개의 x/y 좌표 사이의 거리와 같은 것들을 측정하는 유용한 계산 기능(calculators)이 있습니다. 아래와 같이 그 기능을 import 할 수 있습니다.

그리고 나서 두 지점 간의 거리 측정은 distance라는 계산 기능을 사용하는가의 문제입니다.

이제 터치가 이동했다면, 이 이벤트 리스너를 해제하면 됩니다.

angle 계산 기능을 사용해 두 지점 간의 각도를 측정하세요.

아래 함수에 이 각도(angle)를 전달해서 각도가 수평 혹은 수직 각도인지 판단하는 데 이 코드를 사용할 수 있습니다. 이 함수를 파일의 맨 위에 추가하세요.

이 함수는 만일 제공받은 각도가 -90 +/- 45도 (곧게 올라가는) 혹은 90 +/-45도 (곧게 내려가는) 안에 있다면 true를 반환합니다. 고로 이 함수가 true를 반환하는 경우를 위해 또 다른 return 절을 추가할 수 있습니다.

포인터 트래킹

우리는 사용자가 캐러셀을 스크롤할 것을 알고 있기에 손가락의 움직임을 추적하기 시작할 수 있습니다. Popmotion에서는 마우스나 터치 포인터의 x/y 좌표를 출력해 주는 pointer 동작을 제공합니다.

우선, pointer를 import 하세요.

터치 입력을 추적하기 위해 pointer에 시작하는(originating) 이벤트를 줍니다.

우리는 포인터의 첫 x 좌표를 측정해서 슬라이더에 모든 움직임을 적용하고 싶습니다. 그러려면 applyOffset으로 부르는 변형자(transformer)를 사용하면 됩니다.

변형자들은 값을 가져와서 그렇죠, 변환된 값을 반환하는 순수 함수(pure function)입니다.  const double = (v) => v * 2 가 하나의 예입니다.

applyOffset은 커리화된(curried) 함수입니다. 이는 함수를 호출할 때, 새로운 함수를 생성하고 그 후에 값을 전달하게 된다는 말입니다. 먼저 offset을 측정하려는 숫자를 이용해 함수를 호출하는데, 이 경우에는 action.x의 현재 값과 그 offset를 적용할 숫자입니다. 이 경우에 바로 sliderX인 것이지요.

그러니까 applyOffset 함수는 아래와 같이 보입니다.

이제는 포인터 움직임을 슬라이더에 적용할 포인터의 output 콜백에서 이 함수를 사용할 수 있습니다.

스타일을 이용해 멈추기

이제 캐러셀을 터치해서 드래그할 수 있습니다! 크롬 개발자 도구에서 디바이스 에뮬레이션을 이용해 드래그가 되는지 테스트해 보세요.

좀 엉성한 느낌이죠? 이전에 느껴본 듯한 스크롤이 떠오를지 모릅니다. 손가락을 떼니 스크롤이 순간 멈추는 것이죠. 스크롤이 순간 멈추지 않게 하려면 스크롤이 이어지는 듯이 보이도록 애니메이션을 약간 넣습니다.

여기에서는 그렇게 하지 않습니다. sliderX의 실제 속도를 취하고, 일정 시간이 지나 마찰을 적용하는 Popmotion의 physics 동작을 사용하면 됩니다.

먼저, 계속 늘어나는 import 목록에 그것을 추가하세요.

그런 후에 stopTouchScroll 함수 구문의 맨 끝에 적습니다.

여기를 보면, fromvelocity가 현재 값과 sliderX 속도를 이용해 설정되어 있습니다. 이는 확실히 physics 시뮬레이션이 사용자의 드래그 모션과 동일한 초기 출발조건을 갖도록 합니다.

friction0.2로 설정합니다. 마찰(friction)은 0부터 1까지 값이 정해집니다. 0은 마찰이 전혀 없는 상태이고 1는 절대마찰(absolute friction) 상태입니다. 이 값 위주로 테스트하면서, 사용자가 드래그를 멈출 때 캐러셀이 그 "느낌"을 주며 변하는지 확인해 보세요.

값이 작을수록 가벼운 느낌이 들고, 값이 클수록 더딘 느낌이 듭니다. 스크롤 모션에 관해 얘기하면, 저는 0.2일 때 탄성과 둔함(sluggish) 사이에서 균형을 적절히 잡고 있다고 느꼈습니다.

경계(boundaries)

하지만 문제가 있네요! 새로운 터치 캐러셀을 이것저것 테스트해 봤다면 확실합니다. 움직임에 경계를 두지 않아서 말 그대로 캐로셀을 버릴 수도 있습니다!

이 작업에 또 하나의 변형자인 clamp가 있습니다. 이 또한 커리화된(curried) 함수인데, 이 함수를 최소와 최댓값, 말하자면 01을 사용해 호출하면, 새로운 함수를 반환한다는 말입니다. 이 예제에서 새로운 함수는 주어진 모든 숫자를 01 사이에 있는 값으로 한정시켜 줍니다.

우선 clamp를 import 하세요.

이 clamp 함수를 캐러셀 전반적으로 사용하고 싶으므로 minXOffset을 정의한 다음 아래 코드를 추가하겠습니다.

pipe 변형자로 일부 약간의 기능 구성(functional composition)을 사용해 action에서 설정한 2개의 output을 수정하겠습니다.

Pipe

함수를 호출할 때 우리는 이처럼 작성합니다.

함수의 출력을 다른 함수에 전달하고 싶을 때 우리는 이처럼 코드를 짤 수 있습니다.

가독성이 약간 떨어지며, 더 많은 함수를 추가하는 경우에만  가독성이 더 떨어집니다.

pipe를 써서  foobar로부터 새로운 함수를 작성할 수 있으며 재사용이 가능합니다.

게다가 시작 -> 끝의 자연스러운 순서로 작성되어 따르기가 더 쉽습니다. applyOffsetclamp를 하나의 함수로 작성하는 데 이를 사용할 것입니다. pipe를 import 하세요.

pointeroutput 콜백을 아래와 같이 바꾸겠습니다.

physicsoutput 콜백도 아래와 같이 바꿉니다. 

이런 유형의 기능 구성은 적은 코드 분량과 재사용 가능한 함수로 인해 서술적이며 점진적인 과정을 매우 깔끔하게 만들어 줄 수 있습니다.

이제는 캐러셀을 드래그하고 튕겼을 때 그 경계 밖으로 조금도 움직이지 않을 것입니다.

갑작스러운 멈춤은 불만스럽지요. 이 문제는 다음 튜토리얼에서 다룹니다!

마무리하기

1부가 끝났습니다. 여기까지는 스크롤에 관한 다양한 접근 방식의 장단점을 알아보려고 기존의 캐러셀을 살펴 보았습니다. 터치 스크롤로 성능 기능에 맞춰 캐러셀의 translateX를 애니메이션 시키는 Popmotion의 입력 추적(input tracking)과 physics를 사용했습니다. 더불어 기능 구성과 커리화된 함수를 도입했습니다.

여러분은 이 CodePen에서 "지금까지의 줄거리(story so far)"의 주석 처리된 버전을 보실 수 있습니다.

곧 이어지는 튜토리얼에서 다음과 같은 내용을 살펴볼 것입니다.

  • 마우스 휠로 스크롤하기
  • 윈도우 크기가 변할 때 캐러셀 재측정하기
  • 키보드와 마우스 접근성을 적용한 페이지네이션
  • spring physics를 활용한 가벼운 터치

다음 튜토리얼에서 뵙기를 바래요!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.