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

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

by
Difficulty:AdvancedLength:MediumLanguages:
This post is part of a series called Create the Perfect Carousel.
Create the Perfect Carousel, Part 1
Create the Perfect Carousel, Part 3

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

완벽한 캐러셀 제작하기 튜토리얼에 다신 오신 것을 환영합니다. 자바스크립트와 Popmotion의 물리(physics), tween, 입력 추적(input tracking) 기능을 사용해 접근 가능하고 매력적인 캐러셀을 만들어 보겠습니다.

튜토리얼 1부에서는 아마존과 넷플릭스가 캐러셀을 어떤 방식으로 만들었는지 살펴보았고, 그 방식들의 장단점을 평가했습니다. 그렇게 알게 된 내용으로 캐러셀 전략을 정하고, 물리(physics)를 이용해 터치 스크롤을 구현했습니다.

2부에서는 수평으로 이동하는 마우스 스크롤을 구현하겠습니다. 보편적인 페이지네이션 기술도 살펴보고 구현할 것입니다. 마지막에는 사용자가 캐러셀을 얼마나 멀리 보냈는지 알게 해주는 진행 바(progress bar)를 연결해 보려 합니다.

이 CodePen을 열어서 저장 위치(save point)를 복구하면 됩니다. 그 지점은 멈춘 지점을 알려 줍니다.

수평이동 마우스 스크롤

자바스크립트 캐러셀에서 수평 이동 마우스 스크롤이 가능한 것은 드뭅니다. 유감스럽죠. 모멘텀에 기반한 수평 이동 스크롤을 실행하는 랩톱과 마우스에서 캐러셀을 돌아다니는데 단연코 가장 빠른 방식이거든요. 터치 사용자들에게 스와이프(swipe)보다 버튼으로 돌아다니라고 강요하는 것만큼 좋지 않습니다.

운 좋게도 코드 한두 줄이면 구현할 수 있습니다. carousel 함수의 끝에 새로운 이벤트 리스너를 추가해 주세요.

startTouchScroll 이벤트 아래 onWheel로 명명한 stub 함수를 넣으세요.

이제 캐러셀 위에서 휠로 스크롤을 하고 콘솔 패널을 확인해 보면, x축 출력값(output)에서 휠의 거리를 보게 될 것입니다.

터치를 이용하더라도 휠이 대부분 수직 방향으로 움직이면, 페이지가 평소대로 스크롤 되어야겠지요. 우리는 수평 방향으로 움직이면, 휠 마우스의 움직임을 캡처해서 캐러셀에 적용하길 바랍니다. 고로, onWheel에서 console.log를 아래 코드로 교체해 주세요.

위의 코드 블록은 스크롤이 수평으로 이동하면, 페이지 스크롤을 멈추게 합니다. 슬라이더의 x offset을 업데이트하려면 이제 이벤트의 deltaX 속성을 취하고 그것을 현재 sliderX 값에 추가하기만 하면 됩니다.

이 계산을 감싸고(wrap) 캐러셀이 측정된 경계 밖으로 스크롤 되지 않도록 이전의 clampXOffset 함수를 재사용하겠습니다.

별도로 본 스크롤 이벤트 속도 조절

입력(input) 이벤트를 처리하는 좋은 튜토리얼은 그러한 이벤트들의 속도를 조절(throttle)하는 것이 얼마나 중요한지를 설명해 줍니다. 이는 스크롤과 마우스, 터치 이벤트들이 디바이스의 프레임 속도보다 빠르게 발사되기 때문입니다.

한 프레임에 캐러셀이 두 번이나 렌더링 되는 것처럼 불필요하게 리소스에 집중된 작업을 수행하는 것을 바라지 않을 것입니다. 리소스를 낭비하고, 더딘 느낌을 주는 인터페이스를 만드는 빠른 방법이니까요.

