프론트엔드/Javascript 연습과제 해답

자바스크립트 문법 3 - 이벤트(Event) 처리하기 해답

syleemomo 2022. 1. 15. 10:46
728x90

 

* 연습과제 1

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>랜덤으로 타일의 색상 변경하기</h1>
    <div id='tile-container'>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div>
       <div class="tile"></div> 
    </div>
    <script src='app.js'></script>
</body>
</html>
body{
    padding: 0;
    margin: 0;

    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
}

#tile-container{
    width: 600px;
    height: 600px;

    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
}
.tile{
    width: 200px;
    height: 200px;
    box-sizing: border-box;
    border: 1px solid skyblue;
}
const tiles = document.querySelectorAll('.tile')
console.log(tiles)

function getRandomColor(){
    const red = Math.floor(Math.random() * 256)
    const green = Math.floor(Math.random() * 256)
    const blue = Math.floor(Math.random() * 256)
    return `rgb(${red}, ${green}, ${blue})`
}
function changeBackground(e){
    e.target.style.background = getRandomColor()
}

for(let tile of tiles){
    console.log(tile)
    tile.addEventListener('click', changeBackground)
}

 

* 연습과제 2

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="photo-container">
        <img src="carousel-1.jpg" alt="">
    </div>
    <script src='app.js'></script>
</body>
</html>
body{
    margin: 0;
    padding: 0;
    text-align: center;
}
.photo-container{
    width: 500px;
    height: 300px;
    margin: 100px auto;
    display: none;
}
.show{
    display: block;
}
const photoContainer = document.querySelector('.photo-container')

function hideImg(){
    photoContainer.classList.remove('show')
}

function showImg(){
    photoContainer.classList.add('show')
    setTimeout(hideImg, 3000)
}

function startEffect(){
    setTimeout(showImg, 1000)
}

window.addEventListener('load', startEffect)

 

* 연습과제 3

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="count-box"></div>
    <script src='app.js'></script>
</body>
</html>
body{
    margin: 0;
    padding: 0;
    text-align: center;
}
#count-box{
    width: 100%;
    height: 100vh;
    text-align: center;
    font-size: 5rem;
    font-weight: bold;
    color: white;
    background-color: thistle;
}
const countBox = document.getElementById('count-box')
let cnt = 0

function countNum() {
    countBox.innerText = cnt
    cnt++
}

setInterval(countNum, 1000);

 

* 연습과제 4

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id='text-box'>
        <p></p>
    </div>
    <script src='app.js'></script>
</body>
</html>
body{
    margin: 0;
    padding: 0;
    text-align: center;
}
#text-box{
    width: 800px;
    height: 100px;
    line-height: 100px;
    margin: 100px auto;
    text-align: center;
    font-size: 2rem;
    font-weight: bold;
    color: white;
    background-color: thistle;
    transition: all .5s;
}
const textBox = document.querySelector('#text-box p')
const text = 'You are watching text now !'
let index = 0
let timerID = null

function displayCharacter(){
    console.log(index)
    textBox.innerHTML += text[index]
    index++
    if(index > text.length - 1){
        console.log('timer ended: ', index)
        clearInterval(timerID)
    }
}
function startTextAnimation(){
    timerID = setInterval(displayCharacter, 500);
}

window.addEventListener('load', startTextAnimation)

 

* 연습과제 5

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel='stylesheet' href='style.css'/>
</head>
<body>
    <script src='app.js'></script>
</body>
</html>
.circle{
    width: 100px;
    height: 100px;
    border-radius: 50%;
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    background-color: peru;
    background-image: url('super-mario-left.jpg');
    background-repeat: no-repeat;
    background-size: cover;
}

super-mario-left.jpg 파일 필요함

function addCircle(e){
    const circle = document.createElement('circle')
    circle.className = 'circle'

    console.log(e.clientX, e.clientY)
    const mouseX = e.clientX
    const mouseY = e.clientY
    circle.style.left = mouseX + 'px'
    circle.style.top = mouseY + 'px'

    document.body.appendChild(circle)
}

window.addEventListener('click', addCircle)

 

* 연습과제 6~10

아래 리스트 만들기 프로젝트의 코드 저장소를 참조해주세요! 

