📝

Chapter 5. 실무 응용

 
이번 장은 웹사이트를 구현할 때 사용되는 UI를 직접 구현해 보는 실습 내용을 담았습니다. 이 실습을 통해 실제 웹사이트를 만들 때 DOM API는 어떠한 상황에 사용되는지 직접 경험하실 수 있을 겁니다.
 

1. 캐러셀 만들기

1.1. 캐러셀이란?

캐러셀은 여러 개의 이미지를 슬라이더 형태로 만들어 표현해 주는 UI입니다. 용어가 낯설 수 있지만 아래의 예시를 보면 캐러셀이 어떻게 동작하는지 경험적인 직감으로 아실 겁니다. 예시로 가져온 웹페이지 양옆에는 ‘<’ 와 ‘>’버튼이 있습니다. 버튼을 클릭하면 어떻게 동작할지 상상해 보세요.
삼성삼성
삼성
 
양옆의 버튼을 클릭하면 이미지가 각각 좌우로 슬라이드 되면서 또 다른 이미지를 보여줍니다. 이러한 방식으로 유저는 스크롤을 내리지 않고도 많은 정보를 볼 수 있습니다. 그럼 이제부터 캐러셀을 만들어보겠습니다. 캐러셀을 구현하는 방식은 간단합니다. 일정한 크기의 컨테이너를 만들고, 이미지를 가로로 배치합니다. 그 다음 버튼을 누르면 해당 이미지를 컨테이너 안으로 이동시켜 화면에 보이게 조작할 것입니다.
notion imagenotion image

1.2. 실습 준비하기

실습을 위해 작업하기 편한 IDE 환경에서 캐러셀을 구현할 HTML, JS, CSS 파일을 생성해 주세요.
notion imagenotion image
 

1.3. UI 구성하기

먼저 HTML파일에 캐러셀을 만든 후에, 이미지를 담을 컨테이너와 캐러셀을 동작할 버튼 두 개를 만들어 줍니다. 컨테이너에는 자식 요소로 이미지 세 개를 넣겠습니다.
<!-- index.html --> <!DOCTYPE html> <html lang="ko"> <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" /> <link rel="stylesheet" href="./style.css" /> <title>Carousel</title> </head> <body> <div class="carousel"> <div class="container"> <img src="https://cdn.pixabay.com/photo/2019/09/17/02/20/jeju-4482313__340.jpg" alt="" /> <img src="https://cdn.pixabay.com/photo/2015/09/10/14/11/jeju-934479__340.jpg" alt="" /> <img src="https://cdn.pixabay.com/photo/2020/02/01/17/51/horse-4810956__340.jpg" alt="" /> </div> <button class="btn-prev" type="button"><</button> <button class="btn-next" type="button">></button> </div> <script src="./main.js"></script> </body> </html>
 
이렇게 마크업을 작성하면 다음과 같은 화면이 나오게 됩니다.
notion imagenotion image
이제 이미지들을 캐러셀로 만들어주기 위해 기본적인 스타일링을 해보겠습니다. CSS가 익숙하지 않다면, 가급적 브라우저를 한쪽 화면에 띄워 놓고 각 스타일 코드가 실제로 어떠한 변화를 주는지 확인하면서 작성해 보세요.
 
/* style.css */ * { box-sizing: border-box; } body { margin: 0; } .carousel { width: 500px; height: 300px; /* overflow: hidden; */ } .container { display: flex; height: 100%; } .container > img { min-width: 100%; }
 
캐러셀에 너비와 높이를 선언해 주고 각각의 이미지의 크기를 컨테이너에 맞춰주었습니다. 캐러셀은 좌, 우로 슬라이드 하며 이미지를 보여주기 때문에 display: flex 속성 사용하여 가로 정렬을 하였습니다. 결과는 다음과 같습니다.
notion imagenotion image
 
개발자 도구를 통해 확인해 보면 이미지를 감싸는 캐러셀의 너비를 500px로 선언해 주었지만 이미지가 컨테이너의 너비를 벗어났습니다. overflow 속성을 사용하여 캐러셀을 벗어난 이미지를 숨겨주겠습니다.
/* style.css */ .carousel { width: 500px; height: 300px; /* 이미지를 컨테이너에 맞추기 위해 잘라냅니다. */ overflow: hidden; }
 
