퍼블리셔 포트폴리오 사이트에 들어간 슬라이드입니다.
포트폴리오 사이트 제작하면서 두번째로 시간을 많이 잡아먹은 파트입니다. (첫번째는 디자인..😨😭)
기능을 설명하자면..
정도가 되겠습니다.
이것도 저번 포스트처럼 codepen에 간단한 데모버전을 만들었습니다. 코드펜에 작성한 내용을 바탕으로 설명하겠습니다.
<h2>Gallery</h2>
<ul class="box__gallery">
<li>
<a href="https://picsum.photos/id/237/500/300">
<img src="https://picsum.photos/id/237/150/150" alt="이미지1"></li>
</a>
<li>
<a href="https://picsum.photos/id/238/500/300">
<img src="https://picsum.photos/id/238/150/150" alt="이미지2"></li>
</a>
<li>
<a href="https://picsum.photos/id/239/500/300">
<img src="https://picsum.photos/id/239/150/150" alt="이미지3"></li>
</a>
<li>
<a href="https://picsum.photos/id/240/500/300">
<img src="https://picsum.photos/id/240/150/150" alt="이미지4"></li>
</a>
<li>
<a href="https://picsum.photos/id/241/500/300">
<img src="https://picsum.photos/id/241/150/150" alt="이미지5"></li>
</a>
<li>
<a href="https://picsum.photos/id/242/500/300">
<img src="https://picsum.photos/id/242/150/150" alt="이미지5"></li>
</a>
</div>
</section>
<!--팝업창-->
<div class="slide-overlay">
<button class="close-btn">close</button>
<button class="slide-btn --prev">
prev
</button>
<button class="slide-btn --next">
next
</button>
<div class="slide__container">
<ul class="slides">
<li><img src="" alt="이미지1"></li>
<li><img src="" alt="이미지2"></li>
<li><img src="" alt="이미지3"></li>
<li><img src="" alt="이미지4"></li>
<li><img src="" alt="이미지5"></li>
<li><img src="" alt="이미지6"></li>
</ul>
</div>
</div>
갤러리 썸네일 li 요소에 앵커로 이미지 원본(큰 사진) 주소를 걸어주고
img 태그에는 썸네일(작은 사진) 주소를 집어넣습니다.
그리고 슬라이드 이미지 주소는 비우거나 임시 이미지를 넣어줍니다. 나중에 자바스크립트로 이미지 원본 주소를 슬라이드 이미지 주소에 집어넣을거임
.slide__container {
position: absolute;
width: 500px;
height: 300px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
}
.slides {
width: 600%;
position: relative;
left: 0;
}
.slides::after {
content: "";
display: block;
clear: both;
}
.slides > li {
float: left;
}
길어서 중요한 슬라이드 부분만!
slide의 li들을 float을 이용해 일자로 정렬해줍니다.
ul 요소인 slides는 이미지의 개수만큼 너비를 지정해줍니다.
여기서는 slide__container의 크기 = 이미지 하나의 크기이기 때문에 너비를 600%로 했습니다.
const thumbnails = document.querySelectorAll(".box__gallery > li");
const overlay = document.querySelector(".slide-overlay");
let slidePhoto = document.querySelectorAll(".slides > li>img");
thumbnails.forEach((thumbnail) => {
thumbnail.addEventListener("click", (e) => {
e.preventDefault();
overlay.style.display = "block";
// 썸네일 원본 사진과 갤러리 슬라이드 이미지 소스 링크 연결
for (let i = 0; i < thumbnails.length; i++) {
let photo = thumbnails[i].lastElementChild;
slidePhoto[i].src = photo.href;
}
});
});
document.querySelector(".close-btn").addEventListener("click", () => {
overlay.style.display = "none";
});
const overlay = document.querySelector(".slide-overlay");
let slides = document.querySelectorAll(".slides > li");
let bullets = 0;
function createBullets() {
// bullet들의 리스트를 생성
const bulletsList = document.createElement("ul");
bulletsList.setAttribute("id", "bullets");
overlay.appendChild(bulletsList);
// 이미지 개수대로 bullet를 생성
slides.forEach((slide, index) => {
const a = document.createElement("a");
a.setAttribute("href", "#");
// 이미지의 index를 a의 html에 집어넣음
a.innerHTML = `${index}`;
const li = document.createElement("li");
li.appendChild(a);
bulletsList.appendChild(li);
});
return (bullets = document.querySelectorAll("#bullets > li > a"));
}
createBullets();
const bulletsList = document.createElement("ul");
bulletsList.setAttribute("id", "bullets");
overlay.appendChild(bulletsList);
slides.forEach((slide, index) => {
const a = document.createElement("a");
a.setAttribute("href", "#");
a.innerHTML = `${index}`;
a.setAttribute("href","#");
const li = document.createElement("li");
li.appendChild(a);
bulletsList.appendChild(li);
저장하고 새로고침을 하면 이렇게 이미지의 개수만큼 버튼이 만들어진 것을 확인할 수 있습니다.
const overlay = document.querySelector(".slide-overlay");
let slides = document.querySelectorAll(".slides > li");
const slide = document.querySelector(".slides");
const thumbnails = document.querySelectorAll(".box__gallery > li");
const photoCount = slides.length;
const duration = 400;
let bullets = document.querySelectorAll("#bullets > li > a")
let photoIndex = 0;
// 슬라이드 버튼 클릭 이벤트
document.querySelector(".--next").addEventListener("click", nextSlideImage);
document.querySelector(".--prev").addEventListener("click", prevSlideImage);
// 다음 사진으로 슬라이드
function nextSlideImage() {
photoIndex++;
photoIndex %= photoCount;
slide.style.left = "-100%";
slide.style.transition = duration + "ms";
window.setTimeout(() => {
slide.appendChild(slide.firstElementChild);
slide.removeAttribute("style");
}, duration);
bulletClassReset();
//해당하는 bullet에 on 클래스 넣기
bulletIndex();
}
// 이전 사진으로 슬라이드
function prevSlideImage() {
photoIndex--;
console.log(photoIndex);
photoIndex %= photoCount;
slide.insertBefore(slide.lastElementChild, slide.firstChild);
slide.style.left = "-100%";
slide.style.transition = "0ms";
window.setTimeout(() => {
slide.style.left = 0;
slide.style.transition = duration+"ms";
});
bulletClassReset();
//해당하는 bullet에 on 클래스 넣기
bulletIndex();
}
❓ 이전 사진으로 슬라이드 하는 함수의 setTimeout에 delay를 따로 설정하지 않은 이유는?
A. delay를 넣으면 이전 이미지로 넘어갈 때 반응이 느리게 느껴집니다. 그래서 slide를 잡아당기자마자 "즉시" slide가 움직일 수 있도록 delay를 생략했습니다.
function bulletClassReset() {
bullets.forEach((bullet) => {
bullet.classList.remove("on");
});
}
function bulletIndex() {
// photoIndex가 음수일 때를 고려
let index = photoIndex + bullets.length;
index %= bullets.length;
console.log(index);
bullets[index].classList.add("on");
}
function bulletLink() {
bullets.forEach((bullet, index) => {
bullet.addEventListener("click", (e) => {
e.preventDefault();
// 클릭된 bullet의 인덱스
const clickedIndex = index;
// 현재 bullet과 클릭된 bullet의 차이
let step = clickedIndex - photoIndex;
photoIndex = clickedIndex;
//모든 bullet의 클래스를 없애고 클릭된 bullet에만 on 클래스 추가
bulletClassReset();
bullets[clickedIndex].classList.add("on");
// 클릭할 때마다 순서가 바뀌는 slides들 업뎃
slides = document.querySelectorAll(".slides>li");
let currentSlides = [...slides];
//step이 양수: 현재 요소보다 뒤에 오는 요소로 이동
if (step > 0) {
// 이미지 슬라이드 step의 수 만큼 앞에서 자른다
let sliceSlides = currentSlides.slice(undefined, step);
slide.style.transition = duration+"ms";
slide.style.left=step * -100+"%";
window.setTimeout(() => {
slide.removeAttribute("style");
// 잘린 요소들을 맨 뒤로 집어넣기..
slide.append(...sliceSlides);
}, duration);
} else {
// step이 음수: 현재 요소보다 앞에 있는 요소로 이동
sliceSlides = currentSlides.slice(step);
// 잘린 요소들을 맨 앞으로 집어넣기
slide.prepend(...sliceSlides);
slide.style.left = step * 100 + "%";
window.setTimeout(()=>{
slide.style.left = 0;
slide.style.transition = duration+"ms";
})
}
//서로 같은 경우 이동할 필요가 없기 때문에 함수 즉시 종료
if (step==0) return;
});
});
}
bullets.forEach((bullet, index) => {
// 클릭 이벤트 추가
bullet.addEventListener("click", (e) => {
e.preventDefault();
});
// 클릭된 bullet의 인덱스
const clickedIndex = index;
헷갈려서 변수 할당해줌
let step = clickedIndex - photoIndex;
뜬끔없이 이 둘의 인덱스의 차이를 구하냐면 인덱스의 차이만큼 슬라이드들을 분리시켜서 이동-삽입할 거기 때문입니다.
이 둘의 차가 양수(>0)면 현재 요소보다 뒤에 있는 요소로 가겠다는 뜻이고
음수(<0)면 현재 요소보다 앞에 있는 요소로 가겠다는 뜻입니다.
아래에 예시로 현재 요소보다 뒤에 있는 요소로 갈 경우의 설계도를 그렸습니다.
photoIndex = clickedIndex;
//모든 bullet의 클래스를 없애고 클릭된 bullet에만 on 클래스 추가
bulletClassReset();
bullets[clickedIndex].classList.add("on");
먼저 선택한 bullet에 클래스가 들어가는 코드를 짰습니다.
forEach(bulletClassReset)로 모든 bullet의 클래스를 지워주고 선택한 x번째의 bullet에만 on 클래스가 들어가도록 작성합니다.
// 클릭할 때마다 순서가 바뀌는 slides들 업뎃
slides = document.querySelectorAll(".slides>li");
let currentSlides = [...slides];
//step이 양수: 현재 요소보다 뒤에 있는 요소로 이동
if (step > 0) {
// 이미지 슬라이드 step의 수 만큼 앞에서 자른다
let sliceSlides = currentSlides.slice(undefined, step);
slide.style.transition = duration+"ms";
slide.style.left=step * -100+"%";
window.setTimeout(() => {
slide.removeAttribute("style");
// 잘린 요소들을 맨 뒤로 집어넣기..
slide.append(...sliceSlides);
}, duration);
}
step 값 기준으로 배열의 원소들을 잘라내는 과정을 거치기 위해 slice() 메서드를 사용했습니다.
arr.slice([begin[, end]])
begin: 시작점 (undefined일 경우 0번 인덱스부터 자르고 음수일 경우 끝에서부터 자릅니다)
end : 종료점 (생략되면 배열의 끝까지 잘라냅니다.)
원리는 위와 같습니다.
else {
// step이 음수: 현재 요소보다 앞에 있는 요소로 이동
sliceSlides = currentSlides.slice(step);
// 잘린 요소들을 맨 앞으로 집어넣기
slide.prepend(...sliceSlides);
slide.style.left = step * 100 + "%";
window.setTimeout(()=>{
slide.style.left = 0;
slide.style.transition = duration+"ms";
})
}
//서로 같은 경우 이동할 필요가 없기 때문에 함수 즉시 종료
if (step==0) return;
const thumbnails = document.querySelectorAll(".box__gallery > li");
let photoIndex = 0;
let slides = document.querySelectorAll(".slides > li");
const slide = document.querySelector(".slides");
thumbnails.forEach((thumbnail, index) => {
// 클릭 이벤트 추가
thumbnail.addEventListener("click", (e) => {
e.preventDefault();
const clickedIndex = index;
let step = clickedIndex - photoIndex;
photoIndex = clickedIndex;
bulletClassReset();
bullets[clickedIndex].classList.add("on");
// 클릭할 때마다 순서가 바뀌는 slides들 업뎃
slides = document.querySelectorAll(".slides>li");
let currentSlides = [...slides];
if (step > 0) {
// 이미지 슬라이드 step의 수 만큼 앞에서 자른다
let sliceSlides = currentSlides.slice(undefined, step);
// 잘린 슬라이드들 맨 뒤로 집어넣기..
slide.append(...sliceSlides);
} else {
sliceSlides = currentSlides.slice(step);
// 잘린 슬라이드들 맨 앞으로 집어넣기
slide.prepend(...sliceSlides);
}
});
});
bullet을 클릭하면 해당하는 번호의 이미지로 슬라이드 되는 함수와 같은 원리입니다.
애니메이션 효과만 뺐습니다.
생각보다 고려해야할 점들도 많았고.. 제이쿼리의 eq()를 어떻게 구현할까 많은 고민을 했습니다.
삽질 끝에 slice() 메서드로 어떻게 돌아가게 만들었는데 이게 맞는지는 모르겠네요ㅎ_ㅎ...
슬라이드를 적용한 포트폴리오 페이지가 궁금하시다면
https://soonmac.github.io/portfolio_page/ 이쪽으로~
포폴 사이트 디자인 너무 취향이라 치이고 갑니다! 너무 멋져요 +_+bbb 계속 예쁜 거 많이 만들고 공유 해주세요!!