style.css 파일과 app.js 파일에 주석으로 연습과제 번호를 달아두었으니 참고하기 바래요!

 

리스트 만들기 프로젝트 코드 저장소

 

GitHub - sssssqew/photo-gallery: simple photo gallery app

simple photo gallery app. Contribute to sssssqew/photo-gallery development by creating an account on GitHub.

github.com

 

리스트 만들기 프로젝트 최종 완성본

 

사진 갤러리 앱

1orem ipsum dolor sit amet consectetur adipisicing elit. Error modi nesciunt facilis tempora qui temporibus, odio dolores reiciendis, quis voluptate ducimus quam. Voluptatum omnis, rem quam porro ipsam nam dolorum. Laudantium aliquam suscipit, nobis exerci

sssssqew.github.io

 

* 연습과제 11

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel='stylesheet' href='style.css'/>
</head>
<body>
    <button id='open-btn'>open sidebar</button>
    <div class="sidebar">
        <div class="menu">HOME</div>
        <div class="menu">ABOUT</div>
        <div class="menu">CONTACT</div>
        <div class="menu">DETAIL</div>
        <div class="menu">SETTING</div>
    </div>
    <script src='app.js'></script>
</body>
</html>
.sidebar{
    width: 30%;
    height: 100vh;
    position: absolute;
    left: -30%;
    top: 0px;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    transition: all 0.7s ease-in-out;
}
.show{
    left: 0px;
}
.menu{
    width: 100%;
    flex: 1;
    background-color: peru;   
    font-size: 1.5rem; 

    display: flex;
    justify-content: center;
    align-items: center;
    color: lightgoldenrodyellow;
    border-bottom: 1px solid lightgoldenrodyellow;
}
.menu:hover{
    color: peru;
    background-color: lightgoldenrodyellow;
}
const openBtn = document.getElementById('open-btn')
const sidebar = document.querySelector('.sidebar')
let timerID = null

function hideSidebar(){
    sidebar.classList.remove('show')
    clearTimeout(timerID)
}
function openSidebar(){
    sidebar.classList.add('show')

    timerID = setTimeout(hideSidebar, 3000)
}

openBtn.addEventListener('click', openSidebar)

 

* 연습과제 12

최적화된 해답 - html, css 는 해답 0 과 동일함

const photos = document.getElementById('photos')
const selection = document.getElementById('selection')  
const options = selection.querySelectorAll('.options')
const widthOfPhoto = 500 // 사진너비 
let index = 0 // 사진과 셀렉터의 현재 위치를 나타내는 인덱스 
let timerID = null // 타이머 ID 

function changeIndicator(index){
  selection.querySelector('.active').classList.remove('active') // 이전 active 한 셀렉터 제거
  options[index].classList.add('active') // 현재 셀렉터에 active 추가 
}

function changePosition(){
  const photosLength = photos.querySelectorAll('.photo').length // 사진의 총 갯수
  photos.style.marginLeft = (widthOfPhoto * index)*-1 + 'px' // -1 : 왼쪽으로 이동
  changeIndicator(index) // 셀렉터 변경

  index++
  index = index > photosLength - 1 ? 0 : index
}

function startCarousel(){
    timerID = setInterval(changePosition, 1000)
}

function stopCarousel(){
    clearInterval(timerID)
}

function getIndexClicked(e){ // 클릭한 셀렉터의 인덱스값 구하기
  if(e.target.className === 'options'){ // 이벤트 위임으로 셀렉터를 클릭한 경우에만 실행
    
    for(let optionIndex in options){
      if(options[optionIndex] === e.target){
        console.log('clicked element', optionIndex)
        index = optionIndex // 인덱스 업데이트 
        changePosition() // 클릭한 셀렉터 모양 변경 및 사진변경
      }
    }
  }
}

photos.addEventListener('mouseenter', startCarousel)
photos.addEventListener('mouseleave', stopCarousel)
selection.addEventListener('click', getIndexClicked) // 추가된 부분

 

해답 0 

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Carosel</h1>
    <div id='photo-container'>
        <div id="photos">
            <div class='photo'></div>
            <div class="photo"></div>
            <div class="photo"></div>
            <div class="photo"></div>
            <div class="photo"></div>
        </div>
    </div>
    <div id='selection'>
        <div class="options active"></div>
        <div class="options"></div>
        <div class="options"></div>
        <div class="options"></div>
        <div class="options"></div>
    </div>
    <script src='app.js'></script>