그러면 저희가 의도했던 기본 스타일은 완성입니다.
notion imagenotion image
그런데 슬라이드 버튼이 같이 사라졌습니다. overflow: hidden 속성이 캐러셀을 벗어난 컨텐츠를 모두 잘라냈기 때문입니다. 저희는 보통 캐러셀처럼 슬라이드 버튼을 캐러셀 안쪽에 배치하고자 합니다. 그러기 위해서 먼저 캐러셀에 position: relative 속성을 선언해 주겠습니다.
/* style.css */ .carousel { width: 500px; height: 300px; position: relative; overflow: hidden; }
 
이제 버튼에 position: absoulte 속성을 사용하면 캐러셀을 기준으로 버튼을 배치할 수 있습니다. 디자인을 위한 스타일 코드도 함께 추가하겠습니다.
/* style.css */ .btn-prev, .btn-next { position: absolute; top: 50%; transform: translateY(-50%); font-size: 60px; height: 1em; width: 1em; line-height: 1; cursor: pointer; background: inherit; border: inherit; padding: 0; } .btn-prev { left: 0; } .btn-next { right: 0; }
 
이제 제법 캐러셀 같군요!
notion imagenotion image
 

1.4. 기능 구현하기

이제 이전, 다음 버튼을 구현하겠습니다. 먼저 버튼과 이미지들을 가지고 있는 컨테이너를 가져옵니다.
/* main.js */ const container = document.querySelector(".container"); const prevButton = document.querySelector('.btn-prev'); const nextButton = document.querySelector('.btn-next');
 
그다음 버튼을 클릭하였을 때 이미지가 넘어가도록 할 예정입니다. 버튼에 클릭 이벤트를 선언해 주겠습니다.
/* main.js */ const container = document.querySelector(".container"); const prevButton = document.querySelector('.btn-prev'); const nextButton = document.querySelector('.btn-next'); prevButton.addEventListener('click', () => { }); nextButton.addEventListener('click', () => { });
 
이벤트를 선언하였다면 버튼에서 클릭 이벤트가 발생하였을 때 실행될 이벤트 핸들러를 작성해 주면 됩니다. 그런데 코드를 작성하기 전에 두 가지 문제를 해결해야겠네요. 먼저 이미지를 어떤 스타일을 통해 좌측, 우측으로 이동시킬 것인가입니다. 저희는 transform 속성을 사용하여 이미지를 제어하겠습니다. 요소의 이동이 필요할 때는 transform 속성을 사용하는 것이 성능적으로 좋으니까요. 두 번째 문제는 컨테이너에 첫 번째 이미지가 담긴 상태에서 ‘이전’버튼을 클릭하였을 때 이벤트를 어떻게 막을 것인가입니다. 아래 그림을 보면서 고민해 보세요.
notion imagenotion image
 
이렇게 동작 시키기 위해서는 클릭을 하였을 때 이미지가 몇 개 있는지 감지하고 처음과 마지막을 판단해야 합니다. 그래서 이미지의 순서를 감지하기 위한 변수를 함께 선언해 주겠습니다.
/* main.js */ let index = 0;
 
버튼을 클릭할 때마다 index 값을 변경해 주고, 그 값을 통해 이벤트 실행 여부를 판단합니다. 이전 버튼을 클릭하면 index의 값을 1 감소시키고, 다음 버튼을 클릭하면 index의 값을 1 증가시켜주겠습니다. 만약 컨테이너에 첫 번째 이미지가 담겨있다면 '이전' 버튼을 클릭하였을 때 이벤트가 실행되면 안 됩니다. 그러므로 '이전' 버튼 이벤트에는 index의 값이 0이면 return 하여 이벤트를 종료할 것입니다. 반대의 경우도 마찬가지입니다. 현재 이미지가 총 세 개이므로 index의 값이 2일 때 '다음' 버튼 이벤트를 return 시켜 준다면 이벤트를 종료할 수 있습니다.
/* main.js */ const container = document.querySelector(".container"); const prevButton = document.querySelector('.btn-prev'); const nextButton = document.querySelector('.btn-next'); let index = 0; prevButton.addEventListener('click', () => { if (index === 0) return; index -= 1; }); nextButton.addEventListener('click', () => { if (index === 2) return; index += 1; });
 
