본문 바로가기

DOM

DOM 기초(5) - 이미지 슬라이드, preview / next 버튼 구현

728x90
반응형

1) visual 영역 구현

    1-1) 이미지 2개 슬라이드 구현
    1-2) 이미지 3개 슬라이드 및 preview / next 버튼 구현

    1-3) 실행 순서

 

 

 

 

 

1) visual 영역 구현

1-1) 이미지 2개 슬라이드 구현

 

(중요!!) classList와 className의 차이점

  • classList는 add와 remove를 써서 기존 class가 있다면 해당 class 이름은 그냥 둔 상태(즉, 기존에 이미 존재하던 class 이름을 수정하지 않는다는 것이 point임!!)에서 새로운 이름을 추가(add)하거나 삭제(remove)할 수 있음!!
  • className은 기존에 이미 class 이름이 존재한다면 해당 class 이름을 새로 할당한 이름으로 바꿔줌(즉, 기존에 이미 존재하던 class 이름을 완전히 바꿔버린다는 것이 point임!!)!!

 

 

index.html

<!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>Document</title>
    <link rel="stylesheet" href="./public/css/index.css" />
    <script src="./public/js/index.js" type="text/javascript"></script>
  </head>
  <body>
    <ul id="visual">
      <li>
        <img
          src="http://www.kiweb.or.kr/nBoard/upload/file/main/1666596927_56073_1.jpg"
          alt="img1"
        />
      </li>
      <li>
        <img
          src="http://www.kiweb.or.kr/nBoard/upload/file/main/1661838619_28666_1.png"
          alt="img2"
        />
      </li>
    </ul>
  </body>
</html>

 

index.css

* {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

#visual {
  position: relative;
  width: 1900px;
  height: 500px;
  margin: 0 auto;
  overflow: hidden;
  /* overflow: hidden;을 적용한 element의 영역을 해당 element의 자식 element가 넘어갈 시 넘어간 부분은 브라우저 화면에 보이지 않게 함!!(단, 보이지 않는 부분은 완전히 사라진 것이 아니라 브라우저 화면에 안 보일 뿐 존재는 하고 있음!!) */
}

#visual > li {
  position: absolute;
  transition: all 3s;
}

#visual > li:nth-child(1) {
  left: 0;
}

#visual > li:nth-child(2) {
  left: 1920px;
}

#visual > li.on {
  left: 0;
}

#visual > li.off {
  left: -1920px;
}



/* hover를 써서 커서를 갔다댔을 시에만 이미지가 이동하도록 하게 하는 코드 */
/*
#visual:hover > li.on {
  left: 0;
}

#visual:hover > li.off {
  left: -1920px;
}



#visual > li.on {
  animation-name: on; >> 일종의 변수같은 개념
  animation-duration: 0.5s; >> 애니메이션 지속 시간 설정(마치 transition의 애니메이션 적용 시간을 정하는 것과 같은 역할로 보면 됨)
}

#visual > li.off {
  animation-name: off;
  animation-duration: 0.5s;
}


@keyframes는 위의 animation-name(마치 자바스크립트의 변수와 같다고 이해하면 됨!!)의 값과 일치시겨 서로를 연결시켜 애니메이션 효과를 주는 방식이다!!
"0%"는 처음 시작 지점을, "100%"는 최종 완료 지점을 의미함

@keyframes on {
  0% {
    left: 1920px;
  }
  100% {
    left: 0;
  }
}

@keyframes off {
  0% {
    left: 0;
  }
  100% {
    left: -1920px;
  }
}
*/

 

index.js

function init() {
  const imgs = document.querySelectorAll("#visual > li");
  let count = 0;

  // setInterval 콜백함수 slide
  function slide() {
    if (count % 2 === 0) {
      imgs[0].className = "off";
      imgs[1].className = "on";
      imgs[1].style = "";
    } else {
      imgs[1].style = "left:1920px";
      imgs[0].className = "on";
      imgs[1].className = "off";
    }

    // console.log(count); // count 값 증가 확인용
    count++;
    if (count === 2) {
      count = 0;
    }
  }

  setInterval(slide, 3000);
}

document.addEventListener("DOMContentLoaded", init);

 

 

 

1-2) 이미지 3개 슬라이드 및 preview / next 버튼 구현

 

test.html

<!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>Document</title>
    <link rel="stylesheet" href="./public/css/test.css" />
    <script src="./public/js/test.js" type="text/javascript"></script>
  </head>
  <body>
    <ul id="visual">
      <li>
        <img
          src="http://www.kiweb.or.kr/nBoard/upload/file/main/1666596927_56073_1.jpg"
          alt="img1"
        />
      </li>
      <li>
        <img
          src="http://www.kiweb.or.kr/nBoard/upload/file/main/1661838619_28666_1.png"
          alt="img2"
        />
      </li>
      <li>
        <img
          src="http://www.kiweb.or.kr/nBoard/upload/file/main/1661838462_26399_1.png"
          alt="img3"
        />
      </li>
    </ul>
    <ul id="visualBtn">
      <li><button>버튼1</button></li>
      <li><button>버튼2</button></li>
      <li><button>버튼3</button></li>
    </ul>
    <ul>
      <li class="prev"><button>이전</button></li>
      <li class="next"><button>다음</button></li>
    </ul>
  </body>