</body>
</html>

options 에 id 를 사용하지 않고 구현하는 방법은 위와 같다. 

body{
  margin: 0;
  padding: 0;
  text-align: center;
}
#photo-container{
  width: 500px;
  height: 250px;
  margin: 100px auto;
  overflow: hidden;
}
#photos{
  width: 2500px;
  height: 100%;
  cursor: pointer;

  display: flex;
  transition: all 1s;

  /* margin-left: -500px; */
}
.photo{
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-size: cover;
}
.photo:nth-child(1) {
  background: url("https://media.istockphoto.com/photos/forest-wooden-table-background-summer-sunny-meadow-with-green-grass-picture-id1353553203?b=1&k=20&m=1353553203&s=170667a&w=0&h=QTyTGI9tWQluIlkmwW0s7Q4z7R_IT8egpzzHjW3cSas=") no-repeat center;
}
.photo:nth-child(2) {
  background: url("https://prod-virtuoso.dotcmscloud.com/dA/188da7ea-f44f-4b9c-92f9-6a65064021c1/previewImage/PowerfulReasons_hero.jpg") no-repeat center;
}
.photo:nth-child(3) {
  background: url("http://file.instiz.net/data/file/20121125/5/6/0/5609249762358c2bcb65a46580549c99") no-repeat center;
}
.photo:nth-child(4) {
  background: url("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSN_Dap1JjDe2mQpi2FeEfC-pIXJmRbpYHmug&usqp=CAU") no-repeat center;
}
.photo:nth-child(5){
  background: url('https://www.institutostrom.org/wp-content/uploads/2018/04/NZ.jpg') no-repeat center;
}

/* 커로셀 셀렉터 */
#selection{
  width: 300px;
  height: 50px;
  margin: 0px auto;

  display: flex;
  justify-content: center;
  align-items: center;
}
.options{
  width: 20px;
  height: 20px;
  border-radius: 50%;
  margin-left: 10px;
  background-color: lightgreen;
  box-shadow: 1px 1px 5px 2px lightgreen;
  user-select: none; /* 커서 제거 */
  transition: all 0.7s;
}
.options:hover{
  background-color: greenyellow;
  cursor: pointer;
}
.options.active{
  width: 50px;
}
const photos = document.getElementById('photos')
const selection = document.getElementById('selection') // 추가된 부분 
const widthOfPhoto = 500 // 사진너비 
let marginLeft = widthOfPhoto // 사진이동 간격 
let timerID = null // 타이머 ID 

function changeIndicator(index){
  const selection = document.getElementById('selection')
  const activeOption = selection.querySelector('.active')
  if(activeOption) activeOption.classList.remove('active') // 이전 active 한 인디케이터 제거
  selection.querySelectorAll('.options')[index].classList.add('active') // 현재 인디케이터에 active 추가 
}

function changePosition(){
  const photosLength = photos.querySelectorAll('.photo').length // 사진의 총 갯수
  photos.style.marginLeft = marginLeft*-1 + 'px' // -1 : 왼쪽으로 이동

  const index = parseInt(marginLeft / widthOfPhoto) // 현재 사진의 인덱스값 
  changeIndicator(index) // 인디케이터 변경

  marginLeft = marginLeft >= (widthOfPhoto * (photosLength - 1)) ? 0 : marginLeft + widthOfPhoto // 사진 너비만큼 이동
}

function startCarousel(){
    timerID = setInterval(changePosition, 1000)
}

function stopCarousel(){
    clearInterval(timerID)
}

function getIndexClicked(e){ // 클릭한 셀렉터의 인덱스값 구하기
  if(e.target.className === 'options'){
    const options = selection.querySelectorAll('#selection .options')
    
    for(let index in options){
      if(options[index] === e.target){
        console.log('clicked element', index)
        marginLeft = widthOfPhoto * index // 사진이동 간격 설정하기 
        changePosition() // 클릭한 셀렉터 모양 변경 및 사진변경
      }
    }
  }
}

