강의 출처 : The Complete JavaScript Course 2022 Jonas (Udemy)
scroll 관련 알아두면 좋은 정보들
- getBoundingClientRect() : relative to visible viewport
- window.screen : 모니터 size
- window.outer : page를 넘어선 browser 전체 크기 ( url + tab + page)
- window.inner : 스크롤바 포함한 viewport
- document.documentElement.clientHeight, document.documentElement.clientWidth : viewport의 높이와 너비. scroll bar 제외
- window.pageXOffset, window.pageYOffset : viewport의 가장 끝에서 top of the page의 길이
const btnScrollTo = document.querySelector('.btn--scroll-to');
const section1 = document.querySelector('#section--1');
btnScrollTo.addEventListener('click', e => {
const s1coords = section1.getBoundingClientRect();
console.log(s1coords);
console.log(e.target.getBoundingClientRect());
console.log('Current scroll (X/Y)', window.pageXOffset, window.pageYOffset);
console.log(
'height/width viewport',
document.documentElement.clientHeight,
document.documentElement.clientWidth
);
//scrolling
window.scrollTo(
s1coords.left + window.pageXOffset,
s1coords.top + window.pageYOffset
);
window.scrollTo({
left: s1coords.left + window.pageXOffset,
top: s1coords.top + window.pageYOffset,
behavior: 'smooth',
});
// modern way
section1.scrollIntoView({ behavior: 'smooth' });
});
const h1 = document.querySelector('h1');
const alertH1 = e => {
alert('addEventListener : Great! You are reading the heading :D');
h1.removeEventListener('mouseenter', alertH1); //event 가 한번만 실행되고 제거된다.
};
h1.addEventListener('mouseenter', alertH1);
setTimeout(() => h1.removeEventListener('mouseenter', alertH1), 3000);
//old way
h1.onmouseenter = e => {
alert('addEventListener : Great! You are reading the heading :D');
};
Event 가 발생하면 해당 element가 아닌 가장 상위에 있는 Document에 먼저 전달된다.
- capturing phase : document에서 target phase의 부모 element들을 거쳐 target까지 가는 구간.
- target phase : event가 발생한 target에서 callback 함수를 실행한다.
- bubbling phase : 다시 target에서부터 거쳐왔던 parent element로 올라가 처음 event가 전달되었던 document까지 가는 구간. 이 구간에서 만약 parent Element에 target과 동일한 event가 있다면 함께 발생. 부모 컨테이너는 어떤 자식요소에서 이벤트가 발생하든 모든 이벤트를 다 들을 수 있다.
rgb(255, 255, 255); 랜덤한 색깔 함수 만들기
const randomInt = (min, max) =>
Math.floor(Math.random() * (max - min + 1) + min);
const randomColor = () =>
`rgb(${randomInt(0, 255)},${randomInt(0, 255)},${randomInt(0, 255)})`;
document.querySelector('.nav__link').addEventListener('click', function (e) {
this.style.backgroundColor = randomColor();
console.log('LINK', e.target, e.currentTarget);
// Stop propogation 사용하지 않는게 좋음
//e.stopPropagation(); //bubbling 일어나지 않음
});
document.querySelector('.nav__links').addEventListener('click', function (e) {
this.style.backgroundColor = randomColor();
console.log('CONTAINER', e.target, e.currentTarget);
});
document.querySelector('.nav').addEventListener(
'click',
function (e) {
this.style.backgroundColor = randomColor();
console.log('NAV', e.target, e.currentTarget);
},
true // event handling이 bubbling을 멈추고 capturing event 시작한다.
// capturing phase : nav -> link -> container 순서로 event 발생 (nav는 capturing, 나머지 둘은 bubbling phase 이므로)
);
'.navlink' event 실행시 parent Element인 .navlinks와 .nav 전부다 실행됌.
=> Event propagation
this === e.currentTarget / e.target은 실제로 event 가 일어난 element를 가리킨다. ex) bubbling으로 인해 .nav의 event가 발생하였어도 e.target은 .nav__link를 가리킨다!
Implementing Page Navigation by using Event Delegation
여러 element들 동일한 event 실행시켜주어야 할 때 => 공통의 parent Element에 이벤트를 실행시켜(위임하여) 일일히 함수 만드는 것을 생략 가능. 즉 반복되어지는 이벤트를 처리할 때, 즉 부모 안의 자식들에게 공통적으로 무언가 처리를 해야될 때 일일히 이벤트리스너를 자식노드에 추가하는 것보다 부모에 등록하는 것이 좋다.
document.querySelector('.nav__links').addEventListener('click', e => {
e.preventDefault();
// Matching strategy
if (e.target.classList.contains('nav__link')) {
const id = e.target.getAttribute('href');
if (id === '#') {
return;
}
document.querySelector(id).scrollIntoView({ behavior: 'smooth' });
}
});
참고) link href ='#id' => 해당하는 아이디로 자동 scroll을 해주는 기본 기능을 가지고 있다.
DOM Traversing : walking through the DOM, we can select an element based on another element
const h1 = document.querySelector('h1');
console.log(h1.querySelectorAll('.highlight'));
console.log(h1.childNodes);
//NodeList(9) [text, comment, text, span.highlight, text, br, text, span.highlight, text]
// Nodes can be anything!
console.log(h1.children); //HTMLCollection(3) [span.highlight, br, span.highlight]
h1.firstElementChild.style.color = 'white';
h1.lastElementChild.style.color = 'orangered';
console.log(h1.parentNode); //<div class="header__title">...</div>
console.log(h1.parentElement); //<div class="header__title">...</div>
//closest(selectors) 지정한 선택자와 가장 가깝게 조건에 만족한 부모요소가 반환된다.
h1.closest('.header').style.background = 'var(--gradient-secondary)';
// header에 가장 가까운 h1의 부모요소에 효과 적용된다.
h1.closest('h1').style.background = 'var(--gradient-primary)';
// h1 그 자체
closest는 querySelector의 정 반대의 개념이라 생각하면 된다. 둘다 selector를 받지만 querySelector은 child element를 선택하고 closest는 parent element를 선택하므로!
console.log(h1.previousElementSibling); //바로 전 sibling elememnt
console.log(h1.nextElementSibling); // 바로 다음 sibling element
console.log(h1.previousSibling); //바로 전 sibling node
console.log(h1.nextSibling); //바로 다음 sibling node
console.log(h1.parentElement.children); // h1을 포함한 모든 siblings 출력.
[...h1.parentElement.children].forEach(function (el) {
if (el !== h1) el.style.transform = 'scale(0.5)';
});
tabsContainer.addEventListener('click', e => {
const clicked = e.target.closest('.operations__tab'); //span 부분을 눌러도 button 선택된다!
//Guard clause -modern way (빠른 return)
if (!clicked) return;
// Remove active classes
tabs.forEach(t => t.classList.remove('operations__tab--active'));
// 흔한 logic! 하나의 element에만 classList를 추가하고 싶다면 추가 전에 모든 요소의 classList clear 해주기!
tabsContent.forEach(c => c.classList.remove('operations__content--active'));
//Active tab
clicked.classList.add('operations__tab--active');
//Active content area
document
.querySelector(`.operations__content--${clicked.dataset.tab}`)
.classList.add('operations__content--active');
});