</html>

 

test.css

* {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
}

#visual {
  position: relative;
  width: 1900px;
  height: 500px;
  margin: 0 auto;
  overflow: hidden;
}

#visual > li {
  position: absolute;
  transition: all 1s;
  opacity: 0;
}

#visual > li.on {
  opacity: 1;
}

 

test.js

let intervalId = 0;
let count = 0;

function init() {
  // visual : Element를 반환받음(querySelector는 Element를 반환하기 때문)
  // const visual = document.querySelector("#visual");
  // imgs => Array를 반환받음(querySelectorAll은 Element들을 배열로 반환하기 때문)
  // imgs 데이터 타입(Array) : Element[] => [Element, Element]
  const imgs = document.querySelectorAll("#visual > li");
  const btns = document.querySelectorAll("#visualBtn > li"); // Element[]
  const prevBtn = document.querySelector(".prev");
  const nextBtn = document.querySelector(".next");

  function findIndex() {
    // ["경일", "게임", "아카데미"]의 배열에서 "게임" 요소만 뽑아내는 것과 똑같은 case
    for (let i = 0; i < imgs.length; i++) {
      console.log(imgs[i].getAttribute("class")); // on
      if (imgs[i].getAttribute("class") === "on") {
        return i;
      }
    }
  }

  // setInterval
  function slide() {
    // console.log("현재 인덱스 : ", count);
    // console.log("이전 인덱스 : ", count - 1);

    let prev = count === 0 ? imgs.length - 1 : count - 1;
    imgs[count].className = "on";
    imgs[prev].className = "";

    // console.log(count); // count 값 증가 확인용
    count++;
    if (count === 3) {
      count = 0;
    }

    // class = "a b c"
    // "a b c".split(" ") => ["a", "b", "c"] => classList
    // classList add, remove
    // ["a", "b", "c"]의 배열에서 "a" 요소 빼기 => splice 활용
    // ["a", "b", "c"].splice(0, 1) => ["b", "c"]
    // add와 push는 둘 다 새로운 요소 추가 가능
    // ["a", "b", "c"].add("d") = ["a", "b", "c"].push("d")
  }

  // 버튼 영역
  // Event를 설정하려면 기본적으로 Element를 가져와야 함
  // querySelectorAll을 써서 Element 가져오기
  // const btns = document.querySelectorAll("#visualBtn > li"); // Element[]

  // addEventListener의 2번째 매개변수인 콜백함수의 매개변수는 우리가 조작할 수 없기 때문에 이를 조작하기 위해 아래와 같이 고차함수를 사용함!!
  function btnHandler(index) {
    return function (e) {
      clearInterval(intervalId);
      const current = findIndex();

      imgs[current].className = "";
      imgs[index].className = "on";
      count = index;

      // setInterval : 데이터 타입(number)
      intervalId = setInterval(slide, 2000);
    };
  }

  function prevHandler(e) {
    clearInterval(intervalId);
    const current = findIndex();
    const index = current === 0 ? imgs.length - 1 : current - 1;

    imgs[current].className = "";
    imgs[index].className = "on";
    count = index;
    intervalId = setInterval(slide, 2000);
  }

  function nextHandler(e) {
    clearInterval(intervalId);
    const current = findIndex();
    const index = current === imgs.length - 1 ? 0 : current + 1;

    imgs[current].className = "";
    imgs[index].className = "on";
    count = index;
    intervalId = setInterval(slide, 2000);
  }

  for (let i = 0; i < btns.length; i++) {
    // btns[i]의 데이터 타입 = Element
    btns[i].addEventListener("click", btnHandler(i));
  }

  prevBtn.addEventListener("click", prevHandler);
  nextBtn.addEventListener("click", nextHandler);

  slide(); // 브라우저 load가 완료되지마자 첫번째 이미지에 class 이름(on)을 붙여주기 위한 코드
  intervalId = setInterval(slide, 3000);
}

document.addEventListener("DOMContentLoaded", init);

 

 

 

 

1-3) 실행 순서

let intervalId = 0;
let count = 1; // 3초마다 1씩 증가
  • 0 :
  • 1 :
  • 2 : class="on"

event

1. DOMContentLoaded... 1번
2. init() 함수호출

 

init() 내부안에서
1. Element 내용들 가져오기
2. 함수선언 하기

  • findIndex
  • slide
/*
	< slide >
	count 변수에 있는 인덱스를 class="on" 달아주고, 이전에 달린 값을 class="" 로 만들어줌.
	그리고 나서 count를 1씩 증가시킴.
*/

 

  • btnHandler
  • prevHandler
  • nextHandler

 