photos.addEventListener('mouseenter', startCarousel)
photos.addEventListener('mouseleave', stopCarousel)
selection.addEventListener('click', getIndexClicked) // 추가된 부분

 

해답 1

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Carosel</h1>
    <div id='photo-container'>
        <div id="photos">
            <div class='photo'></div>
            <div class="photo"></div>
            <div class="photo"></div>
            <div class="photo"></div>
            <div class="photo"></div>
        </div>
    </div>
    <div id='selection'>
        <div class="options active" id="0"></div>
        <div class="options" id="1"></div>
        <div class="options" id="2"></div>
        <div class="options" id="3"></div>
        <div class="options" id="4"></div>
    </div>
    <script src='app.js'></script>
</body>
</html>
body{
  margin: 0;
  padding: 0;
  text-align: center;
}
#photo-container{
  width: 500px;
  height: 250px;
  margin: 100px auto;
  overflow: hidden;
}
#photos{
  width: 2500px;
  height: 100%;
  cursor: pointer;

  display: flex;
  transition: all 1s;

  /* margin-left: -500px; */
}
.photo{
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-size: cover;
}
.photo:nth-child(1) {
  background: url("https://media.istockphoto.com/photos/forest-wooden-table-background-summer-sunny-meadow-with-green-grass-picture-id1353553203?b=1&k=20&m=1353553203&s=170667a&w=0&h=QTyTGI9tWQluIlkmwW0s7Q4z7R_IT8egpzzHjW3cSas=") no-repeat center;
}
.photo:nth-child(2) {
  background: url("https://prod-virtuoso.dotcmscloud.com/dA/188da7ea-f44f-4b9c-92f9-6a65064021c1/previewImage/PowerfulReasons_hero.jpg") no-repeat center;
}
.photo:nth-child(3) {
  background: url("http://file.instiz.net/data/file/20121125/5/6/0/5609249762358c2bcb65a46580549c99") no-repeat center;
}
.photo:nth-child(4) {
  background: url("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSN_Dap1JjDe2mQpi2FeEfC-pIXJmRbpYHmug&usqp=CAU") no-repeat center;
}
.photo:nth-child(5){
  background: url('https://www.institutostrom.org/wp-content/uploads/2018/04/NZ.jpg') no-repeat center;
}

/* 커로셀 셀렉터 */
#selection{
  width: 300px;
  height: 50px;
  margin: 0px auto;

  display: flex;
  justify-content: center;
  align-items: center;
}
.options{
  width: 20px;
  height: 20px;
  border-radius: 50%;
  margin-left: 10px;
  background-color: lightgreen;
  box-shadow: 1px 1px 5px 2px lightgreen;
  user-select: none; /* 커서 제거 */
  transition: all 0.7s;
}
.options:hover{
  background-color: greenyellow;
  cursor: pointer;
}
.options.active{
  width: 50px;
}
const photos = document.getElementById('photos')
const selection = document.getElementById('selection') // changeIndicator 함수 내부로부터 이동한 부분
const widthOfPhoto = 500 // 사진너비 
let marginLeft = widthOfPhoto // 사진이동 간격 
let timerID = null // 타이머 ID 

function changeIndicator(index){
  const activeOption = selection.querySelector('.active')
  if(activeOption) activeOption.classList.remove('active') // 이전 active 한 인디케이터 제거
  selection.querySelectorAll('.options')[index].classList.add('active') // 현재 인디케이터에 active 추가 
}

function changePosition(){
  const photosLength = photos.querySelectorAll('.photo').length // 사진의 총 갯수
  photos.style.marginLeft = marginLeft*-1 + 'px' // -1 : 왼쪽으로 이동

  const index = parseInt(marginLeft / widthOfPhoto) // 현재 사진의 인덱스값 
  changeIndicator(index) // 인디케이터 변경

  marginLeft = marginLeft >= (widthOfPhoto * (photosLength - 1)) ? 0 : marginLeft + widthOfPhoto // 사진 너비만큼 이동
}
function changePhoto(e){ // 추가된 부분 
  if(e.target.className === 'options'){
    marginLeft = parseInt(e.target.id) * widthOfPhoto
    changePosition()
  }
}

