최종 결과물
Best Reviews
-
Review Title01
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
-
Review Title02
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.
-
Review Title03
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
HTML
<!DOCTYPE html>
<html lang="kr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>38JS - Reviews</title>
<script src="https://kit.fontawesome.com/e4e94b66e3.js" crossorigin="anonymous"></script>
<script src="./review.js"></script>
<link rel="stylesheet" href="./reset.css">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="container">
<h1>Best Reviews</h1>
<div class="review_container">
<ul class="review_wrapper">
<li class="review_item">
<div class="image_box">
<img src="https://images.unsplash.com/photo-1682687982134-2ac563b2228b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80" alt="image01">
</div>
<div class="text_box">
<h2 class="review_title">Review Title01</h2>
<p class="review_description">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
</div>
</li>
<li class="review_item">
<div class="image_box">
<img src="https://images.unsplash.com/photo-1682686581030-7fa4ea2b96c3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80" alt="image02">
</div>
<div class="text_box">
<h2 class="review_title">Review Title02</h2>
<p class="review_description">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</p>
</div>
</li>
<li class="review_item">
<div class="image_box">
<img src="https://images.unsplash.com/photo-1682687982502-1529b3b33f85?ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1470&q=80" alt="image03">
</div>
<div class="text_box">
<h2 class="review_title">Review Title03</h2>
<p class="review_description">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
</div>
</li>
</ul>
</div>
<div class="button_wrap">
<button type="button" data-action="prev"><i class="fa-solid fa-angle-left"></i></button>
<button type="button" data-action="next"><i class="fa-solid fa-angle-right"></i></button>
</div>
</div>
<script>
const review = new Reviews('.container');
</script>
</body>
</html>
CSS
.container {
background: #fff;
width: 400px;
height: 600px;
padding: 40px 30px;
border-radius: 5px;
box-shadow: 5px 5px 20px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.container h1 {
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.image_box {
width: 100%;
height: 200px;
overflow: hidden;
margin-bottom: 20px;
background: #eee;
}
.image_box img {
width: 100%;
max-height: 100%;
}
.text_box {
flex: 1;
}
.text_box .review_title {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
}
.text_box .review_description {
font-size: 14px;
margin-bottom: 10px;
line-height: 1.4;
}
.button_wrap {
margin-top: auto;
}
.button_wrap button {
border: none;
background: transparent;
font-size: 20px;
cursor: pointer;
}
.button_wrap button + button {
margin-left: 30px;
}
.button_wrap button:hover {
color: orange;
}
.button_wrap button:disabled {
opacity: 0.5;
cursor: not-allowed !important;
}
.button_wrap button:disabled:hover {
color: #222;
}
.review_container {
width: 100%;
position: relative;
overflow: hidden;
height: 100%;
}
.review_wrapper {
position: relative;
display: flex;
top: 0;
left: 0;
transition: 0.4s ease-in-out;
}
.review_item {
width: 100%;
flex-shrink: 0;
position: absolute;
top: 0;
left: 0;
}
JS
constructor(target)
1. this.container = document.querySelector(target);
인자로 제어할 DOM의 class명을 받아 this.container에 할당한다.
2. this.wrapper = this.container.querySelector('.review_wrapper');
위에서 할당된 this.container 내부에서
.review_wrapper 클래스를 가진 DOM을 찾아 this.wrapper에 할당
3. this.reviewItems = this.container.querySelectorAll('.review_item');
위에서 할당된 this.container 내부에서
.review_wrapper 클래스를 가진 DOM을 찾아 this.reviewItems 에 할당
4. this.currentIdx = 0;
현재 슬라이드의 번호를 0으로 설정
5. this.prevBtn = this.container.querySelector('[data-action="prev"]')
위에서 할당된 this.container 내부에서 .
[data-action="next"]속성을 가진 DOM을 찾아
this.prevBtn에 할당
6. this.nextBtn = this.container.querySelector('[data-action="next"]');
위에서 할당된 this.container 내부에서 .
[data-action="next"]속성을 가진 DOM을 찾아
this.nextBtn에 할당
7. this.init();
초기화 매서드인 init()을 실행시킨다.
init()
1. this.reviewItems들에게 각각 순서에 맞는 left값 적용
2. nextBtn, prevBtn에 이벤트 리스너 등록
3. 화면을 바꿔주기 위해 render() 매서드 실행
next(), prev()
1. 각 액션에 맞게 currentIdx를 바꿔주기 전 예외처리를 한다.
prev의 경우 0이하로 떨어지지 않게,
next의 경우 최대 슬라이드 갯수를 넘지 않게.
2. 위의 예외처리에서 걸리지 않았다면 각각 currentIdx를 1씩 증감 시킨다.
3. 화면을 바꿔주기 위해 render() 매서드 실행
render()
1. this.wrapper의 left값을 currentIdx값을 참조하여 변경해준다.
2. currentIdx의 값이 0 또는 최댓값일 경우 각각 next, prev 버튼을 비활성화 시켜준다.
class Reviews {
constructor(target) {
this.container = document.querySelector(target);
this.wrapper = this.container.querySelector('.review_wrapper');
this.reviewItems = this.container.querySelectorAll('.review_item');
this.currentIdx = 0;
this.prevBtn = this.container.querySelector('[data-action="prev"]');
this.nextBtn = this.container.querySelector('[data-action="next"]');
this.init();
}
init() {
this.reviewItems.forEach((item, idx) => {
const itemWidth = item.offsetWidth;
item.style.left = `${itemWidth * idx}px`;
});
this.prevBtn.addEventListener('click', this.prev.bind(this));
this.nextBtn.addEventListener('click', this.next.bind(this));
this.render();
}
next() {
if(this.currentIdx < this.reviewItems.length - 1) {
this.currentIdx++;
} else {
this.currentIdx = 0;
}
this.render();
}
prev() {
if(this.currentIdx > 0) {
this.currentIdx--;
} else {
this.currentIdx = this.reviewItems.length - 1;
}
this.render();
}
render() {
this.wrapper.style.left = `${-this.currentIdx * 100}%`;
if(this.currentIdx === 0) {
this.prevBtn.setAttribute('disabled', true);
} else {
this.prevBtn.removeAttribute('disabled');
}
if(this.currentIdx === this.reviewItems.length - 1) {
this.nextBtn.setAttribute('disabled', true);
} else {
this.nextBtn.removeAttribute('disabled');
}
}
}
JS 리팩토링
주요 변경 내용
render()
메서드에서 setAttribute와 removeAttribute를 사용하여 버튼의 disabled 속성을 변경하는 대신, disabled 속성을 직접 설정하고 해제하는 방식으로 변경- 클래스 이름과 메서드 이름을 더 명확하고 의미 있게 변경하여 코드의 가독성을 향상
- CSS의 transform 속성을 사용하여 요소의 위치를 변경하면 애니메이션이 부드럽게 동작하게끔 변경
class Reviews {
constructor(target) {
this.container = document.querySelector(target);
this.wrapper = this.container.querySelector('.review_wrapper');
this.reviewItems = this.container.querySelectorAll('.review_item');
this.prevBtn = this.container.querySelector('[data-action="prev"]');
this.nextBtn = this.container.querySelector('[data-action="next"]');
this.currentIdx = 0;
this.init();
}
init() {
this.reviewItems.forEach((item, idx) => {
const itemWidth = item.offsetWidth;
item.style.transform = `translateX(${itemWidth * idx}px)`;
});
this.prevBtn.addEventListener('click', this.goToPrev.bind(this));
this.nextBtn.addEventListener('click', this.goToNext.bind(this));
this.render();
}
goToNext() {
if (this.currentIdx < this.reviewItems.length - 1) {
this.currentIdx++;
} else {
return;
}
this.render();
}
goToPrev() {
if (this.currentIdx > 0) {
this.currentIdx--;
} else {
return;
}
this.render();
}
render() {
const translateX = -this.currentIdx * 100;
this.wrapper.style.transform = `translateX(${translateX}%)`;
this.prevBtn.disabled = this.currentIdx === 0;
this.nextBtn.disabled = this.currentIdx === this.reviewItems.length - 1;
}
}