이번시간에는 mouseup, down, move이벤트를 사용해 carousel slider를 만들어보자!
See the Pen vanilla js carosal silder by KumJungMin (@kumjungmin) on CodePen.
1. html 구조
html 구조는 아래 그림과 같다.
<div id="slider">
<button class="btn prev"><</button>
<div class="wrapper">
<div class="items">
<div class="item"></div>
<div class="item"><div class="content pink">1</div></div>
<div class="item"><div class="content yellow">2</div></div>
<div class="item"><div class="content skyblue">3</div></div>
<div class="item"><div class="content orange">4</div></div>
<div class="item"></div>
</div>
</div>
<button class="btn next">></button>
</div>
- silder : 전체 슬라이드 영역이다.
- btn : 좌, 우측에 위치한 이동하기 버튼이다.
- wrapper: 현재 슬라이드가 보여지는 부분이다.
- items: item을 감싸는 큰 box이다. (슬라이드는 이 items의 위치(X축) 변경으로 이루어짐)
- item: 각각 슬라이드 아이템을 지칭한다.(회색박스 item은 좌 우 여백을 주기 위한 용도)
- content: 각각 슬라이드 페이지에 넣을 내용을 작성한다.
2. scss
1) silder의 width 지정하기
- 슬라이드 영역의 너비를 지정한다.
#slider {
position: relative;
width: 30rem; //this
margin: 0 auto;
}
2) 좌 우 버튼 디자인 적용하기
- 페이지 좌, 우 이동 버튼에 대한 너비, 색상 등 기본 스타일 지정이다.
.btn {
position: absolute;
width: 3rem;
height: 100%;
top: 50%;
transform: translateY(-50%);
z-index: 1;
border: 0;
background: rgba(250, 250, 250, 0.3);
transition: 0.2s;
cursor: pointer;
font-size: 2rem;
background: #c3c3c3;
opacity: 0.3;
&:hover {
background: rgba(250, 250, 250, 0.5);
}
&.prev {
left: 0;
}
&.next {
right: 0;
}
}
3) wrapper에 overflow 지정하기
- wrapper는 보여지는 영역으로 넘치는 부분은 숨기는 게 좋다.
- wrapper에
overflow:hidden
을 적용하여 보여지는 영역이외는 숨기도록 한다.
.wrapper {
position: relative;
width: 30rem;
height: 18rem;
overflow: hidden; //this!
padding: 3px 0;
}
4) items의 너비, X축 위치는 얼마로?
이 슬라이드 이벤트에서 제일 중요한 값이 바로 items의 너비와 X축 위치이다.
items의 너비의 경우 충분한 공간을 주기 위함이며,
x축 위치의 경우 items의 left값을 변경하여 슬라이드를 넘기기 위해서다.
(1) items의 너비는?
- items의 너비는
(전체 item - 1)%
으로 지정해준다.
.items {
position: absolute;
width: 500%; //this
top: 0;
&.active {
transition: 0.3s;
}
}
(2) items의 X축 위치는?
- 이 슬라이드는 아래 사진처럼 좌 우에 이전, 이후 슬라이드가 보여야 한다.
- 이렇게 하기위해서는 items의 초기 X축 위치를 잘 지정해야한다.
여기서 사용한 방법은 아래와 같다.
wrapper
는 사용자가 보는 view영역이다.여기서 중요한 점은 이전, 이후 슬라이드가 보여야하므로
b
의 너비를 구해야한다는 것이다.
그림과 같이
(wrapper너비 - items너비) / 2
를 하여b
를 구한다.그 다음
item
의 너비에b
를 빼면left
의 초기값을 구할 수 있다.
(3) item에 스타일 지정하기
- items에서
display: flex
을 했으므로 item의width: 100%
으로 한다. - 그 이외는 스타일적인 css이므로 가볍게 보면 좋을 거 같다 :)
.item {
width: 100%; //this
pointer-events: none;
position: relative;
padding: 0 1rem;
.content {
width: 100%;
height: 18rem;
border: 1px solid #c8c8c8;
box-shadow: 0 1rem 2.8rem rgba(0, 0, 0, 0.05);
border-radius: 1rem;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 5rem;
color: #fff;
}
}
3. js
1) 필요한 요소 가져오기
const wrapper = document.querySelector('.wrapper');
const items = document.querySelector('.items');
const item = document.querySelectorAll('.item');
const next = document.querySelector('.next');
const prev = document.querySelector('.prev');
- wrapper : 마우스 이벤트를 설정을 위해 필요하다.
- items: 페이지 로드시 items의
width
를 지정할 예정이다. (이벤트에 따라 items의 위치 변경) - item: item의
width
값을 이용해, 전체적인 레이아웃 수치를 결정한다. - next, prev: 버튼 이벤트 설정을 위해 가져온다.
2) 필요한 변수들
let startX = 0; //mousedown시 위치
let moveX = 0; //움직인 정도
let currentIdx = 0; //현재 위치(index)
let positions = [];
- startX:
mousedown
시 위치를 저장한다. - moveX:
mousedown
→mousemove
이벤트를 통해 마우스가 움직인 정도를 저장한다. - currentIdx: 현재 보고 있는 슬라이드의 인덱스값을 저장한다. (초기값은 0)
- positions: 각 슬라이드 페이지를 보기 위해 사용해야할 위치값(X축)을 저장한 배열이다.
3) 페이지 로드시 기본값 설정하기
페이지가 load되거나 resize이벤트가 발생할 때마다 items의 너비, x축 위치를 변경해줘야한다.
(1) items에 애니메이션 클래스(active) 제거
- items에
active클래스
가 적용되면 애니메이션 시간(transition
)이 설정된다. - 하지만
resize
,load
될 때transition
이 적용되어 있으면, 재배치하는 과정이 보이게 된다;; - 재배치할 때는 transition이 발생하지 않도록
remove
로active클래스
를 제거해주도록 한다.
function initializeData() {
const isActive = items.classList.contains('active');
if (isActive) items.classList.remove('active');
...
}
(2) items의 위치와 너비 계산하기
items의 위치를 계산하기 위해서는 wrapper의 너비, item 너비가 필요하다!
한 번 알아보자~
function initializeData() {
...
const wrapper = wrapper.clientWidth; //[1]
const itemWidth = item[1].clientWidth; //[2]
const b = (wrapper - itemWidth) / 2 //[3]
const initX = Math.floor((itemWidth - b) * -1); //[4]
...
[1] view영역을 담당하는 wrapper
의 너비를 가져온다.
[2] item
의 너비이다. (슬라이드 위치 간격으로도 쓰인다)
[3] 그림에서 나타난 b
는 wrapper
에서 item
의 너비를 빼고, 2를 나눠 구한다.
[4] left의 초기위치값은 item
너비에서 방금 구한 b
를 빼고 -1
을 곱한 값이 된다.
function initializeData() {
...
let pos = [];
for (let i=0; i<itemCount; i++) { //[6]
pos.push(initX - itemWidth * i); //itemWidth가 위치간격으로 쓰임
}
positions = pos //[6]
...
}
[6] 반복문을 사용해 슬라이드별 위치값을 미리 배열에 저장해준다.
function initializeData() {
...
items.style.width = (itemCount + 1)*100 + '%'; //[7]
items.style.left = positions[currentIdx] + 'px'; //[8]
}
[7] items의 너비는 (여백용 item을 제외한)itemCount + 1
에 100%
곱하기로 구한다.
[8] items의 초기위치값(left
)을 지정한다.
window.addEventListener('resize', initializeData); //[9]
window.addEventListener('load', initializeData); //[9]
[9] items의 너비, 위치값은 resize
, load
이벤트가 발생할 때마다 갱신해준다.
4) 좌 우 버튼 이벤트 설정하기
(1) next 버튼 이벤트
next.addEventListener('click', (e) => {
if (currentIdx === itemCount - 1) return; //[1]
const isActive = items.classList.contains('active'); //[2]
if (!isActive) items.classList.add('active');
currentIdx = currentIdx + 1; //[3]
items.style.left = positions[currentIdx] + 'px'; //[4]
});
[1] 현재 idx
가 itemCount - 1
과 같으면 더이상 넘길 슬라이드가 없으므로 return
한다.
[2] active
클래스가 미적용이면 items
에 active
클래스를 추가해준다.
[3] 다음 슬라이드를 넘기는 이벤트이므로 currentIdx
에 1을 더해준다.
[4] 변경된 currentIdx
를 이용해 items
의 left
값을 변경해준다.
(2) prev 버튼 이벤트
prev.addEventListener('click', (e) => {
if (currentIdx === 0) return; //[1]
const isActive = items.classList.contains('active'); //[2]
if (!isActive) items.classList.add('active');
currentIdx = currentIdx - 1; //[3]
items.style.left = positions[currentIdx] + 'px'; //[4]
});
[1] 현재 idx
가 0
과 같으면 더이상 넘길 슬라이드가 없으므로 return
한다.
[2] active
클래스가 미적용이면 items
에 active
클래스를 추가해준다.
[3] 이전 슬라이드로 넘기는 이벤트이므로 currentIdx
에 1을 빼준다.
[4] 변경된 currentIdx
를 이용해 items
의 left
값을 변경해준다.
5) 슬라이드 넘기기 이벤트 지정하기
슬라이드를 넘기기 위해
mousedown
→mousemove
→mouseup
이벤트 순서로 지정해줘야한다.
mousedown
은 처음 슬라이드를 클릭했을 때,
mousemove
는 클릭한 상태에서 마우스를 좌 혹은 우로 이동할 때,
마지막으로mouseup
을 했을 때 슬라이드 위치를 변경해준다.
- 로직을 글로 작성하면 아래와 같다.
wrapper.onmousedown =(e)=> {
처음 커서 위치를 저장
active클래스 여부를 체크하고 없으면 해당 클래스를 items에 적용
items.addEventListener('mousemove', 마우스 움직이기 이벤트);
document.onmouseup =(e)=> {
currentIdx와 items의 위치값을 변경
}
}
(1) mousedown시에는...
mousedown
이벤트에서는 현재 커서값을startX
에 저장하고,
items
에active
클래스가 적용되어있지 않으면active
클래스를 적용하는 역할을 한다.
wrapper.onmousedown =(e)=> {
const rect = wrapper.getBoundingClientRect(); //[1]
startX = e.clientX - rect.left; //[2]
const isActive = items.classList.contains('active');
if (!isActive) items.classList.add('active'); //[3]
...
}
[1] getBoundingClientRect
를 사용해 화면 기준 wrapper
의 위치를 구한다.
[2] e.clientX
(화면기준 커서 위치값)에 rect.left
를 빼서, wrapper
기준 커서 위치값을 구한다.
[3] 만약 items
에 active
클래스가 없다면 추가해준다.
(2) mousemove시에는...
mousemove
이벤트는mousedown
이벤트가 발생한 상태에서 진행시키도록 한다.
mousemove
이벤트에서는 커서가 움직인 정도를moveX
에 저장하고,
움직일 때마다items
의 위치값을 변경해준다.
wrapper.onmousedown =(e)=> { //[1]
...
items.addEventListener('mousemove', onMouseMove); //[1]
...
}
[1] wrapper
에서 mousedown
이벤트가 발생한 상태에서, mousemove
이벤트를 정의해준다.
function onMouseMove(e) {
const rect = wrapper.getBoundingClientRect(); //[2]
moveX = e.clientX - rect.left - startX; //[3]
if (currentIdx === 0 && moveX > 0) return; //[4]
else if(currentIdx === itemCount - 1 && moveX < 0) return; //[5]
const left = positions[currentIdx] + moveX; //[6]
items.style.left = left + 'px'; //[7]
}
[2] getBoundingClientRect
를 사용하여 wrapper
의 위치값을 구한다.
[3] 현재커서위치 - wrapper위치 - 시작위치
를 하여 커서가 움직인 정도를 구한다.
[4] 만약 현재 idx
가 0이며, moveX > 0
이면 return한다.
[5] 만약 현재 idx
가 itemCount - 1이며, moveX < 0
이면 return한다.
[6] currentIdx
를 사용해 현재 슬라이드 위치값을 구하고, 이 값에 moveX
를 더한다.
[7] 사용자가 드래그할 때마다 items
의 위치값을 (6번에서 구한)left
로 변경해준다.
(3) mouseup시에는...
mouseup
이벤트에서는mousemove
이벤트와 기본mouseup
이벤트를 제거해준다.
그 다음 움직인 정도(moveX
)에 따라currentIdx
를 변경하고 → 변경한idx
를 사용해items
위치를 변경해준다.
wrapper.onmousedown =(e)=> {
document.onmouseup =(e)=> {
items.removeEventListener('mousemove', onMouseMove); //[1]
document.onmouseup = null; //[1]
}
}
[1] mousemove와 mouseup 이벤트를 제거해준다.
wrapper.onmousedown =(e)=> {
document.onmouseup =(e)=> {
...
if (moveX > -70 && moveX <= 70) { //[2]
return items.style.left = positions[currentIdx] + 'px';
}
...
}
}
[2] 만약 moveX가 -70~70이면 움직이기 전 위치로 이동해준다.
wrapper.onmousedown =(e)=> {
document.onmouseup =(e)=> {
if (moveX > 0 && currentIdx > 0) { //[3]
currentIdx = currentIdx - 1;
items.style.left = positions[currentIdx] + 'px';
}
if (moveX < 0 && currentIdx < itemCount - 1){ //[4]
currentIdx = currentIdx + 1;
items.style.left = positions[currentIdx] + 'px';
}
}
}
[3] moveX>0
이고 idx>0
이면 → idx
를 1빼주고 위치를 갱신해준다. (이전 슬라이드로 이동)
[4] moveX<0
이고 idx < item-1
이면 → idx
를 1더하고 위치를 갱신해준다. (다음 슬라이드로 이동)
'개발 기술 > css 애니메이션 (with js)' 카테고리의 다른 글
svg가 그려지는 효과(with. stroke dasharray, stroke-dashoffset) (2) | 2021.09.29 |
---|---|
Gooey Effect 만들기 (with js) (0) | 2021.09.29 |
계산기 만들기(with js, css) (0) | 2021.09.27 |
(css, js) 햄버거 메뉴 애니메이션 만들기 (0) | 2021.09.26 |
hover시 색이 변하는 애니메이션(with. box-shadow로 네온 효과) (0) | 2021.09.25 |
댓글