function startCarousel(){
    timerID = setInterval(changePosition, 1000)
}

function stopCarousel(){
    clearInterval(timerID)
}

photos.addEventListener('mouseenter', startCarousel)
photos.addEventListener('mouseleave', stopCarousel)
selection.addEventListener('click', changePhoto) // 추가된 부분

 

* 연습과제 13

const boxes = document.querySelectorAll('.box')
let select = 0
let prevSelect = 0

function moveBox(){
    const prevBox = boxes[prevSelect]
    const box = boxes[select]

    prevBox.style.marginBottom = 0 + 'px'
    box.style.marginBottom = 50 + 'px'

    prevSelect = select
    select++
    if(select > boxes.length - 1){
        select = 0
    }
}
function startMove(){
    setInterval(moveBox, 500)
}

window.addEventListener('load', startMove)

 

* 연습과제 14

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="menu-container">
        <div class="menu">
            <div class='title'>사과</div>
            <div class='info'>
                <h4>단가 : 700원</h4>
                <h4>색깔 : 빨간색</h4>
            </div>
        </div>
        <div class="menu">
            <div class='title'>바나나</div>
            <div class='info'>
                <h4>단가 : 200원</h4>
                <h4>색깔 : 노란색</h4>
            </div>
        </div>
        <div class="menu">
            <div class='title'>오렌지</div>
            <div class='info'>
                <h4>단가 : 1000원</h4>
                <h4>색깔 : 주황색</h4>
            </div>
        </div>
    </div>
    <script src='app.js'></script>
</body>
</html>
body{
    margin: 0;
    padding: 0;
}
#menu-container{
    width: 500px;
    margin: 100px auto;

    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}
.menu{
    width: 100%;
    flex: 1;
    margin-bottom: 10px;
    cursor: pointer;
    user-select: none;
}
.title{
    background: lightgray;
    padding: 10px;
}
.info{
    padding: 10px;
    background: lightgoldenrodyellow;
    color: goldenrod;
    font-weight: bold;
    display: none;
    transition: all 0.5s;
}
.open{
    display: block;
}
const menuContainer = document.getElementById('menu-container')
let prevTarget = null

function openMenu(e){
    const target = e.target

    if(prevTarget !== null){
        prevTarget.nextElementSibling.classList.remove('open')
    }
    if(target.className === 'title'){
        console.dir(target)
        target.nextElementSibling.classList.add('open')
        prevTarget = target
    }
}

menuContainer.addEventListener('click', openMenu)

위와 같이 작성하면 하나의 아이템 정보만 열리기는 하지만 열려있던 아이템 정보를 보이지 않게 하지는 못한다. 열려있던 아이템 정보를 다시 클릭해서 숨기려면 아래와 같이 자바스크립트 코드를 수정하면 된다. 

const menuContainer = document.getElementById('menu-container')
let prevTarget = null

function openMenu(e){
    const prevInfo = document.querySelector('.open')
    if(prevInfo) prevInfo.classList.remove('open')

    if(e.target.classList.contains('title') && e.target.nextElementSibling !== prevInfo){
        e.target.nextElementSibling.classList.toggle('open')
    }
}

menuContainer.addEventListener('click', openMenu)

하지만 이전에 열려있던 아이템과 현재 클릭한 아이템이 동일하면 classList 의 remove 메서드 때문에 아이템 정보가 숨겨졌다가 toggle 에 의해서 다시 열린다. 즉, toggle 메서드는 이전의 아이템과 현재 아이템이 다른 경우에만 적용해야 한다. 그렇게 해야 이전에 열려있던 아이템 정보를 닫고, 현재 아이템 정보는 토글해서 열린다. 

 

연습과제 15

<!DOCTYPE html>
<html lang="en">
<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>자바스크립트 연습</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id='contents'></div>
    <div id="page-btns"></div>
    <script src='app.js'></script>
