→ JavaScript를 통해 웹 서비스를 동작
생략
→ id
는 하나이기 때문에 하나의 요소를 선택하기에 좋음
→ 잘못된 id
선택시 : undefined
const myNumberTag = document.getElementById("myNumber");
const decreaseBtn = document.getElementById("decrease");
const increaseBtn = document.getElementById("increase");
→ 여러 요소를 선택하기 위해서는 class
선택
→ 잘못된 class
선택시 : undefined
→ ❗️유사 배열 : 여러개의 값을 담고 있는 배열과 형태는 유사하지만, 배열 메소드는 사용할 수 없음
index
, length
사용const colorBtns = document.getElementsByClassName("color-btn");
: 배열과 모양은 같지만, 완벽히 배열은 아닌 형태
length
프로퍼티 존재→ 문서 내에 있는 모든 해당 태그를 선택
const btns = document.getElementsByTagName('button');
// const myNumberTag = document.getElementById("myNumber");
const myNumberTag = document.querySelector('#myNumber');
// const colorBtns = document.getElementsByClassName("color-btn");
// const colorBtns = document.querySelector(".color-btn");
const colorBtns = document.querySelectorAll(".color-btn");
→ querySelector
는 class
의 경우 첫번째 인덱스만 선택됨 ⚠️ 주의 → querySelectorAll
// 이벤트(Event)와 버튼 클릭
const btn = document.querySelector("#myBtn");
// 이벤트 핸들링 (Event Handling)
btn.onclick = function () {
// 이벤트 핸들러 (Event Handler)
console.log("Hello Codeit!");
};
const myBtn = document.querySelector('#grade');
myBtn.onclick = function() {
alert('정답입니다!👏🏻');
};
메소드 | 의미 | 결과 |
---|---|---|
document.getElementById('id') | HTML id속성으로 태그 선택하기 | id에 해당하는 태그 하나 |
document.getElementsByClassName('class') | HTML class속성으로 태그 선택하기 | class에 해당하는 태그 모음(HTMLCollection) |
document.getElementsByTagName('tag') | HTML 태그 이름으로 태그 선택하기 | tag에 해당하는 태그 모음(HTMLCollection) |
document.querySelector('css') | css 선택자로 태그 선택하기 | css 선택자에 해당하는 태그 중 가장 첫번째 태그 하나 |
document.querySelectorAll('css') | css 선택자로 태그 선택하기 | css 선택자에 해당하는 태그 모음(NodeList) |
→ window
: 전역객체
innerWidth
innerHeight
→ 문서 객체 모델 (Document Object Model)
console.log
: dom에 해당하는 html 태그 출력
title.style.color = ‘red’;
→ js를 통해 스타일 적용
log
메소드는 파라미터로 전달받은 값을 위주로 출력하는 반면, dir
메소드는 객체의 속성을 좀 더 자세하게 출력log
메소드는 여러 값을 쉼표로 구분해서 전달하면 전달받은 모든 값을 출력하는 반면, dir
메소드는 여러 값을 전달하더라도 첫 번째 값만 출력→ 콘솔에서 값 자체를 확인하고 싶다면 log
메소드를, 객체의 속성들을 살펴보고 싶다면 dir
메소드를 활용
→ 부모 노드, 자식 노드, 형제 노드 / 요소 노드, 텍스트 노드, 잎 노드, 코멘트 노드, 문서 노드
: 텍스트 노드는 요소 노드의 자식 노드! 자식 노드를 가질 수 없다.
const myTag = document.querySelector('#content');
console.log(myTag);
//console.log(myTag.parentElement.nextElementSibling);
// 형제 요소 노드
console.log(myTag.previousElementSibling);
console.log(myTag.nextElementSibling);
// 부모 요소 노드
console.log(myTag.parentElement);
// 자식 요소 노드
console.log(myTag.childern[1]);
console.log(myTag.firstElementChild);
console.log(myTag.lastElementChild);
요소 노드에 대한 이동 프로퍼티
프로퍼티 | 유형 | 결과 |
---|---|---|
element.children | 자식 요소 노드 | element의 자식 요소 모음(HTMLCollection) |
element.firstElementChild | 자식 요소 노드 | element의 첫 번째 자식 요소 하나 |
element.lastElementChild | 자식 요소 노드 | element의 마지막 자식 요소 하나 |
element.parentElement | 부모 요소 노드 | element의 부모 요소 하나 |
element.previousElementSibling | 형제 요소 노드 | element의 이전(previous) 혹은 좌측(left)에 있는 요소 하나 |
element.nextElementSibling | 형제 요소 노드 | element의 다음(next) 혹은 우측(right)에 있는 요소 하나 |
모든 노드에 대한 이동 프로퍼티
프로퍼티 | 유형 | 결과 |
---|---|---|
node.childNodes | 자식 노드 | node의 자식 노드 모음(NodeList) |
node.firstChild | 자식 노드 | node의 첫 번째 자식 노드 하나 |
node.lastChild | 자식 노드 | node의 마지막 자식 노드 하나 |
node.parentNode | 부모 노드 | node의 부모 요소 하나 |
node.previousSibling | 형제 노드 | node의 이전(previous) 혹은 좌측(left)에 있는 노드 하나 |
node.nextSibling | 형제 노드 | node의 다음(next) 혹은 우측(right)에 있는 노드 하나 |
// 요소 노드 주요 프로퍼티
const myTag = document.querySelector('#list-1');
// innerHTML
console.log(myTag.innerHTML);
myTag.innerHTML = '<li>Exotic</li>';
// outerHTML -> 요소 자체가 교체 (원래 요소 삭제)
console.log(myTag.outerHTML);
myTag.outerHTML = '<ul id="new-list"><li>Exotic</li></ul>';
// textContent -> innerHTML과 마찬가지로 수정, only 텍스트만 다룸(특수문자도 텍스트로)
console.log(myTag.textContent);
myTag.textContent = 'new text!';
// 요소 노드 추가하기
const tomorrow = document.querySelector('#tomorrow');
// 1. 요소 노드 만들기: document.createElement('태그이름');
const first = document.createElement('li');
// 2. 요소 노드 꾸미기: textContent, innerHTML, ...
first.textContent('처음');
// 3. 요소 노드 추가하기: NODE.prepend, append, after, before
tomorrow.prepend(first); // 처음 (한번에 순서대로 추가)
tomorrow.prepend(append); // 마지막
tomorrow.prepend(after); // 이전 (한번에 순서대로 추가)
tomorrow.prepend(before); // 다음
// 노드 삭제와 이동
const today = document.querySelector('#today');
const tomorrow = document.querySelector('#tomorrow');
// 노드 삭제하기: Node.remove();
tomorrow.remove();
today.children[2].remove();
// 노드 이동하기: prepend, append, after, before
today.append(tomorrow.children[1]);
tomorrow.children[1].after(today.children[1]);
tomorrow.lastElementChild.after(today.children[1]);
const toDoList = document.querySelector('#to-do-list');
function addNewTodo(text) {
const li = document.createElement("li");
li.textContent = text;
toDoList.append(li);
}
// 테스트 코드
addNewTodo('자바스크립트 공부하기');
addNewTodo('고양이 화장실 청소하기');
addNewTodo('고양이 장난감 쇼핑하기');
// HTML 속성
const tomorrow = document.querySelector('#tomorrow');
const item = tomorrow.firstElementChild;
const link = item.firstElementChild;
// id 속성
console.log(tomorrow.id);
// class 속성
console.log(item.className);
// href 속성
console.log(link.href);
console.log(tomorrow.href);
// elem.getAttribute('속성'): 속성에 접근하기
console.log(tomorrow.getAttribute('href'));
console.log(item.getAttribute('class'));
// elem.setAttribute('속성', '값'): 속성에 추가(수정)하기
tomorrow.setAttribute('class', 'list');
link.setAttribute('href', 'https://www.codeit.kr');
// elem.removeAttribute('속성'): 속성 제거하기
tomorrow.removeAttribute('href');
tomorrow.removeAttribute('class');
// 스타일 다루기
const today = document.querySelector('#today');
const tomorrow = document.querySelector('#tomorrow');
// style 프로퍼티
today.children[0].style.textDecoration = 'line-through';
today.children[0].style.backgroundColor = '#DDDDDD';
// elem.className
today.children[1].className = 'done';
// elem.classList: add, remove, toggle
const item = tomorrow.children[1];
item.classList.add('done', 'other');
item.classList.remove('done');
item.classList.remove('toggle'); // 없으면 추가, 있으면 제거, 여러개 불가(클래스 1개만을 다룸)
가장 간단하게는 아래와 같이 querySelector
로 태그를 선택할 때 css 선택자를 활용해서 태그를 선택하는 데에 활용 가능
const fields = document.querySelectorAll('[field]');
console.log(fields);
비표준 속성은 객체 형태의 데이터가 있을 때, 각 프로퍼티 값들이 들어갈 태그를 구분하는데 활용 가능
const fields = document.querySelectorAll('[field]');
const task = {
title: '코드 에디터 개발',
manager: 'CastleRing, Raccoon Lee',
status: '',
};
for (let tag of fields) {
const field = tag.getAttribute('field');
tag.textContent = task[field];
}
getAttribute
메소드를 활용해서 속성값을 가져오고, setAttribute
메소드를 활용해서 속성값을 설정해주는 원리로 이벤트를 통해 실시간으로 스타일을 변경하거나 데이터를 변경하는데 활용 가능
때로는 class
를 다루는 것보다 setAttribute
로 비표준 속성을 변경하는게 스타일을 다루기에 오히려 편리한 경우도 존재
const fields = document.querySelectorAll('[field]');
const task = {
title: '코드 에디터 개발',
manager: 'CastleRing, Raccoon Lee',
status: '',
};
for (let tag of fields) {
const field = tag.getAttribute('field');
tag.textContent = task[field];
}
const btns = document.querySelectorAll('.btn');
for (let btn of btns) {
const status = btn.getAttribute('status');
btn.onclick = function () {
fields[2].textContent = status;
fields[2].setAttribute('status', status);
};
}
→ 좀 더 안전하게, dataset 프로퍼티
: data-
로 시작하는 속성은 모두 dataset이라는 프로퍼티에 저장
예. data-status
라는 속성이 있다면, element.dataset.status
라는 프로퍼티에 접근해서 그 값을 가져옴
const fields = document.querySelectorAll('[data-field]');
const task = {
title: '코드 에디터 개발',
manager: 'CastleRing, Raccoon Lee',
status: '',
};
for (let tag of fields) {
const field = tag.dataset.field;
tag.textContent = task[field];
}
const btns = document.querySelectorAll('.btn');
for (let btn of btns) {
const status = btn.dataset.status;
btn.onclick = function () {
fields[2].textContent = status;
fields[2].dataset.status = status;
};
}
// 이벤트 핸들러 등록하기
const btn = document.querySelector("#myBtn");
function event1() {
console.log('Hi Codeit!');
}
function event2() {
console.log('Hi again!');
}
// onclick은 하나의 것만 가능 (덮어씌워짐)
// elem.addEventListener(event, handler)
btn.addEventListener('click', event1);
btn.addEventListener('click', event2);
// elem.removeEventListener(event, handler)
btn.removeEventListener('click', event2);
마우스 이벤트
이벤트 타입 | 설명 |
---|---|
mousedown | 마우스 버튼을 누르는 순간 |
mouseup | 마우스 버튼을 눌렀다 떼는 순간 |
click | 왼쪽 버튼을 클릭한 순간 |
dblclick | 왼쪽 버튼을 빠르게 두 번 클릭한 순간 |
contextmenu | 오른쪽 버튼을 클릭한 순간 |
mousemove | 마우스를 움직이는 순간 |
mouseover | 마우스 포인터가 요소 위로 올라온 순간 |
mouseout | 마우스 포인터가 요소에서 벗어나는 순간 |
mouseenter | 마우스 포인터가 요소 위로 올라온 순간 (버블링이 일어나지 않음) |
mouseleave | 마우스 포인터가 요소에서 벗어나는 순간 (버블링이 일어나지 않음) |
키보드 이벤트
이벤트 타입 | 설명 |
---|---|
keydown | 키보드의 버튼을 누르는 순간 |
keypress | 키보드의 버튼을 누르는 순간 ('a', '5' 등 출력이 가능한 키에서만 동작하며, Shift, Esc 등의 키에는 반응하지 않음) |
keyup | 키보드의 버튼을 눌렀다 떼는 순간 |
포커스 이벤트
이벤트 타입 | 설명 |
---|---|
focusin | 요소에 포커스가 되는 순간 |
focusout | 요소로부터 포커스가 빠져나가는 순간 |
focus | 요소에 포커스가 되는 순간 (버블링이 일어나지 않음) |
blur | 요소로부터 포커스가 빠져나가는 순간 (버블링이 일어나지 않음) |
입력 이벤트
이벤트 타입 | 설명 |
---|---|
change | 입력된 값이 바뀌는 순간 |
input | 값이 입력되는 순간 |
select | 입력 양식의 하나가 선택되는 순간 |
submit | 폼을 전송하는 순간 |
스크롤 이벤트
이벤트 타입 | 설명 |
---|---|
scroll | 스크롤 바가 움직일 때 |
윈도우 창 이벤트
이벤트 타입 | 설명 |
---|---|
resize | 윈도우 사이즈를 움직일 때 발생 |
// 이벤트 객체 (Event Object)
const myInput = document.querySelector("#myInput");
const myBtn = document.querySelector("#myBtn");
function printEvent(event) {
console.log(event);
event.target.style.color = 'red';
}
myInput.addEventListener("keydown", printEvent);
myBtn.addEventListener("click", printEvent);
아래의 프로퍼티들은 이벤트 타입과 상관없이 모든 이벤트 객체들이 공통적으로 가지고 있는 프로퍼티
프로퍼티 | 설명 |
---|---|
type | 이벤트 이름 ('click', 'mouseup', 'keydown' 등) |
target | 이벤트가 발생한 요소 |
currentTarget | 이벤트 핸들러가 등록된 요소 |
timeStamp | 이벤트 발생 시각(페이지가 로드된 이후부터 경과한 밀리초) |
bubbles | 버블링 단계인지를 판단하는 값 |
마우스와 관련된 이벤트의 경우에는 아래와 같은 이벤트 객체의 프로퍼티들
프로퍼티 | 설명 |
---|---|
button | 누른 마우스의 버튼 (0: 왼쪽, 1: 가운데(휠), 2: 오른쪽) |
clientX, clientY | 마우스 커서의 브라우저 표시 영역에서의 위치 |
pageX, pageY | 마우스 커서의 문서 영역에서의 위치 |
offsetX, offsetY | 마우스 커서의 이벤트 발생한 요소에서의 위치 |
screenX, screenY | 마우스 커서의 모니터 화면 영역에서의 위치 |
altKey | 이벤트가 발생할 때 alt키를 눌렀는지 |
ctrlKey | 이벤트가 발생할 때 ctrl키를 눌렀는지 |
shiftKey | 이벤트가 발생할 때 shift키를 눌렀는지 |
metaKey | 이벤트가 발생할 때 meta키를 눌렀는지 (window는 window키, mac은 cmd키) |
키보드와 관련된 이벤트의 경우에는 아래와 같은 이벤트 객체의 프로퍼티들
프로퍼티 | 설명 |
---|---|
key | 누른 키가 가지고 있는 값 |
code | 누른 키의 물리적인 위치 |
altKey | 이벤트가 발생할 때 alt키를 눌렀는지 |
ctrlKey | 이벤트가 발생할 때 ctrl키를 눌렀는지 |
shiftKey | 이벤트가 발생할 때 shift키를 눌렀는지 |
metaKey | 이벤트가 발생할 때 meta키를 눌렀는지 (window는 window키, mac은 cmd키) |
const toDoList = document.querySelector('#to-do-list');
const items = toDoList.children;
// 1. updateToDo 함수를 완성해 주세요
function updateToDo(event) {
event.target.classList.toggle('done');
}
// 2. 반복문을 활용해서 각 li태그에 이벤트 핸들러를 등록해 주세요
for (let item of items) {
item.addEventListener('click', updateToDo);
}
// 테스트 코드
items[2].removeEventListener('click', updateToDo);
→ 부모 요소(최상단까지)의 핸들러도 동작
// 이벤트 버블링 (Event Bubbling)
const content = document.querySelector("#content");
const title = document.querySelector("#title");
const list = document.querySelector("#list");
const items = document.querySelectorAll(".item");
content.addEventListener("click", function () {
console.log("content Event");
});
title.addEventListener("click", function () {
console.log("title Event");
});
list.addEventListener("click", function () {
console.log("list Event");
});
for (let item of items) {
item.addEventListener("click", function () {
console.log("item Event");
});
}
→ currentTarget
은 이벤트가 실제로 실행되는 것을 담고 있음
e.stopPropagation();
: 버블링을 막는 코드, 지양
캡처링 단계: 이벤트가 하위 요소로 전파되는 단계
타깃 단계: 이벤트가 실제 타깃 요소에 전달되는 단계
→ 타깃 단계는 이벤트 객체의 target
프로퍼티가 되는 요소에 등록되어있던 이벤트 핸들러가 동작하는 단계
→ 쉽게 생각해서 가장 처음 이벤트 핸들러가 동작하게 되는 순간
버블링 단계: 이벤트가 상위 요소로 전파되는 단계
📌 캡쳐링은 이벤트가 발생하면 가장 먼저, 그리고 버블링의 반대 방향으로 진행되는 이벤트 전파 방식
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Codeit Acid Rain</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>DIV
<ul>UL
<li>LI</li>
</ul>
</div>
<script>
for (let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`캡쳐링 단계: ${elem.tagName}`), true);
elem.addEventListener("click", e => alert(`버블링 단계: ${elem.tagName}`));
}
</script>
</body>
</html>
→ 자식 요소의 이벤트를 부모 요소에도 위임 (반대도 포함) : 버블링 활용
// 이벤트 위임 (Event Delegation)
const list = document.querySelector("#list");
list.addEventListener("click", function (e) {
// if (e.target.tagName === 'LI')
if (e.target.classList.contains("item")) {
e.target.classList.toggle("done");
}
});
const li = document.createElement("li");
li.classList.add("item");
li.textContent = "일기 쓰기";
list.append(li);
const toDoList = document.querySelector('#to-do-list');
// 1. updateToDo 함수를 완성해 주세요
function updateToDo(event) {
if (event.target.classList.contains('item')) {
event.target.classList.toggle('done');
}
}
// 2. 각 li 태그가 아닌 하나의 태그에만 이벤트 핸들러를 등록해 주세요
toDoList.addEventListener('click', updateToDo);
// 테스트 코드
const newToDo = document.createElement('li');
newToDo.textContent = '가계부 정리하기';
newToDo.classList.add('item');
toDoList.append(newToDo);
toDoList.children[2].addEventListener('click', function(e) {e.stopPropagation()});
// 브라우저의 기본 동작
const link = document.querySelector("#link");
const checkbox = document.querySelector("#checkbox");
const input = document.querySelector("#input");
const text = document.querySelector("#text");
// event.preventDefault
link.addEventListener("click", function (e) {
e.preventDefault();
alert("지금은 이동할 수 없습니다.");
});
input.addEventListener("keydown", function (e) {
if (!checkbox.checked) {
e.preventDefault();
alert("체크박스를 먼저 클릭해 주세요.");
}
});
document.addEventListener("contextmenu", function (e) {
e.preventDefault();
alert("마우스 오른쪽 클릭은 사용할 수 없습니다.");
});
→ 우클릭의 경우 운영체제별로 실행 순서가 변경될 수 있음
[마우스 버튼 이벤트]
MouseEvent.button
MouseEvent.type
const flagBlue = document.querySelector('.flag-blue');
const flagWhite = document.querySelector('.flag-white');
function reset() {
document.querySelector('.up').classList.remove('up');
}
// 1. flagUp 함수를 완성해 주세요
function flagUp(e) {
if (e.button === 0) {
flagBlue.classList.add('up');
} else if (e.button === 2) {
flagWhite.classList.add('up');
}
// 500 밀리초 뒤에 reset함수를 실행
setTimeout(reset, 500);
}
// 2. 마우스 오른쪽 버튼 클릭시 나타나는 메뉴창을 막아주세요
document.addEventListener('contextmenu', function (event) {
event.preventDefault();
});
// 테스트 코드
document.addEventListener('mousedown', flagUp);
[마우스 이동 이벤트]
MouseEvent.type
MouseEvent.clientX, clientY
: 화면에 표시되는 창 기준 마우스 포인터 위치MouseEvent.pageX, pageY
: 웹 문서 전체 기준 마우스 포인터 위치MouseEvent.offsetX, offsetY
: 이벤트가 발생한 요소 기준 마우스 포인터 위치const box1 = document.querySelector('#box1');
function onMouseMove(e) {
console.log(`client: (${e.clientX}, ${e.clientY})`);
console.log(`page: (${e.pageX}, ${e.pageY})`);
console.log(`offset: (${e.offsetX}, ${e.offsetY})`);
console.log('------------------------------------');
}
box1.addEventListener('mousemove', onMouseMove);
MouseEvent.target
: 이벤트가 발생한 요소MouseEvent.relatedTarget
: 이벤트가 발생하기 직전(또는 직후)에 마우스가 위치해 있던 요소const box2 = document.querySelector('#box2');
function printEventData(e) {
console.log('event:', e.type);
console.log('target:', e.target);
console.log('relatedTarget:', e.relatedTarget);
console.log('------------------------------------');
if (e.target.classList.contains('cell')) {
e.target.classList.toggle('on');
}
}
box2.addEventListener('mouseover', printEventData);
box2.addEventListener('mouseout', printEventData);
client
프로퍼티는 말 그대로 클라이언트 영역 내에서 마우스의 좌표 정보client
값은 그 순간 보여지는 화면을 기준으로 계산하기 때문에 스크롤 위치와는 무관하게 항상 보여지는 화면의 좌측 상단의 모서리 위치를 (0, 0)으로 계산offset
프로퍼티는 이벤트가 발생한 target이 기준target
내에서 마우스의 X좌표 위치target
내에서 마우스의 Y좌표 위치offset
값도 이벤트가 발생한 대상을 기준으로 계산하기 때문에 스크롤 위치와는 무관하게 항상 대상의 좌측 상단의 모서리 위치를 (0, 0)으로 계산버블링이 일어나지 않는다.
→ mouseenter
와 mouseleave
는 버블링이 일어나지 않음
자식 요소의 영역을 계산하지 않는다.
→ mouseenter
와 mouseleave
는 자식 요소의 영역을 계산하지 않음
→ 이벤트가 자식 요소에 영향끼치는지가 둘의 가장 큰 차이
mouseover/mouseout
mouseenter/mouseleave
를 활용function showTitle(e) {
if (e.target.dataset.title) {
const span = document.createElement(<'span');
span.classList.add('title');
span.textContent = e.target.dataset.title;
e.target.append(span);
}
}
function removeTitle(e) {
if (e.target.dataset.title) {
e.target.lastElementChild.remove();
}
}
const map = document.querySelector('.map');
map.addEventListener('mouseover', showTitle);
map.addEventListener('mouseout', removeTitle);
[키보드 이벤트]
KeyboardEvent.type
KeyboardEvent.key
: 이벤트가 발생한 버튼의 값KeyboardEvent.code
: 이벤트가 발생한 버튼의 키보드에서 물리적인 위치const chatBox = document.querySelector('#chat-box');
const input = document.querySelector('#input');
const send = document.querySelector('#send');
function sendMyText() {
const newMessage = input.value;
if (newMessage) {
const div = document.createElement('div');
div.classList.add('bubble', 'my-bubble');
div.innerText = newMessage;
chatBox.append(div);
} else {
alert('메시지를 입력하세요...');
}
input.value = '';
}
send.addEventListener('click', sendMyText);
function sendMyTextByEnter (e) {
if (e.key === 'Enter' && !e.shiftKey) {
sendMyText();
e.preventDefault();
}
}
input.addEventListener('keypress', sendMyTextByEnter);
[input 태그 다루기]
포커스 이벤트
const input = document.querySelector('#input');
function checker() {
const words = document.querySelectorAll('.word');
if (words.length === 0) {
alert('Success!👏🏻');
if(confirm('retry?')) {
window.location.reload();
}
}
}
function removeWord() {
const word = document.querySelector(`[data-word="${input.value}"]`);
if (word) {
word.remove();
checker();
}
input.value = '';
}
input.addEventListener('change', removeWord);
// Scroll 이벤트
function printEvent(e) {
const STANDARD = 30;
const nav = document.querySelector("#nav");
const toTop = document.querySelector("#to-top");
// 스크롤이 30px을 넘었을 때
if (window.scrollY > STANDARD) {
nav.classList.add("shadow");
toTop.classList.add("show");
} else {
nav.classList.remove("shadow");
toTop.classList.remove("show");
}
}
window.addEventListener("scroll", printEvent);