Introduction
React-Toastify라는 토스트 알림 라이브러리가 있다. 이것의 90개 정도의 기능을 구현해보려고 한다.
파일 구조
먼저 토스트 메시지가 잘 보이는지 확인합니다.
Open with Live Server를 클릭
이런 스타일을 만들어야 한다.
일단 박스를 만들어 position : relative를 주고
닫기 위한 X 버튼에 absolute로 절대 위치를 잡아준다.
또한 뷰포트에 무관하게 토스트 메시지의 너비를 max-width로 잡는다. (200 ~ 250px)
그 다음 메시지가 오른쪽 상단, 중앙 상단, 왼쪽 상단 그리고 세 방향에서 모두 하단에도 위치할 수 있다.
이것을 위해서는 마치 X 버튼을 위치시켰듯이 레이아웃 하나를 더 만드는 것이 구성상 필요하다.
컨테이너는 토스트 메시지가 오른쪽 상단이나 왼쪽 상단 모서리에 고정된 상태로 유지되길 원하기 때문에 position : fixed 를 toast-container에 준다.
그리고, data-position에 따라 위치를 바꾸는 것 또한 styles.css에 선언한다. 이 때 메시지 박스는 max-width가 아닌 width 250px로 변경해도 된다.
위의 셀렉터는 제대로 작동하지 않는다. 말했듯이 우리는 top-right, top-left, top-middle, bottom-right,,, 이렇게 모두 적어야 한다.
이때 data property를 셀렉터에서 캐치하는 문법을 적용해본다. ^는 첫 문자열에 나오는 것을 강제하고, $는 마지막 문자열이 되는 것을 강제한다. 그러면 이와 같이 적용시킬 수 있다.
bottom-도 셀렉터로 지원하도록 하면 이렇게 된다.
바꿔서 변경사항을 확인해본다.
center 정렬도 추가해준다. left: 50%를 주면 박스가 50% 이동해버리니, transform: translateX(-50%)로 박스의 50%만큼을 되돌려줘서 정중앙에 배치한다.
또한, toast에 width: 250px는 toast-container의 너비로 준다. 이 컨테이너가 메시지 박스를 fixed하게 배치해주는 역할이면서 내부에 마진이나 패딩을 가질 필요가 없기 때문이다.
( bottom도 잘 작동하지만 대부분 상단이 보기 편하다. )
클릭으로 간단하게 제거하는 기능도 있기 때문에 cursor 스타일도 추가한다.
여러 개의 토스트 메시지가 쌓이는 것을 고려해 스타일을 추가해본다. gap을 0.5rem으로 준다. 꽤 보기 좋은 것 같다.
이제는 실제로 메시지를 띄우기 위한 자바스크립트를 script.js에 작성한다.
Toast라는 클래스를 만들고, options를 받아 constructor에서 처리한다.
options는 객체 형식일테니 Object.entries로 이차원 배열화하면 각 요소를 forEach로 [key, value]로 접근할 수 있게 된다.
이 방식으로 options를 처리하면 좀더 깔끔하다. 이를 통해 인스턴스의 프로퍼티에 그대로 key를 넣어 value를 할당해주는 것이 가능하다. (options로 담아둘 수도 있다.)
요소를 받아 toast-container에 위치시키는 setter를 position이라는 이름으로 선언한다.
콘솔에 position으로 들어온 값은 잘 찍힌다.
토스트 메시지는 자동으로 표시하는 것을 기본 옵션으로 주는 것이 더 나을지도 모른다.
toastElem은 constructor에서 생성하도록 한다.
그러면 자동으로 표시하기 전에 setter position은 어떻게 적용시킬 것인가가 문제다.
그리고 container 자체가 없어도 만들어서 메시지를 배치해 보여주고 싶다.
appendChild() 대신 append()를 써서 무엇이 다른지 궁금할 수 있다. append는 요소에 Node 객체와 DOM string 모두 추가할 수 있다. 그리고 추가한 자식 노드를 반환하지 않는다.
패딩을 살짝 조절해준다.
메시지 제거 기능을 구현한다. 몇 초 후 자동으로 제거되는 기능이다.
이때 유의할 것은 toast-container가 그대로 남아있지 않게 하는 것이다. 메시지 박스만 제거시키지 말자.
요소가 깔끔히 지워졌다.
이제 앞서 확인한 autoClose 기능을 Toast 클래스 내에 구현해본다.
자동 닫기가 아닌 경우는 계속 살아남도록 ealry return을 걸어놓는다.
추가적으로 만약 setTimeout 비동기 작업이 걸려있는 상태에서도 value가 주어지는 경우 바로 close할 수 있게 만든다.
1초 후 메시지가 잘 사라진다.
autoClose를 주지 않으면? 유지된다.
하지만 false로 주지도 않았고, 따로 주지 않으면 어떤 옵션이 적용되는가?
기본 옵션을 만들어준다음 { ...기본 옵션, ...option } 로 앞에 선언된 것을 뒤에 입력된 options로 덮어씌운다.
옵션을 주지 않으면 자동으로 5초 후 사라지는 메시지가 되는 것을 확인할 수 있다.
또, position 기본 옵션을 준다.
아래 로직은 별도의 update 메서드에 분리해 가독성을 향상시킨다. “~~”로 업데이트한다. 만 알면 되기 때문이다.
remove()를 호출하지 않더라도 position만 주는 경우 컨테이너가 남아있을 것이다. 이를 없애줄 로직을 position에 추가한다.
메시지가 닫힐 때 alert가 트리거되는지 확인하는 코드를 작성한다.
이제 메서드로 분리한다. canClose로 이름 지었다.
닫으려고 하면 닫히지 않는다.
콘솔로 파라미터를 확인해보면 true가 나온다.
적합한 솔루션을 찾아볼 필요가 있다.
이 문제는 constructor에서 remove를 바인딩하는 것을 일찍했기 때문이다. 아직 가지고 있지 않을 때 접근하도록 되어 있어 발생한 문제였다. this.update()를 마지막으로 옮긴다.
다시 실행해 클릭해보면 기본적으로 canClose: true 이므로 닫을 수 있고,
이와 같이 옵션을 주면 클릭해도 닫히지 않는다.
이제 canClose가 true인 경우에만 닫기 버튼(x)를 넣기로 한다.
transition
클릭할 때마다 토스트를 만들기 위해 button을 추가하고 이벤트 핸들러를 등록한다.
클릭하면 나타나는 모습은 이렇다.
이것은 단순히 계속 쌓이고 있는데 슬라이드시키면서 안쪽으로 미끄러지듯이 넣고 싶다. (React-toastify 참고)
일단, 화면 밖에서 시작하도록 transform과 transition 프로퍼티를 추가하고 .toast.show{} 를 만든다.
버튼을 클릭해보면 오른쪽에서 왼쪽으로 슬라이드하면서 들어온다.
다만, 다른 방향에서 슬라이드하면서 들어오는 것을 고려해야 한다.
입력에 따라 달라져야할 스타일은 transform이니 클래스로 분리한다.
Math.random > .5 로 스타일이 잘 적용되었는지 확인해본다.
작동하지 않는다.
Elements탭을 보니 컨테이너만 쌓이고 있다.
스타일을 .toast에 줘야 맞다.
그러나 작동하지 않는다. toast-container만 생겨난다. css 구체성이 낮기 때문이다. 높여준다.
랜덤으로 슬라이딩하면서 들어온다.
아직 중앙으로 내려오거나 올라오는 걸 구현하지 않았다. 추가해준다.
이 애니메이션은 허점이 있다. 여러번 누르면 토스트 메시지가 복제하듯이 생긴다.
하지만 가장 상단에서 항상 내려주는 방식이 되어야 하므로 transform: translateY(-100vh) 로 바꾼다.
이렇게 확인해보면 아래에서 위로 쌓을 때 조금 버벅거리는 것을 볼 수 있다. 이는 아직 신경 쓸 필요가 없다.
remove 메서드에 로직을 추가할 때 transitionend 이벤트가 발생할 때 요소의 remove가 호출되도록 변경한다.
이건 이전 메시지들이 차례로 사라지는 것을 구현하기 위해서이기도 하다.
잘 작동한다.
React-tostify에는 각 토스트 메시지 밑에 progress bar가 있다. 박스 하나가 필요하다.
둥근 모서리 부분이 오버플로되는 것을 숨기도록 되어 있다.
진행률은 CSS 변수로 적용해준다.
.toast 에 overflow를 주석처리하고 실행해본다.
이 부분이 이해 안될 수 있다.
.three {
background-color: var(--my-var, var(--my-background, pink)); /* my-var와 --my-background가 정의되지 않았을 경우 pink로 표시됨, 다시 말해 대체 변수임 */
}
CSS
복사
이제 0 ~ 1 사이로 지정한 뒤 확인해보자.
주의해야 할 것은 픽셀이 삐져나온다는 것이다 굉장히 알아차리기는 힘들다.
그래서 이게 필요하다.
스타일링이 끝났으니 대체 변수는 지워준다.
이제 css variables를 js로 접근해서 autoClose가 쓸 수 있게 해본다.
시간 초과가 되는 경우 지우기 위한 코드를 추가한다.
여기서 조금 복잡한 로직이 들어간다. (코드 생략) 지금까지 만든 진행표시줄은 슬라이딩된 메시지가 일시중지된 후에 줄어들게 만드는 것이다.
기본적으로 바로 줄어들게 되어 있기 때문이다.
계산을 잠깐 멈추는 연산이 추가된다.
pauseOnHover
이미 만든 autoClose가 관련되어 있다.
마우스 오버를 하면 프로그레스 바가 멈춘다.
pauseOnFocusLoss
토스트 메시지가 띄워졌을 때 다른 탭을 누른다면 초점을 잃은 동안 일시중지된다.
다시 탭에 복귀하면 정상 작동한다.