</body>
</html>
body{
  margin: 0;
  padding: 0;
  text-align: center;
}
#contents{
  width: 100%;
  min-height: 100vh;
  background-color: thistle;
  color: white;
  font-weight: bold;
  /* padding: 15px; */

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.list-item{
  width: 500px;
  height: 100px;
  background-color: white;
  color: purple;
  margin: 10px;
}
#page-btns{
  position: fixed;
  bottom: 50px;
  left: 50%;
  transform: translate(-50%);
}
.page-btn{
  width: 50px;
  height: 50px;
  background-color: purple;
  border-radius: 10px;
  margin-right: 5px;
  cursor: pointer;
  color: white;
  font-size: 1rem;
  font-weight: bold;
  border: none;
  outline: none;
}
const pageContents = [] // 데이터를 담고 있는 배열
const numOfData = 120 // 전체 리스트 수
const limit = 4 // 페이지당 보여줄 리스트 수
const numOfBtns = 5 // 페이지당 보여줄 버튼 수 
const totalNumOfBtns = Math.ceil(numOfData/limit) // 총 버튼수
let offset = 0, btnOffset = 0

const contents = document.getElementById('contents')
const pageBtns = document.getElementById('page-btns')

// 버튼 생성하기
function buildBtn(contents){
  const btn = document.createElement('button')
  btn.className = 'page-btn'
  btn.innerText = contents
  return btn 
}

// 화면에 페이지네이션 버튼 보여주기
function showPaginationBtns(pageBtns, btnOffset, numOfBtns){
  pageBtns.innerHTML = ''
  if(btnOffset > 0){
    pageBtns.appendChild(buildBtn("<"))
  }

  for(let i=btnOffset;i<btnOffset+numOfBtns;i++){
    if(i >= 0 && i < totalNumOfBtns){
      pageBtns.appendChild(buildBtn(i+1))
    }
  }
  if(btnOffset + numOfBtns < totalNumOfBtns){
    console.log(btnOffset, numOfBtns, totalNumOfBtns)
    console.log(btnOffset, numOfBtns)
    pageBtns.appendChild(buildBtn(">"))
  }
}

function showList(pageContents, offset, limit, contents){
  contents.innerHTML = '' // 화면 초기화
  for(let i=offset; i<offset+limit; i++){
    const listItem = pageContents[i]

    if(listItem){
      contents.innerHTML += `
                <div id=${listItem.id} class='list-item'>
                    <h3>${listItem.name} (${listItem.id})</h3>
                    <h3>${listItem.age}</h3>
                </div>
            `
    }
  }
}

// 리스트 배열 만들기
for(let i=0;i<numOfData;i++){
    pageContents.push({ name: 'sunrise', age: 20, id: i })
}
console.log(pageContents)


function changePage(e){
    const target = e.target
    if(target.className === 'page-btn'){
        if(target.innerText === '>'){
          btnOffset += numOfBtns
          showPaginationBtns(pageBtns, btnOffset, numOfBtns)
        }else if(target.innerText === '<'){
          btnOffset -= numOfBtns
          showPaginationBtns(pageBtns, btnOffset, numOfBtns)
        }else{
          const indexSelected = parseInt(target.innerText) - 1
          offset = limit * indexSelected // 콘텐츠 시작점(오프셋) = (페이지번호 - 1) x 페이지당 보여줄 갯수
          showList(pageContents, offset, limit, contents) // 클릭한 페이지 로딩
        }
    }
}


showList(pageContents, offset, limit, contents) 
showPaginationBtns(pageBtns, btnOffset, numOfBtns)

pageBtns.addEventListener('click', changePage)

 

const numOfBtns = 5 // 페이지당 보여줄 버튼 수 
const totalNumOfBtns = Math.ceil(numOfData/limit) // 총 버튼수
let offset = 0, btnOffset = 0

페이지당 보여줄 버튼수는 이전/다음 버튼을 누를때마다 생성할 버튼의 갯수이다. btnOffset 은 페이지가 전환될때마다 보여줄 버튼의 시작번호이다. 

// 버튼 생성하기
function buildBtn(contents){
  const btn = document.createElement('button')
  btn.className = 'page-btn'
  btn.innerText = contents
  return btn 
}

버튼을 생성하는 부분을 함수로 정의하였다. 

