Stripe Follow Along Nav
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
console.log('ENTER!!');
}
function handleLeave() {
console.log('LEAVE!!');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
trigger에 enter되면 console창에 출력되는 모습을 볼수 있습니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(function() {
console.log(this);
this.classList.add('trigger-enter-active');
}, 150);
}
function handleLeave() {
console.log('LEAVE!!');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
handleEnter function을 작성해줍니다.
현재 마우스가 enter 된 요소에 class를 추가해줍니다.
근데 현재 작성한 것은 error가 발생합니다.
error의 원인은 console.log로 this를 출력해 봤을 때, window로 찍히는 것을 볼 수 있습니다.
this를 마우스가 enter한 요소로 생각하고 작성한 코드이므로 에러가 나게 됩니다.
이를 수정하는 방법 중 하나는 arrow function으로 수정하는 방법이 있습니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => {
console.log(this);
this.classList.add('trigger-enter-active')
}, 150);
}
function handleLeave() {
console.log('LEAVE!!');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
arrow function으로 수정하고 확인해보면 error가 발생하지 않습니다.
console.log로 this를 출력해보니 마우스가 enter 된 요소가 출력되는 것을 확인할 수 있습니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
}
function handleLeave() {
console.log('LEAVE!!');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
handleEnter function을 arrow function으로 작성하고, console.log도 삭제한 모습
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
background가 나오게 하기 위해 open이라는 class를 추가/제거될 수 있게 작성해줍니다.
테스트를 해보면 mouse가 enter가 되면 왼쪽 상단에 흰색 박스가 보이게 됩니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
console.log(dropdown);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
1 → 2 → 3의 순서로 마우스를 올려보면 각각 다른 dropdown이 출력됩니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
console.log(dropdownCoords);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
getBoundingClientRect()을 통해 이 각각의 dropdown의 위치를 확인합니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
console.log(navCoords);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
동일한 방법으로 nav의 위치로 확인합니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width
};
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('height', `${coords.height}px`);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
이제 dropdown의 사이즈에 따라 background의 사이즈가 변화됩니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width,
top: dropdownCoords.top,
left: dropdownCoords.left
};
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('height', `${coords.height}px`);
background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
위치를 따라가기 위해 top과 left의 값을 넣어줍니다.
확인해보니까 약간씩 위치가 아래도 내려가 있는 것을 볼 수 있습니다.
그것은 background는 nav안에 있기 때문입니다.
그래서 nav의 top만큼을 빼주면 dropdown위치와 동일한 background를 가질 수 있습니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width,
top: dropdownCoords.top - navCoords.top,
left: dropdownCoords.left - navCoords.left
};
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('height', `${coords.height}px`);
background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
여기까지 하면 모양이 완성이 됩니다.
이번에는 mouse를 아주 빠르게 이동해봅니다.
trigger와 trigger 사이를 아주 빠르게 이동하게 되면 아직 background가 아직 생성되지 않아
보이는 내용을 감싸주지 못하는 현상을 볼 수 있게 됩니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => {
if (this.classList.contains('trigger-enter')) {
this.classList.add('trigger-enter-active')
}
}, 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width,
top: dropdownCoords.top - navCoords.top,
left: dropdownCoords.left - navCoords.left
};
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('height', `${coords.height}px`);
background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
trigger-enter라는 클래스가 있을 경우에만 active하게 되면 background가 생긴 후에 내용이 보이게 되어
위와 같은 현상을 어느 정도 잡아줄 수 있습니다.
const triggers = document.querySelectorAll('.cool > li');
const background = document.querySelector('.dropdownBackground');
const nav = document.querySelector('.top');
function handleEnter() {
this.classList.add('trigger-enter');
setTimeout(() => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150);
background.classList.add('open');
const dropdown = this.querySelector('.dropdown');
const dropdownCoords = dropdown.getBoundingClientRect();
const navCoords = nav.getBoundingClientRect();
const coords = {
height: dropdownCoords.height,
width: dropdownCoords.width,
top: dropdownCoords.top - navCoords.top,
left: dropdownCoords.left - navCoords.left
};
background.style.setProperty('width', `${coords.width}px`);
background.style.setProperty('height', `${coords.height}px`);
background.style.setProperty('transform', `translate(${coords.left}px, ${coords.top}px)`);
}
function handleLeave() {
this.classList.remove('trigger-enter', 'trigger-enter-active');
background.classList.remove('open');
}
triggers.forEach(trigger => trigger.addEventListener('mouseenter', handleEnter));
triggers.forEach(trigger => trigger.addEventListener('mouseleave', handleLeave));
handleEnter function에서 if로 이뤄진 문장을 &&로 연결해주는 형태로 변경해봅니다.
this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active') 로 작성하면 앞에가 true이면 뒤에가 실행될 것이고, false라면 뒷문장은 실행되지 않을 것입니다.
Element.getBoundingClientRect() : 뷰포트를 기준으로 한 위치를 반환하는 메서드
JavaScript30 강의를 보고 공부한 글입니다. (https://javascript30.com/)
'JavaScript30 Challenge' 카테고리의 다른 글
[JavaScript30] Day 28 (0) | 2020.08.28 |
---|---|
[JavaScript30] Day 27 (0) | 2020.08.27 |
[JavaScript30] Day 25 (0) | 2020.08.19 |
[JavaScript30] Day 24 (0) | 2020.08.18 |
[JavaScript30] Day 22 (0) | 2020.08.17 |