이 튜토리얼에서는 그렇게 처리하지 않습니다. Popmotion에서 제공하는 렌더러는 아주 작은 프레임에 맞춰진 작업 스케줄러인 Framesync를 실행하기 때문이죠. 이 말인즉슨, 여러분은 한 줄에 (v) => sliderRenderer.set('x', v)를 여러 번 호출할 수 있으며, 비경제적인 렌더링은 단 한 번, 다음 프레임에서 실행됩니다.

페이지네이션

스크롤링이 끝났습니다. 이제 지금까지 사랑받지 못한 내비게이션 버튼에 생명을 불어넣어야 합니다.

이 튜토리얼은 인터랙션에 관한 것이니 맘 편히 바라는 대로 이런 버튼을 디자인해 보세요. 저는 개인적으로 방향을 가리키는 화살표가 좀 더 직관적(이고 기본적으로 꽤 전 세계적인!) 것을 알게 되었습니다.

페이지네이션은 어떻게 동작하나요?

캐러셀에서 페이지네이션 할 때 여러분이 취할 수 있는 2가지 명확한 전략이 있습니다. 개별적이거나 감춰진 첫 번째 아이템입니다. 옳은 전략은 단 하나이지만, 다른 하나가 흔히 구현되는 것을 보아왔기에, 저는 그것이 옳지 않은지를 설명할 가치가 있다고 봅니다.

1. 개별적으로

Item By Item Example

목록에서 다음 항목의 x offset을 간단히 측정하고 그만큼 칸을 애니메이션 합니다. 저는 사용자 친화성보다 단순함 때문에 선택된 가장 간단한 알고리즘이라고 추측합니다.

대다수 화면에서 한 번에 많은 항목을 보여줄 수 있으며, 사람들이 둘러보려고 하기 전에 그 항목들을 금세 훑어본다는 게 문제입니다.

전면적으로 불만스럽지 않다면 지루하게 느껴지겠죠. 이 방식이 좋은 선택이 될지 모를 단 하나의 상황은 캐러셀에 있는 항목들이 같은 너비이거나 가시적인 영역보다 좀 더 작다는 것을 때입니다.

그렇다 해도 여러 개의 항목을 볼 때, 첫 번째 항목을 숨기는 방식을 사용하는 게 낫습니다.

2. 감추어진 첫 번째 아이템

The First Obscured Item

이는 캐러셀을 이동하길 원하는 방향에서 그냥 숨겨진 첫 번째 항목을 찾고, x offset 을 가져와서 거기로 스크롤하는 방식입니다.

그렇게 해서, 사용자가 현재 보인 항목들을 전부 보았다는 가정하에 새로운 항목의 최대 개수를 가져옵니다.

더 많은 항목을 가져오므로 캐러셀은 둘러볼 클릭 수를 최소한으로 요구합니다. 빠르게 둘러보면 관심이 증가하고 사용자가 상품을 더 많이 볼 것입니다.

이벤트 리스너

먼저 이벤트 리스너를 설정해서 페이지네이션으로 이것저것 테스트할 수 있게 해봅시다.

처음에는 이전 버튼과 다음 버튼을 선택해야 합니다. carousel 함수 에 추가하세요.

그런 후에 carousel 함수 아래에 이벤트 리스너를 넣으세요.

마지막으로 이벤트 리스너 블록 바로 위에 실행할 함수를 추가하세요.

goto는 페이지네이션에 관한 로직 전부를 처리할 함수입니다. 단순히 페이지로 가고자 하는 이동 방향을 표시한 숫자를 가져옵니다. gotoNextgotoPrev는 그저 각각 1이나 -1을 사용해 이 함수를 호출합니다. 

"페이지" 계산하기

사용자는 자유롭게 이 캐러셀을 스크롤하고, n개의 아이템이 그 안에 있으며, 캐러셀은 크기가 조절될 수 있습니다. 그러니 예전 방식의 페이지 개념은 여기서 적용하지 못합니다. 페이지 수를 세지 않겠습니다.