// 화면에 페이지네이션 버튼 보여주기
function showPaginationBtns(pageBtns, btnOffset, numOfBtns){
  pageBtns.innerHTML = ''
  if(btnOffset > 0){
    pageBtns.appendChild(buildBtn("<"))
  }

  for(let i=btnOffset;i<btnOffset+numOfBtns;i++){
    if(i >= 0 && i < totalNumOfBtns){
      pageBtns.appendChild(buildBtn(i+1))
    }
  }
  if(btnOffset + numOfBtns < totalNumOfBtns){
    console.log(btnOffset, numOfBtns, totalNumOfBtns)
    console.log(btnOffset, numOfBtns)
    pageBtns.appendChild(buildBtn(">"))
  }
}

화면에 페이지네이션 버튼을 보여주는 함수이다. 이때 btnOffset 이 제로(0)보다 큰 경우에만 이전 버튼을 보여준다. 초기 로딩시에 btnOffset 은 제로(0)이므로 이전 버튼은 숨긴다.

해당 페이지에 보여줄 버튼은 시작점(btnOffset)부터 끝점(btnOffset+numOfBtns)까지 버튼을 생성하고 화면에 보여준다. 이때 해당 페이지에 보여줄 버튼이 numOfBtns (5) 보다 적은 경우에는 numOfBtns (5)만큼 생성하면 안된다. 그러므로 조건문을 이용하여 페이지네이션 버튼의 숫자가 1 이상이고 totalNumOfBtns (페이지수) 이하인 경우에만 버튼을 생성하도록 한다.

만약 마지막 버튼의 숫자(btnOffset+numOfBtns)가  totalNumOfBtns (페이지수) 보다 커지면 더이상 다음 페이지는 없으므로 조건문을 이용하여 다음 버튼은 생성하지 않도록 한다. 

function showList(pageContents, offset, limit, contents){
  contents.innerHTML = '' // 화면 초기화
  for(let i=offset; i<offset+limit; i++){
    const listItem = pageContents[i]

    if(listItem){
      contents.innerHTML += `
                <div id=${listItem.id} class='list-item'>
                    <h3>${listItem.name} (${listItem.id})</h3>
                    <h3>${listItem.age}</h3>
                </div>
            `
    }
  }
}

해당 페이지의 내용을 보여주는 부분이다. 리스트 아이템의 시작점(offset)부터 끝점(offset+limit)까지 리스트 아이템 정보를 HTML 코드로 변경하고 화면에 보여준다. 

페이지당 보여줄 리스트 아이템의 갯수는 limit 이지만 마지막 페이지에는 보여줄 갯수가 limit 보다 적은 경우가 있으므로 이때는 조건문을 이용하여 listItem 이 존재하는 경우에만 해당 아이템을 contents 에 추가할 수 있도록 한다. 

showList(pageContents, offset, limit, contents) 
showPaginationBtns(pageBtns, btnOffset, numOfBtns)

웹 화면 초기 로딩시에 리스트와 페이지네이션 버튼을 보여주기 위하여 해당 함수들을 실행한다. 

// 리스트 배열 만들기
for(let i=0;i<numOfData;i++){
    pageContents.push({ name: 'sunrise', age: 20, id: i })
}
console.log(pageContents)

실무에서는 리스트 데이터를 서버에서 조회해서 가져오지만 현재는 서버가 없으므로 가상으로 데이터를 생성한다.

function changePage(e){
    const target = e.target
    if(target.className === 'page-btn'){
        if(target.innerText === '>'){
          btnOffset += numOfBtns
          showPaginationBtns(pageBtns, btnOffset, numOfBtns)
        }else if(target.innerText === '<'){
          btnOffset -= numOfBtns
          showPaginationBtns(pageBtns, btnOffset, numOfBtns)
        }else{
          const indexSelected = parseInt(target.innerText) - 1
          offset = limit * indexSelected // 콘텐츠 시작점(오프셋) = (페이지번호 - 1) x 페이지당 보여줄 갯수
          showList(pageContents, offset, limit, contents) // 클릭한 페이지 로딩
        }
    }
}

다음 버튼을 누르면 btnOffset 이 numOfBtns (5)만큼 증가하여 다음 페이지네이션 버튼 세트를 보여준다. 이전 버튼을 누르면 btnOffset 이 numOfBtns (5)만큼 감소하여 이전 페이지네이션 버튼 세트를 보여준다. 페이지네이션 버튼을 클릭하면 해당 페이지의 리스트 목록을 보여준다. 

728x90