스킨스쿨 다이어리 앱에 로그인 하시면 보이는 온보딩화면에서 만날 수 있는 계속해서 반복되는 슬라이드 화면이 있어요.

이 슬라이드는 GIF를 이용하지 않고 JavaScript 코드로 구현되었어요.
(사실 디자이너에게 GIF 달라고했는데 거절당해서..)
무튼! 이 글에선 쉬지않고 계속 반복되는 슬라이드를 개발한 과정을 공유해볼게요.
“어떻게 하면 이 슬라이드를 구현할 수 있을까?” 고민에 빠지게 되었는데요. 가로형으로 반복되는 슬라이드는 제작해 보았지만, 대각선으로 된 슬라이드는 처음이었거든요.
고민 중 떠오른 것이 제작했던 가로형 슬라이드의 코드를 응용하여 wrap 안에 여러 개의 div를 만들고 Transform: rotate(00 deg)를 한 후 여러 개의 가로 슬라이드를 만들어 루프 시키면 된다 생각해 보았어요.
예시 )

(실제로는 예시 이미지보다 내부 슬라이드 영역을 더 크게 설정해야 해요. )
이렇게 구성된 item들을 이미지의 붉은 container 밖으로 벗어나는 item을 이동시키기 위해서 item의 left가 0 - item크기 보다 작으면 부모 div의 마지막 item의 뒤로 이동시키기로 기획했어요.
예시 )