그 대신, goto 함수가 호출될 때 delta 방향을 보고 부분적으로 숨겨질 첫 번째 아이템을 찾습니다. 바로 그것이 다음 "페이지"에서 첫 번째 아이템이 됩니다.

첫째 단계에서 슬라이더의 현재 x offset 값을 얻고, 스크롤하려는 곳까지 "이상적인" offset을 계산하기 위해 눈에 보이는 슬라이더의 전체 너비를 이용해 그 값을 사용하겠습니다. 이상적인 offset은 슬라이더의 콘텐츠로 생각할만큼 순진할 때까지 스크롤  지점입니다. 우리가 첫 번째 아이템을 찾기 시작하게 하는 좋은 지점을 제공해 줍니다.

여기에서 지나친 최적화(cheeky optimisation)를 이용하면 됩니다. targetX를 이전 튜토리얼에서 작성한 clampXOffset 함수에 제공해서 출력값(output)이 targetX와 다른지 알아볼 수 있습니다. 다르다면, targetX가 스크롤 가능한 경계 너머로 있다는 얘기이므로 가장 가까이 있는 아이템을 알아볼 필요가 없습니다. 그냥 끝까지 스크롤하는 것이죠.

가장 근접한 아이템 찾기

다음에 오는 코드가 캐러셀에 있는 아이템 모두 동일한 크기라는 가정 하에 동작한다는 것을 알고 있는 것이 중요합니다. 그 가정 하에, 모든 아이템의 크기를 측정하지 않아도 되는 것처럼 최적화를 해보겠습니다. 만약에 아이템들이 서로 다른 크기라면, 그래도 좋은 출발점이 될 것입니다.

goto 함수 위에 마지막 스니펫에 참조된 findClosestItemOffset 함수를 추가하세요.

우선, 아이템들의 너비가 얼마나 되고 아이템 사이의 공간이 얼마나 되는지를 알아야 합니다.   Element.getBoundingClientRect() 메서드에서 필요한 모든 정보를 제공해 줍니다. width는 간단히 첫 번째 아이템 요소를 측정합니다. 아이템 간의 공간 계산은 첫 번째 아이템의 right offset과 두 번째 아이템의 left offset을 재면 됩니다. 그러고 나서 후자에서 전자를 뺍니다.

함수로 전달했던 targetXdelta 변수를 이용해 재빨리 하나의 offset에서 스크롤 지점까지 계산해야 하는 모든 데이터를 갖게 됩니다.

그 계산은 targetX의 절댓값을 width + spacing으로 나누는 것입니다. 거리 안에서 딱 맞출 수 있는 정확한 아이템 수가 나옵니다.

그러고 나서 페이지네이션 방향(delta)에 따라 반올림하세요. 맞출 수 있는 정확한 아이템 수가 나옵니다. 

마지막으로, 전체 아이템이 같은 offset이 되도록 그 수를 width + spacing으로 곱해 주세요.

페이지네이션 애니메이션 하기

계산된 targetX가 있으므로 애니메이션 하면 됩니다! 이에 관해 웹 애니메이션의 일꾼인 tween을 사용하겠습니다.

잘 모르시는 분들을 위해 얘기하자면, "tween"은 between을 줄인 단어입니다.  tween은 설정된 시간에 걸쳐 하나의 값에서 다른 값으로 변합니다. 만약에 CSS transition을 사용해 봤다면, 그와 같습니다.

tween에 관해 CSS 외에 자바스크립트를 사용하는 것이 장점(과 결점!)이 많습니다. 이 예제에서 물리(physics)와 사용자 입력으로 sliderX를 애니메이션도 하기 때문에 tween을 적용하는 데 이 워크플로우를 따르는 것이 더 편합니다.

이는 또한 이후에 진행 바를 연결할 수 있고 전체 애니메이션으로 자연스럽게, 무료로 동작하게 된다는 말입니다.