다음 슬라이드에 이미지 존재 여부에 따른 이벤트 조건을 만들었습니다. 그러면 이미지가 존재할 때 좌측 혹은 우측으로 슬라이드 하는 스타일을 추가해 주겠습니다. translate라는 transform 속성을 사용할 겁니다. 이 속성은 리플로우, 리페인트를 발생하지 않고 컴포지션 단계만 다시 수행하는 속성이죠. 레이아웃에 대한 연산을 하지 않기 때문에 사용성을 보다 좋게 만듭니다. 캐러셀은 x축으로만 좌표가 이동되니 translateX를 써주면 되겠네요!
현재 이미지가 총 3개 있으며 index는 0부터 시작됩니다. 컨테이너의 너비가 500px라는 점을 고려하여 컨테이너를 500px만큼 좌측으로 밀어낸다면, 오른쪽에 있는 다음 이미지를 볼 수 있을 것입니다.
/* main.js */ const container = document.querySelector(".container"); const prevButton = document.querySelector('.btn-prev'); const nextButton = document.querySelector('.btn-next'); let index = 0; prevButton.addEventListener('click', () => { if (index === 0) return; index -= 1; container.style.transform = `translateX(-${500 * index}px)`; }); nextButton.addEventListener('click', () => { if (index === 2) return; index += 1; container.style.transform = `translateX(-${500 * index}px)`; });
 
다시 말하자면 컨테이너의 너비는 500px이고, 첫 번째 이미지는 원점부터 500px만큼의 차지하고 있으니 그다음 이미지는 500px 너머에 있을 것입니다. 따라서, 현재와 동일한 너비만큼 왼쪽으로 밀어낸다면 다음 이미지가 기존에 있는 이미지의 자리를 차지할 것입니다.
버튼을 눌러 보세요. 캐러셀이 잘 작동하나요? 그렇다면 이미지가 부드럽게 슬라이드하도록 도와줄 아래의 스타일 코드를 CSS에 추가하고, 버튼을 눌러 다시금 결과를 확인해 보세요.
/* style.css */ .container { /* 버튼 클릭 시 슬라이드되는 속도를 조절합니다. */ transition: transform 0.3s ease-in-out; }
 
이렇게 캐러셀의 마지막 기능까지 모두 구현하였습니다.
 

1.5. 최종 코드

<!-- index.html --> <!DOCTYPE html> <html lang="ko"> <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" /> <link rel="stylesheet" href="./style.css" /> <title>Carousel</title> </head> <body> <div class="carousel"> <div class="container"> <img src="https://cdn.pixabay.com/photo/2019/09/17/02/20/jeju-4482313__340.jpg" alt="" /> <img src="https://cdn.pixabay.com/photo/2015/09/10/14/11/jeju-934479__340.jpg" alt="" /> <img src="https://cdn.pixabay.com/photo/2020/02/01/17/51/horse-4810956__340.jpg" alt="" /> </div> <button class="btn-prev" type="button"><</button> <button class="btn-next" type="button">></button> </div> <script src="./main.js"></script> </body> </html>
 
/* style.css */ * { box-sizing: border-box; } body { margin: 0; } .carousel { position: relative; overflow: hidden; width: 500px; height: 300px; } .container { display: flex; height: 100%; transition: transform 0.3s ease-in-out; } .container > img { min-width: 100%; } .btn-prev, .btn-next { position: absolute; top: 50%; transform: translateY(-50%); font-size: 60px; height: 1em; width: 1em; line-height: 1; cursor: pointer; background: inherit; border: inherit; padding: 0; } .btn-prev { left: 0; } .btn-next { right: 0; }
 
/* main.js */ const container = document.querySelector(".container"); const prevButton = document.querySelector(".btn-prev"); const nextButton = document.querySelector(".btn-next"); let index = 0; prevButton.addEventListener("click", () => { if (index === 0) return; index -= 1; container.style.transform = `translateX(-${500 * index}px)`; }); nextButton.addEventListener("click", () => { if (index === 2) return; index += 1; container.style.transform = `translateX(-${500 * index}px)`; });
 

2. 마치며

DOM은 웹을 동적으로 구현할 때 필요합니다. DOM이 아니면 개발자는 페이지 컨텐츠에 접근하고 조작을 가할 수 없습니다.
5장에서 다룬 코드에는 요소 접근 및 조작, 이벤트, 렌더링 최적화 등 지금까지 다룬 내용들 중 상당 부분이 녹아들어 있습니다.
지금까지의 내용을 잘 따라온 독자라면 DOM을 활용하여 캐러셀을 만드는 데에 어려움이 없었으리라 기대합니다. 만약 아직 DOM이 익숙치 않다고 생각된다면 이 예제를 반복해서 작성하시기를 권해드립니다.
인생은 성장의 연속된 과정이며 지식을 탐구하는 행위는 그의 중요한 밑거름이 됩니다. 앞으로도 지적 욕구를 바탕으로 이해와 배움을 추구하며 아름답게 성장해 나가시길 바랍니다!