3. 이벤트 등록

  • Btns에 있는 모든 element에 대해 click 이벤트를 줌.
  • prev, next element에게 click 이벤트를 줌.

4. slide() 호출


5. setInterval() 실행 - 비동기 [이벤트 루프]

  • setInterval의 return 값을 intervalId 변수에 대입함.
  • Background라는 공간에 3초마다 slide를 실행시킴.

 

사용자 클릭을 해야지만 실행됩니다.

 

6. btnHandler ...

목적 : 내가 클릭한 버튼이 어떤 버튼인지 알기 위함!!

 

고차 함수 : 해당 함수 안에서 함수를 return해주는 함수

function fn(index) {
	return function (e) {
		console.log(index, e)
	}
}

Element.addEventListner("click", fn(1))

// +) fn()() >> 이 또한 고차함수와 같은 방식으로 결과값을 반환하는 방식이므로 기억해 둘 것!!(빈번히 사용한다고 함)
 
addEventListener의 장점하나의 함수로 여러 element에게 이벤트 등록이 가능함!! 
 
속성으로 부여할 시 아래와 같이 작성됨! 

 

btns[0].onClick = function () {
	//1
}

btns[1].onClick = function () {
	//2
}

btns[2].onClick = function () {
	//3
}

function handler() {
	// 이것이 몇 번 버튼인지 알아야 함!!
}

btns[0].onClick = handler
btns[1].onClick = handler
btns[2].onClick = handler

 

 

0, 1, 2를 전달하고 싶은데 어떻게 전달할 수 있을까?

function handler(i) {
	// 이것이 몇 번 버튼인지 알아야 함!!
	return undefined; // 함수의 경우, return이 따로 명시되어 있지 않거나 return에 따로 값이나 계산식이 들어가지 않고 비어있는 경우에는 default 값으로 "undefined"를 반환함!!
}

btns[0].onClick = handler(0) // undefined
btns[1].onClick = handler(1) // undefined
btns[2].onClick = handler(2) // undefined

 

그렇다면 위의 함수를 실행해서 함수를  return 한다면 어떻게 될까?

function handler(index) {
	// 이것이 몇 번 버튼인지 알아야 함!!
	return function () {
		console.log(index) // 오 이 버튼은 인덱스가 0번인 버튼이네!!
	}
}

btns[0].onClick = handler(0) // function
btns[1].onClick = handler(1) // function
btns[2].onClick = handler(2) // function

 

내가 클릭한 버튼의 인덱스로 count 값이 바뀌길 원한다면 아래와 같이 코드를 작성함!!

imgs[index].className = "on"

 

문제 발생
기존에 있던 class="on"이 지워지지 않는 상황!!
첫번째 방법의 경우, 반복문을 통해서 문제를 해결함.

const arr = ["경일", "게임", "아카데미"]
const search = "게임"

// arr 배열 안에서 "게임" 뺴고 다 출력하기
for (let i = 0; i < arr.length; i++) {
	if (arr[i] !== search) {
		console.log(arr[i])
	}
}

 

우리가 슬라이드할 element들은 모두 배열에 담고 있었고.
우리는 버튼의 인덱스 번호도 알고 있음!!

function btnHandler(index) {
	// 이것이 몇 번 버튼인지 알아야 함!!
	return function () {
		for (let i = 0; imgs.length; i++) {
			if (i === index) {
				imgs[i].clasName = "on"
			} else {
				imgs[i].className = "off"
			}
		}
	}
}

 

반복문을 통해서 처리하기는 했지만 이 경우 반복문을 조금 돌렸지만 만일 내가 40개를 돌려야 되는 상황이라면, 너무 비효율적이다!!
내가 class="on"이 달려있는 index 번호를 알 수 있다면 이보다 더 쉽게 해결 가능하다!!

 

 

element.getAttribute()

<input type="text" />
const input = document.querySelector("type") // result : text

 

이 메서드를 활용하여 "findIndex()"라는 함수를 제작함!

const arr = ["경일", "게임", "아카데미"]
const search = "게임"
for (let i = 0; i < arr.length; i++) {
	if (arr[i] === search) {
		console.log(i)
	}
}

function findIndex() {
	for (let i = 0; i < imgs.length; i++) {
		if (imgs[i].getAttribute("class") === "on") return i
	}
	return -1
}

 

btnHandler의 비효율적인 문제를 위의 "findIndex" 함수 제작으로 해결함!!

function btnHandler(index) {
	return function (e) {
		const current = findIndex()
		
        imgs[current].className = ""
		imgs[index].className = "on"
		count = index
	}
}

 

btnHandler의 count 변수 재할당

 

3초마다

let count = 2 // 3초마다 증가. 0~2

 

 

2.9초 지났을 때 이전 인덱스에 대해 ""을 처리할 시 잘못된 인덱스를 바라보는 문제를 handler 함수의 "count = index" 코드로 해결함!!

  • 0 : class="on"
  • 1 :
  • 2 :