먼저 Popmotion에서 tween을 import하고 싶네요.

goto 함수의 마지막에 currentX에서 targetX로 애니메이션 하는 tween을 추가하면 됩니다.

Popmotion은 기본적으로 시간300밀리세컨드로, easeeasing.easeOut으로 설정합니다. 사용자 입력에 대응하는 애니메이션에 즉각적으로 반응하는 느낌을 주도록 특별히 선택된 설정값들입니다. 그래도 맘 편히 테스트하고 여러분의 브랜드 감성에 더 들어맞는 무언가를 발견할 수 있는지 알아보세요.

진행 표시자

사용자들에게 캐러셀에서 그들의 위치를 알리는 표시자가 있다는 것은 유용합니다. 이를 위해 진행 표시자를 연결할 수 있습니다.

진행 바를 다양한 방법으로 꾸밀 수 있습니다. 이 튜토리얼에서  색상 입힌 div와 5px 높이로 만들었으며, 이전 버튼과 다음 버튼 사이에서 동작합니다. 우리가 이것을 코드와 연결했었고, 중요하며 이 튜토리얼의 중심이 되는 바를 애니메이션 하는 방식입니다.

애초에 transform: scaleX(0)으로 스타일 되지 않아서 아직은 표시자를 보지 못합니다. 진행 바의 너비를 조정하는 데 scale 트랜스폼을 사용하겠습니다. 1부에서 설명했듯이 트랜스폼은 left나 이 경우에 width와 같이 변하는 속성보다 성능 기준에 더 적절하기 때문입니다.

게다가 minXOffsetmaxXOffset 사이의 현재 sliderX 값인 퍼센트(percentage)로 scale을 정하는 코드를 쉽게 작성할 수 있습니다.

previousButton 선택자 다음에 있는 div.progress-bar를 선택하는 것으로 시작해 봅시다.

sliderRenderer를 정의하고 나서 progressBar에 대한 렌더러를 추가하면 됩니다.

이제는 진행 바의 scaleX를 업데이트하는 함수를 정의해 보죠.

getProgressFromValue라 이름 지은 calc 함수를 사용하겠습니다. 이 함수는 범위(range)와 세 번째 숫자를 가져옵니다. 이 경우에 범위는 minmaxXOffset가 됩니다. 01 사이의 숫자이며, 주어진 범위 안에 존재하는 세 번째 숫자의 progress 값을 반환합니다.

여기에서 직관에 의거해 반대로 되어야 할 때, 그 범위를 maxXOffset, minXOffset로 적었습니다. x는 음수이고 maxXOffset도 음수인 데 반해 minXOffset0이기 때문입니다. 엄밀히 따져 보면 0은 두 숫자보다 크지만, 최대치 offset을 현실적으로 표현하기에 더 작은 값입니다. 음수들이란 말이지?

진행 바가 sliderX와 정확한 비율로 업데이트되었으면 합니다. 그래서 이 줄을 변경해 보죠.

이 줄로 말입니다.

자, sliderX가 업데이트될 때마다 진행 바도 그렇게 될 것입니다.

마무리 하기

2부가 끝났네요! 가장 최근의 코드를 이 CodePen에서 보실 수 있습니다. 수평이동 휠과 스크롤, 페이지네이션, 진행 바를 성공적으로 도입했습니다.

캐러셀이 지금까지 꽤 근사한 형태가 되었죠! 마지막 튜토리얼에서 한 단계 더 발전시키겠습니다. 누구라도 사용할 수 있는 키보드로 완전히 접근 가능한 캐러셀을 만들어 보겠습니다.

더불어 사용자가 터치 스크롤이나 페이지네이션으로 경계를 지나 캐러셀을 스크롤하려고 시도할 때, 스프링을 활용한 터그(tug)를 사용해서 한두 가지 매력적인 터치를 추가하겠습니다.

그럼 거기서 보아요!

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.