티스토리 뷰
Touch Slider (Image)
Touch Slider Image 기능을 구현하면서 Image 특정 부분에 넣어 Image Touch Slider 구현하기 등 여러 문제를 겪어 다음에는 같은 실수를 반복하지 않고 다른 분들도 효율적인 구현을 할 수 있도록 작성한 글입니다. ( Svelte를 기반으로 제작된 글입니다. )
Tech
- Svelte
- JavaScript / Html / CSS
과정
Touch slider은 Image를 쭉 나열해 두고 overflow: hidden
으로 이미지를 숨기고 mouse event와 transform: translateX()
를 이용하여 image
위치 고정(image index
* -보여지는 너비(width)) 및 dragging
하는 모습을 보여줍니다.
1. Image 고정 및 Html/CSS 구조
Html / CSS를 간략화 해서 구조를 파악하겠습니다.
[ CSS ]
.div_slider_preview_container_cls {
width: 350px;
height: 200px;
margin: 0 auto;
position: relative;
overflow: hidden;
text-align: center;
}
.div_image_container_cls {
width: 100%;
height: 100%;
display: inline-flex;
position: relative;
transform: translateX(0);
transition: transform 0.3s ease-out;
cursor: grab;
}
.img_preview_cls {
width: 100%;
object-fit: contain;
flex-shrink: 0;
}
[ Html ]
<div class="div_slider_preview_container_cls">
<div class="div_image_container_cls">
{#each ref_image_arr as image_src, index}
<img src="{image_src}" class="img_preview_cls" />
{/each}
</div>
</div>
.div_image_container_cls
내부에서 .img_preview_cls
를 쭉 나열하고 .div_slider_preview_container_cls
에 overflow: hidden
을 넣어 특정 부분에서만 그림이 보이게 하는 식으로 구조를 짜게 됩니다.
그림을 통해 더 자세히 알아보도록 하겠습니다.
Image1: .img_preview_cls
.img_preview_cls
만 따로 때서 보면 다음과 같이 그리며 object-fit: contain
을 통해 image
를 틀 안에 가둬 둘 수 있습니다.
Image2: 전체 구조
전체 구조를 보면 위의 .img_preview_cls
을 여러 개 붙어 있으며 보이는 부분과 안 보이는 부분을 색으로 구분하는 것을 볼 수 있습니다.
초록색: .div_slider_preview_container_cls
으로 그림이 보이는 부분
빨간색: .div_slider_preview_container_cls
의 overflow: hidden
부분으로 가려진 부분
이렇게 만들어 뒤에 나오는 mouse event, transform: translateX()
를 이용하여 Image
를 움직입니다.
2. Slider Event (mouse event, transform: translateX()
)
Html
에 mouse event를 추가하면 다음과 같습니다.
(Svelte에 추가해서 on:*
과 같은 형태로 추가됩니다.)
[ Html ]
<div class="div_slider_preview_container_cls">
<div bind:this="{ref_this_preview}" class="div_image_container_cls">
{#each ref_image_arr as image_src, index} <img src={image_src} alt=""
on:dragstart|preventDefault on:mousedown={event =>
preview_mouse_start(event, index)} on:mouseup={preview_mouse_end}
on:mouseleave={preview_mouse_end} on:mousemove={preview_mouse_move}
class="img_preview_cls" /> {/each}
</div>
</div>
내부 함수에 대한 설명에 앞서 mouse event에 대한 설명을 간단히 하고 가겠습니다.
mouse event는 말 그대로 mouse를 가지고 하는 이벤트를 말합니다. (버블링 주의)
> mousedown: 마우스 왼쪽 버튼을 누를 때 발생
> mouseup: 마우스 버튼을 누르고 있다가 뗄 때 발생
> mouseleave: 마우스 커서가 해당 element를 나올 때 발생 (이벤트 버블링 X)
> mousemove: 마우스 커서가 해당 element에서 움직일 때 발생 (UI 느리게 한다)
- 사용 변수
mouse event에서 사용하는 함수에서 사용하는 변수에 대해 알고 가겠습니다.
let ref_image_arr = ['1.jpg', '2.jpg', '3.jpg']; // image 저장 배열 저장 변수
let ref_this_preview; // Svelte의 bind:this에 연결된 변수
let preview_start_position = 0; //왼쪽 상단을 기준으로 마우스 위치의 x 좌표 값 저장 변수 (mouse 시작 x 좌표)
let preview_current_index = 0; // 현재 image의 index값 저장 변수
let preview_animation_id = 0; // requestAnimationFrame() 사용 id 저장 변수
let preview_is_dragging = false; // drag를 실행했는지 check 변수
let preview_current_translate = 0; // 현재 translateX 위치
let preview_prev_translate = 0; // 이전 translateX 위치
* 추가 (requestAnimationFrame()
)
: 애니메이션을 수행하고 싶다고 브라우저에 알리고 다음 다시 그리기 전에 브라우저가 지정된 함수를 호출하여 애니메이션을 업데이트하도록 요청하는 함수 (부드러운 애니메이션을 위해 사용하는 함수)
[ 사용 이유 ]
- 브라우저에서 최적화할 수 있으므로 애니메이션이 더 부드러워집니다.
- 비활성 탭의 애니메이션이 중지되어 CPU가 식을 수 있습니다.
- 배터리 친화적입니다.
- Mouse event 내부 함수
[ preview_mouse_start ]
mouse event가 시작될 때 실행되는 함수로 현재 위치, drag
체크, animation
을 실행시키는 함수입니다.
function preview_mouse_start(event, index) {
preview_current_index = index;
preview_start_position = event.pageX;
preview_is_dragging = true;
preview_animation_id = requestAnimationFrame(animation);
}
[ preview_mouse_end ]
mouse event 가 마무리될 때 위치 image
의 index
를 파악하고 이를 보여주는 부분인 .div_slide_preview_container_cls
의 마이너스 너비(width: 350)와 곱하여 image
가 딱 보이게 고정하는 역할을 하는 함수입니다.
function preview_mouse_end() {
preview_is_dragging = false;
cancelAnimationFrame(preview_animation_id);
const move_by = preview_current_translate - preview_prev_translate; // translateX 움직인 거리
if (move_by < -100 && preview_current_index < ref_image_arr.length - 1)
preview_current_index += 1;
if (move_by > 100 && preview_current_index > 0) preview_current_index -= 1;
preview_current_translate = preview_current_index * -350;
preview_prev_translate = preview_current_translate;
ref_this_preview.style.transform = `translateX(${preview_current_translate}px)`;
}
[ preview_mouse_move ]
Slider의 image
가 움직이는 모션을 보여주기 위한 함수입니다.
function preview_mouse_move(event) {
if (!preview_is_dragging) return;
const current_position = event.pageX;
preview_current_translate =
preview_prev_translate + current_position - preview_start_position;
}
[ animation ]
애니메이션을 나타내는 함수입니다.
function animation() {
ref_this_preview.style.transform = `translateX(${preview_current_translate}px)`;
preview_is_dragging && requestAnimationFrame(animation);
}
전체 코드
<style>
.div_slider_preview_container_cls {
width: 350px;
height: 200px;
margin: 0 auto;
position: relative;
overflow: hidden;
text-align: center;
}
.div_image_container_cls {
width: 100%;
height: 100%;
display: inline-flex;
position: relative;
transform: translateX(0);
transition: transform 0.3s ease-out;
cursor: grab;
}
.img_preview_cls {
width: 100%;
object-fit: contain;
flex-shrink: 0;
}
</style>
<div class="div_slider_preview_container_cls">
<div bind:this={ref_this_preview} class="div_image_container_cls">
{#each ref_image_arr as image_src, index}
<img src={image_src}
alt=""
on:dragstart|preventDefault
on:mousedown={event => preview_mouse_start(event, index)}
on:mouseup={preview_mouse_end}
on:mouseleave={preview_mouse_end}
on:mousemove={preview_mouse_move}
class="img_preview_cls"
/>
{/each}
</div>
</div>
<script>
let ref_this_preview; // Svelte의 bind:this에 연결된 변수
let ref_image_arr = [
"1.jpg",
"2.jpg",
"3.jpg",
]; // image 저장 배열 저장 변수
let preview_start_position = 0; //왼쪽 상단을 기준으로 마우스 위치의 x 좌표 값 저장 변수 (mouse 시작 x 좌표)
let preview_current_index = 0; // 현재 image의 index값 저장 변수
let preview_animation_id = 0; // requestAnimationFrame() 사용 id 저장 변수
let preview_is_dragging = false; // drag를 실행했는지 check 변수
let preview_current_translate = 0; // 현재 translateX 위치
let preview_prev_translate = 0; // 이전 translateX 위치
function preview_mouse_start(event, index) {
preview_current_index = index;
preview_start_position = event.pageX;
preview_is_dragging = true;
preview_animation_id = requestAnimationFrame(animation);
}
function preview_mouse_end() {
preview_is_dragging = false;
cancelAnimationFrame(preview_animation_id);
const move_by = preview_current_translate - preview_prev_translate; // translateX 움직인 거리
//image index 파악하기
if (move_by < -100 && preview_current_index < ref_image_arr.length - 1) preview_current_index += 1;
if (move_by > 100 && preview_current_index > 0) preview_current_index -= 1;
// 보여줘야 할 image 계산
preview_current_translate = preview_current_index * -350;
preview_prev_translate = preview_current_translate;
ref_this_preview.style.transform = `translateX(${preview_current_translate}px)`;
}
function preview_mouse_move(event) {
if (!preview_is_dragging) return;
const current_position = event.pageX;
preview_current_translate = preview_prev_translate + current_position - preview_start_position;
}
function animation() {
ref_this_preview.style.transform = `translateX(${preview_current_translate}px)`;
preview_is_dragging && requestAnimationFrame(animation);
}
</script>
마치며
Drag and Drop과 Touch Slider을 구현하게 되었는데 Drag and Drop은 splice
를 적절하게 사용하면 쉽게 구현이 가능한 부분이 있었습니다.
그러나 Touch Slider는 구조부터 고민하면서 움직이는 부분과 고정되는 부분을 잘 파악하는 데 어려움을 겪어 재미있게 글을 작성할 수 있었고 다시 한번 정리가 되는 기회였습니다. 그러나 requestAnimationFrame
과 같은 것들을 내장 함수 & 변수들을 잘 사용하지 못한 것, CSS를 깨끗하게 정리하여 개발하지 못한 점, 공식 문서에 대한 약간의 꺼리낌등 아쉬운 점이 있어 더욱 개선을 해 나가야 할 것 같습니다.
Pagination button
을 추가하고 싶으면 image
개수만큼 button
을 만들고 mousedown
, mouseup
을 추가해주고 position: absolute
를 잘 사용하면 만드실 수 있습니다.
긴 글 읽어주셔서 감사합니다. :)
Ref
Touch Slider • REPL • Svelte
https://www.w3schools.com/jsref/obj_mouseevent.asp
https://javascript.info/mouse-events-basics
https://hianna.tistory.com/492
https://webisfree.com/2020-03-19/[%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8]-requestanimationframe()%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EB%B0%8F-%EC%98%88%EC%A0%9C
'Tip and Error' 카테고리의 다른 글
2. OAuth 2.0 카카오 간단 구현 (0) | 2023.06.17 |
---|---|
1. OAuth 2.0 소셜 로그인 개념 (0) | 2023.06.12 |
첫 서비스 개발을 어떻게 잘 할 수 있을까? (0) | 2023.05.06 |
React + TypeScript + Babel + ESLint & Prettier With Webpack 세팅 (CRA X) (0) | 2023.03.18 |
TypeScript에서 Type을 어떻게 쉽게 파악할까? (4) | 2023.01.29 |