무한 루프 이미지 슬라이더 이미지 슬라이더에 대한 이해
이미지가 실제 보이는 div가 있다
그 안에 ul로 감싸진 실제 이미지들(li)이 일렬로 정렬되어 있다
ul의 포지션이 움직이면서 다음이나 전의 이미지가 보여진다
HTML 구조 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <body > <div class ="kind_wrap" > <div class ="kind_slider" > <ul class ="slider" > <li > <img src ="https://via.placeholder.com/800x200.png?text=A" alt ="" /> </li > <li > <img src ="https://via.placeholder.com/800x200.png?text=B" alt ="" /> </li > <li > <img src ="https://via.placeholder.com/800x200.png?text=C" alt ="" /> </li > <li > <img src ="https://via.placeholder.com/800x200.png?text=D" alt ="" /> </li > </ul > </div > <div class ="arrow" > <a href ="javascript:void(0)" class ="prev" > 이전</a > <a href ="javascript:void(0)" class ="next" > 다음</a > </div > </div > </body >
style 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 * { margin : 0 ; padding : 0 ; } li { list-style : none; } .kind_wrap { border : 2px solid black; width : 100% ; max-width : 800px ; margin : 0 auto; position : relative; } .kind_wrap > .kind_slider { overflow : hidden; } .kind_wrap > .kind_slider .slider { position : relative; } .kind_wrap > .kind_slider .slider li { float : left; } .kind_wrap > .kind_slider img { vertical-align : top; } .kind_wrap .arrow > a .prev { position : absolute; left : -50px ; top : 100px ; } .kind_wrap .arrow > a .next { position : absolute; right : -50px ; top : 100px ; }
실제 보여지는 화면인 div.kind_wrap에만 최대 넓이 값이 들어있고 ul 이나 li에는 사이즈가 없다
ul의 width 값은 이미지(li)의 갯수에 의해 정해진다 (ul의 width값이 변할수 있다)
이미지(li)의 사이즈 역시 변할수 있다
자바스크립트에서 li의 사이즈를 구하고 li의 갯수만큼 ul의 사이즈를 구해서 스타일에 넣어준다
JavaScript
각 노드들을 가져온다
li의 width 값을 구해준다
li의 갯수만큼 ul의 width값도 구해준다
ul의 width 값을 설정해 준다
노드 준비 1 2 3 4 5 6 7 8 9 10 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' ) const slideLis = slider.querySelectorAll ('li' ) const arrow = kindWrap.querySelector ('.arrow' )const liWidth = slideLis[0 ].clientWidth const sliderWidth = liWidth * slideLis.length slider.style .width = sliderWidth + 'px'
버튼에 클릭 이벤트를 줘서 ul의 포지션을 li의 넓이값만큼 움직여 주면 된다.
버튼을 각각이 아닌 한번에 가져왔기 때문에 if문으로 구별을 해준다
클릭 이벤트 추가 1 2 3 4 5 6 7 arrow.addEventListener ('click' , function (e ) { e.preventDefault if (e.target .className === 'next' ) { slider.style .left = `-${liWidth} px` } })
이렇게까지 작성하고 나면 다음으로 가는 버튼이 작동하지만 한 번만 작동한다
slider의 left값이 계속 liWidth 값으로만 되기 때문이다.. 그래서 버튼을 누를때마다 liWidth 값을 누적시켜줄 필요가 있다
그리고 현재 보고 있는 이미지(li)가 몇 번째 이미지 인지 인덱스 값도 설정 해준다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' )const slideLis = slider.querySelectorAll ('li' )const arrow = kindWrap.querySelector ('.arrow' )const liWidth = slideLis[0 ].clientWidth const sliderWidth = liWidth * slideLis.length slider.style .width = sliderWidth + 'px' let curIndex = 0 let moveDist = 0 arrow.addEventListener ('click' , function (e ) { e.preventDefault if (e.target .className === 'next' ) { if (curIndex === slideLis.length - 1 ) { curIndex = 0 moveDist = 0 slider.style .transform = `translateX(${moveDist} px)` } else { moveDist += -liWidth slider.style .transform = `translateX(${moveDist} px)` curIndex++ } } })
넥스트 버튼을 눌렀을때 현재 인덱스가 몇 번째인지에 따라 동작을 추가해준다
curIndex는 0부터 시작해서 마지막 이미지에 가면 3가 된다
slideLis의 갯수는 4개 이므로 -1을 해줘야 마지막 숫자가 동일해진다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' )const slideLis = slider.querySelectorAll ('li' )const arrow = kindWrap.querySelector ('.arrow' )const liWidth = slideLis[0 ].clientWidth const sliderWidth = liWidth * slideLis.length slider.style .width = sliderWidth + 'px' let curIndex = 0 let moveDist = 0 const speedTime = 500 arrow.addEventListener ('click' , function (e ) { e.preventDefault if (e.target .className === 'next' ) { if (curIndex === slideLis.length - 1 ) { curIndex = 0 moveDist = 0 slider.style .transform = `translateX(${moveDist} px)` } else { moveDist += -liWidth slider.style .transform = `translateX(${moveDist} px)` slider.style .transition = `all ${speedTime} ms ease` curIndex++ } } else { if (curIndex === 0 ) { curIndex = slideLis.length - 1 moveDist = -(liWidth * curIndex) slider.style .transform = `translateX(${moveDist} px)` slider.style .transition = `all ${speedTime} ms ease` } else { moveDist += liWidth slider.style .transform = `translateX(${moveDist} px)` curIndex-- } } })
이렇게 작성하고 나면 문제점이 하나 생긴다 첫번째 이미지에서 이전을 클릭하거나 마지막 이미지에서 다음을 클릭하면 이미지가 역재생(?)되며 돌아간다는 것 이걸 자연스럽게 해주기 위해 트릭을 써야한다 첫 번째 이미지와 맨 마지막 이미지를 복제한 후1번 앞에는 4번의 복제본을 4번 뒤에는 1번의 복제본을 추가해준다
이 상황에서 4번 이미지일때 다음을 클릭하게 되면 1번 복제본으로 넘어가는 애니메이션 (transition)을 500ms동안 수행한다. 그리고나서 수행이 끝나자마자 원본 이미지인 1번 이미지로 위치를 이동 시킨다.
이미지 클론
첫 번째 이미지는 slideLis[0]으로 직접 지정해 복제해도 상관 없지만, 마지막 이미지는 나중에 이미지가 추가 될 경우를 대비해 배열의 길이에서 -1만큼을 뺀 값으로 지정해 복제해준다
inserBefore(‘추가할노드’,’추가 할 위치’)는 ‘추가 할 위치’의 앞 쪽으로 추가하는 메소드 appendChild(‘추가할노드’)는 그냥 맨 뒤쪽으로 추가된다.
1 2 3 4 5 6 7 8 9 10 11 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' )const slideLis = slider.querySelectorAll ('li' )const arrow = kindWrap.querySelector ('.arrow' )const cloneA = slideLis[0 ].cloneNode (true )const cloneD = slideLis[slideLis.length - 1 ].cloneNode (true )slider.insertBefore (cloneD, slideLis[0 ]) slider.appendChild (cloneA)
복제한 이미지를 추가하고 나면 화면이 이렇게 이상해진다.. 그 이유는 복제해서 넣기전의 넓이 값을 먼저 가지기 때문이다 그래서 복제한후의 넓이값을 다시 구해줘야한다!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' )const slideLis = slider.querySelectorAll ('li' )const arrow = kindWrap.querySelector ('.arrow' )const cloneA = slideLis[0 ].cloneNode (true )const cloneC = slideLis[slideLis.length - 1 ].cloneNode (true )slider.insertBefore (cloneC, slideLis[0 ]) slider.appendChild (cloneA) let curIndex = 1 let moveDist = 0 const speedTime = 500 const slideCloneLis = slider.querySelectorAll ('li' ) const liWidth = slideLis[0 ].clientWidth const sliderWidth = liWidth * slideCloneLis.length slider.style .width = sliderWidth + 'px' moveDist = -liWidth
setTimeout()을 이용해 마지막 이미지에 가서 버튼을 클릭했을때 transition 효과를 0초로 설정하여 넘어가는 이미지효과를 제거한후 이동하는 방식이다
setTimeout
처음으로 보여줄 이미지가 사실상 두 번째에 있으므로 포지션 초기값을 다시 설정해준다
버튼을 눌렀을 경우 이미지가 계속 넘어가도록 해준다
setTimeout()함수로 이미지의 위치를 자연스럽게 교체해준다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' )const slideLis = slider.querySelectorAll ('li' )const arrow = kindWrap.querySelector ('.arrow' )const cloneA = slideLis[0 ].cloneNode (true )const cloneC = slideLis[slideLis.length - 1 ].cloneNode (true )slider.insertBefore (cloneC, slideLis[0 ]) slider.appendChild (cloneA) let curIndex = 1 let moveDist = 0 const speedTime = 500 const slideCloneLis = slider.querySelectorAll ('li' )const liWidth = slideLis[0 ].clientWidth const sliderWidth = liWidth * slideCloneLis.length slider.style .width = sliderWidth + 'px' slider.style .transform = `translateX(-${liWidth} px)` moveDist = -liWidth arrow.addEventListener ('click' , function (e ) { e.preventDefault if (e.target .className === 'next' ) { moveDist += -liWidth curIndex += 1 slider.style .transform = `translateX(${moveDist} px)` slider.style .transition = `all ${speedTime} ms ease-in-out` if (curIndex === slideCloneLis.length - 1 ) { setTimeout (() => { slider.style .transition = 'all 0ms' moveDist = -liWidth curIndex = 1 slider.style .transform = `translateX(${moveDist} px)` }, speedTime) } } else { moveDist += liWidth curIndex += -1 slider.style .transform = `translateX(${moveDist} px)` slider.style .transition = `all ${speedTime} ms ease-in-out` if (curIndex === 0 ) { setTimeout (() => { slider.style .transition = 'all 0ms' moveDist = -liWidth * (slideCloneLis.length - 2 ) curIndex = slideCloneLis.length - 2 slider.style .transform = `translateX(${moveDist} px)` }, speedTime) } } })
prev 버튼을 눌렀을시 헷갈린점
moveDist 값이 원래의 4번 위치로 가야한다
curIndex도 4번이어야한다
현재 이미지의 갯수는 6개이고 원래의 4번이미지의 curIndex 값은 4번이므로 배열의 길이에서 클론한 li의 갯수만큼 빼준다
코드 정리 코드가 길어지고 반복되는 요소가 있으므로 함수로 만들어 코드를 정리해준다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const kindWrap = document .querySelector ('.kind_wrap' )const slider = kindWrap.querySelector ('.slider' )const slideLis = slider.querySelectorAll ('li' )const arrow = kindWrap.querySelector ('.arrow' )const cloneA = slideLis[0 ].cloneNode (true )const cloneC = slideLis[slideLis.length - 1 ].cloneNode (true )slider.insertBefore (cloneC, slideLis[0 ]) slider.appendChild (cloneA) let curIndex = 1 let moveDist = 0 const speedTime = 500 const slideCloneLis = slider.querySelectorAll ('li' )const liWidth = slideLis[0 ].clientWidth const sliderWidth = liWidth * slideCloneLis.length slider.style .width = sliderWidth + 'px' moveDist = -liWidth slider.style .transform = `translateX(-${liWidth} px)` arrow.addEventListener ('click' , function (e ) { e.preventDefault if (e.target .className === 'next' ) { move (-1 ) if (curIndex === slideCloneLis.length - 1 ) moveTimeOut (1 ) } else { move (1 ) if (curIndex === 0 ) moveTimeOut (slideCloneLis.length - 2 ) } }) function move (direction ) { moveDist += liWidth * direction curIndex += -1 * direction slider.style .transform = `translateX(${moveDist} px)` slider.style .transition = `all ${speedTime} ms ease-in-out` } function moveTimeOut (index ) { setTimeout (() => { slider.style .transition = 'all 0ms' moveDist = -liWidth * index curIndex = index slider.style .transform = `translateX(${moveDist} px)` }, speedTime) }