해당 포스트는 제로베이스 오프라인스쿨 진행 과정 중 JS PairProgramming과제를 진행하며 회고를 정리하는 글이다.
08 Toast TIL
1. 아직 존재하지 않는 요소를 불러와야 할 때
click event로 생기는 toast 속 요소의 X버튼을 클릭하면 toast를 삭제하고자 한다.
document.querySelector('.toast-close').addEventListner('click', e => {
e.target.parentNode.remove();
});
위 방법에서는 전역에서 .toast-close 요소를 취득하려고 한다. 하지만, .toast-close는 .button-container가 click되면 생성되는 요소이므로 이 시점에서는 .toast-close가 없다.
따라서, .toast-close가 확실히 생성되는 시점에서 이벤트 리스너를 등록하여 실행되도록 해야한다.
$buttonContainer.addEventListener('click', e => {
// ...
document.querySelector('.toast-close').addEventListner('click', e => {
e.target.parentNode.remove();
});
// ...
});
2. 동적으로 추가한 요소의 자식노드로 추가
.toast-container는 동적으로 생기는 요소이다. .toast-container를 동적으로 생성하기 위해 어떠한 경우에만 비교를 하여 생성할지 생각 해 본 결과 toast.position을 비교하여 null일시에만 생성을 해야 하기 때문에 하기와 같이 작성을 하였다.
if (document.querySelector(`.${toast.position}`) === null) {
const toastContainer = `<div class="toast-container ${toast.position}"></div>`;
$configContainer.insertAdjacentHTML('beforeend', toastContainer);
}
.toast도 마찬가지로 동적생성이 되어야 하며 .toast-container 안에 존재해야 한다.
const $toast = document.createElement('div');
$toast.classList.add(`toast`);
$toast.classList.add(`${toast.type}`);
$toast.dataset.position = `${toast.position}`;
$toast.innerHTML = `
<i class="fa-solid ${icon[`${toast.type}`]}"></i>
<span>${toast.message}</span>
${toast.closeOnClick ? '<button class="toast-close">×</button>' : ''}
`;
const $toastContainer = document.querySelector(`.${toast.position}`);
$toastContainer.appendChild($toast);
3. animation trigger와 animationend event
.toast가 삭제될 때, animation을 적용시켜주고자 한다.
.toast가 삭제되기 전, animation이 적용되어 있는 클래스인 dismiss를 추가해, animation을 실행시키려 했다.
e.target.parentNode.classList.add('dismiss');
e.target.parentNode.remove();
위 방법에서는 animation이 나오지 않는다. dismiss animation은 transition이 0.3초 있는데, 이 transition이 끝나기 전 .toast가 삭제되는 것이다.
e.target.parentNode.classList.add('dismiss');
setTimeout(() => {
e.target.parentNode.remove();
}, 300);
따라서, dismiss 클래스를 추가하고, setTimeout 함수를 이용해 0.3초 후에 삭제되도록 했다.
위 방식은 setTimeout 함수 안에 다시 한 번 setTimeout을 써야 하는 단점이 있다.
if (toast.autoClose) {
timerId = setTimeout(() => {
$toast.classList.add('dismiss');
setTimeout(() => {
$toast.parentNode.children.length === 1 ? $toast.parentNode.remove() : $toast.remove();
}, 300);
}, toast.autoCloseDelay);
}
따라서, animationend event를 이용해 CSS 애니메이션이 완료되면 시작하도록 했다.
if (toast.autoClose) {
timerId = setTimeout(() => {
$toast.classList.add('dismiss');
$toast.addEventListiner('animationend', () => {
$toast.parentNode.children.length === 1 ? $toast.parentNode.remove() : $toast.remove();
});
}, toast.autoCloseDelay);
}
animationend event
- animatinonend event는 CSS 애니메이션이 완료될 때 발생하는 event다. DOM에서 해당 요소가 제거되거나, 애니메이션이 제거될 때는 발생하지 않는다.
- https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event
4. timer 종료
autoClose와 closeOnclick이 모두 true이면, 타이머를 통해서도 Remove 될 수 있고, X버튼을 클릭해서도 Remove 할 수 있다.
if (toast.autoClose) {
setTimeout(() => {
$toast.parentNode.children.length === 1 ? $toast.parentNode.remove() : $toast.remove();
}, toast.autoCloseDelay);
}
if (toast.closeOnClick) {
$toast.querySelector('.toast-close').addEventListener('click', e => {
e.target.parentNode.remove();
if ($toastContainer.children.length === 0) $toastContainer.remove();
});
}
.toast-close버튼을 클릭했을시 타이머를 clearTimeout메서드를 이용하여 반환하지 않으면 Remove하는 로직이 2번 실행되어 Error가 발생한다.
X버튼을 눌러도 timer는 동작하고 있기 때문에, .toast가 Remove되고 timer가 만료되면서 다시 한 번 Remove 로직을 실행한다. 이때, .toast는 이미 없어진 요소이기 때문에 $toast.parentNode.children을 통해서 접근하는 것은 불가능하다.
따라서, timerId를 사용해 X버튼 클릭시에 타이머를 반환하는 로직을 추가하였다.
let timerId = 0;
if (toast.autoClose) {
timerId = setTimeout(() => {
$toast.parentNode.children.length === 1 ? $toast.parentNode.remove() : $toast.remove();
}, toast.autoCloseDelay);
}
if (toast.closeOnClick) {
$toast.querySelector('.toast-close').addEventListener('click', e => {
if (timerId > 0) clearTimeout(timerId);
e.target.parentNode.remove();
if ($toastContainer.children.length === 0) $toastContainer.remove();
});
}
5. autoClose의 값 불러오기
.autoClose에서 change event가 발생 할 때, disabled 설정을 바꾸어 주려고 한다.
toast 객체의 autoClose 값이 true라면, disabled를 false로 하고, toast 객체의 autoClose 값이 false라면, disabled를 true로 만들었다.
if (toast.autoClose) {
$autoCloseDelay.disabled = false;
toast.autoClose = !toast.autoClose;
} else {
$autoCloseDelay.disabled = true;
toast.autoClose = !toast.autoClose;
}
위 방법에서는 change event가 발생해도 toast.autoClose의 값을 update해주지 않아 문제가 생기게 되었다.
결국 toast.autoClose는 .autoClose의 checked요소의 값을 받아와야 하는 것이다.
toast.autoClose = $autoClose.checked;
따라서, 다음과 같이 리펙토링 할 수 있다.
$autoCloseDelay.disabled = !$autoClose.checked;
6. 스크롤 방지
.toast가 생성되고 삭제될 때, 화면의 크기가 늘어나는 현상이 발생했다. 화면의 크기가 늘어나면서 스크롤이 생겼고, .toast 애니메이션이 어색하게 동작했다.
.toast-container 영역 오른쪽에서 .toast가 생긴 후, 이동하는데, 이때 화면의 크기가 늘어나는 것이다.
따라서, .toast-container에 overflow: hidden CSS를 동적으로 추가해서, 화면의 크기가 늘어나지 않도록 했다.
$toastContainer.style.overflow = 'hidden';
7. 모듈화
toast기능을 어디서든 재사용할 수 있게 하기위해 모듈화를 진행하였다.
app.js에서 toast를 생성하기 위한 설정 정보를 담은 객체(toastInfo)를 넘겨준다. 그리고 toast.js에서 toast가 생성이 되도록 구현했다.
<배운점>
++ Javascript를 동적인 방법으로 코딩할 때에는 코드의 실행흐름을 잘 알아야 의도한 방식으로 코딩할 수 있다는 것을 느꼈다.
++ timer를 쓰는 방법도 있지만, 상황에 맞는 적절한 event를 쓰는 것이 더 적절하다고 생각했다.
'JavaScript > PairProgramming1 회고' 카테고리의 다른 글
Carousel Slider (0) | 2023.05.09 |
---|---|
AutoComplete (0) | 2023.05.09 |
Tabs (0) | 2023.05.09 |
StopWatch (0) | 2023.05.09 |
PopupModal (0) | 2023.05.09 |