위에서 설계한 대로 HTML을 작성하고, container의 위치와 크기 설정 wrap의 각도를 원하는 만큼 틀고 box와 item의 스타일을 작성해 주었습니다.
container의 크기와 overflow를 설정해 줌으로써 자식들이 container 밖으로 나가도 보이지 않게 됩니다. 그리고 overflow를 hidden으로 하여 벗어난 영역을 사용자가 볼 수 없게 만들어 줬어요.
<div id="conatiner">
<div class="wrap">
<div class="box">
<img class="item" src="img/item_01.png" />
<img class="item" src="img/item_02.png" />
...more img.item tag
</div>
<div class="box">
<img class="item" src="img/item_01.png" />
<img class="item" src="img/item_02.png" />
...more img.item tag
</div>
...more div.box tag
</div>
</div>
#container {
position: relative;
width: 100%;
height: 25rem;
overflow: hidden;
}
#container > .wrap {
position: absolute;
/* container 보다 크기가 50% 더 크도록 설정 */
width: 150%;
height: 150%;
/* 각도 조정 */
transform: rotate(-114deg);
-webkit-transform: rotate(-114deg);
-moz-transform: rotate(-114deg);
-ms-transform: rotate(-114deg);
-o-transform: rotate(-114deg);
/* 위치 조정 (선택사항) */
top: -8rem;
left: -2.5rem;
}
#container > .wrap > .box {
position: relative;
width: 100%;
height: 7.5rem; /* item 의 높이와 동일하게 설정 */
overflow: hidden;
}
#container > .wrap > .box + .box {
margin-top: 0.63rem;
}
#container > .wrap > div.box > .item {
position: absolute;
width: 7.5rem;
height: 7.5rem;
transform: rotate(90deg); /* 이미지 세로로 보이게 변경 */
}
하지만 위 HTML과 CSS를 넣었을 땐 absolute 상태인 item의 위치가 설정되지 않았기 때문에 한쪽에 모여있을 거예요.
item 위치를 설정해 주기 위해서 Jquery를 이용하여 위치를 조절하는 스크립트를 작성했어요.
box안의 item들을 totalItemWidth + width + gap 을 하여 left의 위치를 조절해 주면 올바른 위치에 정렬이 됩니다.
$("#container > .wrap > .box").each(function() {
initRolling($(this));
})
function initRolling($parent) {
var items = $parent.find(" > .item");
var itemLeft = 0;
var itemSize = 0;
var itemGap = convertRemToPixels(0.43); // item 의 간격 [Pixel 크기를 int 형식으로 작성해도 무관함]
$(items).each(function() {
$(this).css("left", itemLeft);
itemLeft += $(this).width() + itemGap;
itemSize = $(this).width();
}
}
이 코드를 실행해보면 완벽하게 정렬이 되었습니다.

이렇게 정렬된 항목들을 코드를 더 작성해서 슬라이드 하면 되는데요.
※ 코드 작성 전 먼저 생각해야 할 문제가 있습니다.
객체에 Transform: rotate를 적용하였을 때 left의 위치는 코드에서 받아오는 위치와 동일하지 않습니다.

이렇게 실제 객체의 left는 0px이지만 JavaScript, Jquery로 위치를 찾으려 했을 때 다른 위치를 표시해 주는 것을 확인할 수 있습니다.
💡 이런 현상이 발생하는 이유는 getBoundingClientRect 함수의 left는 왼쪽위 꼭지점을 기준으로 좌표를 반환해주는데, Rotate를 했기 때문에 left의 위치가 변한 것 입니다.
이를 해결하기 위해 Stack Overflow[javascript - jQuery .position() strangeness while using CSS3 rotate attribute - Stack Overflow]에도 자료가 있습니다.
저는 위 방법을 이용하지 않고 간단한 방법으로 해결하였습니다.
먼저 Array 변수를 생성하고 Array에 현재 item이 지정되어야 할 left 위치를 담도록 하였습니다.
이렇게되면 실제 item의 left와 동일하기 때문에 기능 구현상 문제가 발생하지 않습니다.
function initRolling($parent) {
var items = $parent.find(" > .item");
var itemLeft = 0;
var itemSize = 0;
var itemGap = convertRemToPixels(0.43); // item 의 간격 [Pixel 크기를 int 형식으로 작성해도 무관함]
let rememberLeft = [];
$(items).each(function() {
rememberLeft.push(itemLeft);
$(this).css("left", itemLeft);
itemLeft += $(this).width() + itemGap;
itemSize = $(this).width();
}
}
이 글에서는 left를 코드로 지정해 주기 때문에 가능한 코드였지만, CSS를 통하여 이미 위치를 고정하신 분들은 위 Stack Overflow 링크의 예제를 참고하여 구현하셔야 할 것 같습니다.
슬라이드와 동시에 반복하는 코드를 작성해야 합니다.
items를 for loop을 돌려 객체의 left를 1씩 감소시키고, 위에서 계획했던 item의 left가 0 - item크기 보다 작으면 부모의 마지막 item의 뒤로 이동시키는 코드를 작성해야 합니다.
first와 last의 변수를 만들고 first에는 1, last에는 items의 배열 수[length]를 해줍니다.
먼저 무한 반복을 구현을 위해 setInterval 함수를 0.01초마다 호출하게 하게 하고, 그 안에 rememberLeft [i]가 -itemSize 보다 작은지 체크합니다.
조건에 일치할 시 그 item에 새로운 left 좌표를 할당해 줍니다. 새로운 좌표의 식은 rememberList [last - 1] + itemSzie + itemGap 으로 last의 left에 새로운 item의 사이즈를 넣어주면 됩니다.
조건에 일치하지 않을 땐 현재 item의 rememberLeft [i] 를 1 감소하여 새로운 left를 할당해 줍니다.
var first = 1;
var last;
...Codes
last = items.length;
setInterval(function() {
for (let i = 0; i < items.length; i++) {
const element = items[i];
// 현재 left 체크
if (rememberLeft[i] < -itemSize) {
var newLeft = rememberLeft[last - 1] + itemSize + itemGap;
$(element).css("left", newLeft);
rememberLeft[i] = newLeft; // 새로운 left를 같이 변경해줘야함
first++;
last++;
// first, last도 같이 루프되게 초기화 함수가 필요함
if(last > itemCount) { last=1; }
if(first > itemCount) { first=1; }
}
else {
$(element).css("left", --rememberLeft[i]);
}
}
}, 10);
이렇게 보니 설명 글을 보는 것보다 코드가 간단하다고 생각이 듭니다.
이 코드처럼 setInterval 함수 안에 여러 계산이 들어가고 아주 짧은 텀으로 이용한다면, 모바일 기기에서 배터리가 사라지는 경험을 할 수 있습니다.
사소한 한 화면이지만 이용자의 시각적 즐거움을 위해 여러 가지 방법으로 표현하고 있어요. 비록 단점처럼 계속 보고 있으면 배터리가 사라지는 문제가 있지만 이러한 페이지는 많이 구성되어 있지 않답니다.
대부분은 APNG를 이용하여 애니메이션이 있는 이미지를 표시해드리고 있지만 이런 슬라이드는 APNG 리소스를 제공받지 못하던 상황이기 때문에 개발을 해보았어요.