적응형이며 액세스 가능한 토스트 구성요소를 빌드하는 방법에 관한 기본 개요입니다.
이 게시물에서는 토스트 구성요소를 빌드하는 방법에 대한 생각을 공유하고자 합니다. 데모를 사용해 보세요.
동영상을 선호하는 경우 이 게시물의 YouTube 버전을 참고하세요.
개요
토스트는 사용자에게 비대화형, 수동형, 비동기식의 짧은 메시지를 표시합니다. 일반적으로 작업 결과를 사용자에게 알리는 인터페이스 피드백 패턴으로 사용됩니다.
상호작용 수
토스트는 알림, 알림, 프롬프트와 달리 상호작용이 불가능합니다. 닫거나 유지할 수 없습니다. 알림은 더 중요한 정보, 상호작용이 필요한 동기식 메시지 또는 시스템 수준 메시지 (페이지 수준이 아님)를 위한 것입니다. 토스트는 다른 알림 전략보다 수동적입니다.
마크업
<output>
요소는 화면 리더에 전달되므로 토스트에 적합합니다. 올바른 HTML은 JavaScript 및 CSS로 향상할 수 있는 안전한 기반을 제공하며, JavaScript가 많이 사용됩니다.
토스트
<output class="gui-toast">Item added to cart</output>
role="status"
를 추가하여 포용성을 높일 수 있습니다. 이렇게 하면 브라우저가 사양에 따라 <output>
요소에 암시적 역할을 부여하지 않는 경우 대체 옵션을 제공할 수 있습니다.
<output role="status" class="gui-toast">Item added to cart</output>
토스트 컨테이너
한 번에 두 개 이상의 토스트가 표시될 수 있습니다. 여러 토스트를 조정하기 위해 컨테이너가 사용됩니다. 이 컨테이너는 화면의 토스트 위치도 처리합니다.
<section class="gui-toast-group">
<output role="status">Wizard Rose added to cart</output>
<output role="status">Self Watering Pot added to cart</output>
</section>
레이아웃
뷰포트의 inset-block-end
에 토스트를 고정하도록 선택했습니다. 토스트가 더 추가되면 해당 화면 가장자리에서 스택됩니다.
GUI 컨테이너
토스트 컨테이너는 토스트를 표시하기 위한 모든 레이아웃 작업을 실행합니다. 뷰포트에 fixed
이며 논리적 속성 inset
를 사용하여 고정할 가장자리와 동일한 block-end
가장자리의 약간의 padding
를 지정합니다.
.gui-toast-group {
position: fixed;
z-index: 1;
inset-block-end: 0;
inset-inline: 0;
padding-block-end: 5vh;
}
토스트 컨테이너는 표시 영역 내에 배치하는 것 외에도 토스트를 정렬하고 배포할 수 있는 그리드 컨테이너입니다. 항목은 justify-content
를 사용하여 그룹으로 중앙에 정렬되고 justify-items
를 사용하여 개별적으로 중앙에 정렬됩니다.
토스트가 겹치지 않도록 gap
를 약간 추가합니다.
.gui-toast-group {
display: grid;
justify-items: center;
justify-content: center;
gap: 1vh;
}
GUI 토스트
개별 토스트에는 padding
가 있고, border-radius
를 사용하면 모서리를 더 부드럽게 만들 수 있으며, min()
함수를 사용하면 모바일 및 데스크톱 크기 조절에 도움이 됩니다. 다음 CSS의 반응형 크기는 토스트가 표시 영역의 90% 또는 25ch
보다 넓어지지 않도록 합니다.
.gui-toast {
max-inline-size: min(25ch, 90vw);
padding-block: .5ch;
padding-inline: 1ch;
border-radius: 3px;
font-size: 1rem;
}
스타일
레이아웃과 위치를 설정했으면 사용자 설정 및 상호작용에 적응하는 데 도움이 되는 CSS를 추가합니다.
토스트 컨테이너
토스트는 상호작용이 불가능합니다. 토스트를 탭하거나 스와이프해도 아무 일도 일어나지 않지만 현재 포인터 이벤트는 소비합니다. 다음 CSS를 사용하여 토스트가 클릭을 가로채지 못하도록 합니다.
.gui-toast-group {
pointer-events: none;
}
GUI 토스트
맞춤 속성, HSL, 환경설정 미디어 쿼리를 사용하여 토스트에 밝은 테마 또는 어두운 테마를 적용합니다.
.gui-toast {
--_bg-lightness: 90%;
color: black;
background: hsl(0 0% var(--_bg-lightness) / 90%);
}
@media (prefers-color-scheme: dark) {
.gui-toast {
color: white;
--_bg-lightness: 20%;
}
}
애니메이션
새 토스트가 화면에 표시될 때 애니메이션과 함께 표시됩니다.
모션 감소를 수용하려면 기본적으로 translate
값을 0
로 설정하고 모션 환경설정 미디어 쿼리에서 모션 값을 길이로 업데이트하면 됩니다 . 모든 사용자에게 애니메이션이 표시되지만 일부 사용자만 토스트가 이동합니다.
다음은 토스트 애니메이션에 사용된 키프레임입니다. CSS는 하나의 애니메이션으로 토스트의 진입, 대기, 종료를 모두 제어합니다.
@keyframes fade-in {
from { opacity: 0 }
}
@keyframes fade-out {
to { opacity: 0 }
}
@keyframes slide-in {
from { transform: translateY(var(--_travel-distance, 10px)) }
}
그런 다음 토스트 요소가 변수를 설정하고 키프레임을 조정합니다.
.gui-toast {
--_duration: 3s;
--_travel-distance: 0;
will-change: transform;
animation:
fade-in .3s ease,
slide-in .3s ease,
fade-out .3s ease var(--_duration);
}
@media (prefers-reduced-motion: no-preference) {
.gui-toast {
--_travel-distance: 5vh;
}
}
자바스크립트
스타일과 스크린 리더 액세스 가능한 HTML이 준비되면 사용자 이벤트를 기반으로 토스트의 생성, 추가, 소멸을 조정하는 JavaScript가 필요합니다. 토스트 구성요소의 개발자 환경은 다음과 같이 최소한으로 간편하게 시작할 수 있어야 합니다.
import Toast from './toast.js'
Toast('My first toast')
토스트 그룹 및 토스트 만들기
토스트 모듈이 JavaScript에서 로드되면 토스트 컨테이너를 만들고 페이지에 추가해야 합니다. body
앞에 요소를 추가했습니다. 이렇게 하면 컨테이너가 모든 본문 요소의 컨테이너 위에 있으므로 z-index
스택 문제가 발생할 가능성이 줄어듭니다.
const init = () => {
const node = document.createElement('section')
node.classList.add('gui-toast-group')
document.firstElementChild.insertBefore(node, document.body)
return node
}
init()
함수는 모듈 내부에서 호출되어 요소를 Toaster
로 스태시합니다.
const Toaster = init()
Toast HTML 요소 생성은 createToast()
함수로 실행됩니다. 이 함수에는 토스트의 텍스트가 필요하며, <output>
요소를 만들고, 일부 클래스와 속성으로 장식하고, 텍스트를 설정하고, 노드를 반환합니다.
const createToast = text => {
const node = document.createElement('output')
node.innerText = text
node.classList.add('gui-toast')
node.setAttribute('role', 'status')
return node
}
하나 이상의 토스트 관리
이제 JavaScript가 문서에 토스트를 포함할 컨테이너를 추가하고 생성된 토스트를 추가할 준비가 되었습니다. addToast()
함수는 하나 이상의 토스트 처리를 조정합니다. 먼저 토스트 수를 확인하고 모션이 정상인지 확인한 다음 이 정보를 사용하여 토스트를 추가하거나 다른 토스트가 새 토스트를 위해 '공간을 확보'하는 것처럼 보이도록 멋진 애니메이션을 실행합니다.
const addToast = toast => {
const { matches:motionOK } = window.matchMedia(
'(prefers-reduced-motion: no-preference)'
)
Toaster.children.length && motionOK
? flipToast(toast)
: Toaster.appendChild(toast)
}
첫 번째 토스트를 추가할 때 Toaster.appendChild(toast)
는 CSS 애니메이션(안으로 애니메이션, 3s
대기, 밖으로 애니메이션)을 트리거하는 토스트를 페이지에 추가합니다.
flipToast()
는 기존 토스트가 있을 때 호출되며 폴 루이스의 FLIP이라는 기법을 사용합니다. 새 토스트가 추가되기 전과 후의 컨테이너 위치 차이를 계산하는 것입니다.
토스터가 현재 어디에 있고 어디로 이동할지 표시한 다음 이전 위치에서 현재 위치로 애니메이션을 적용하는 것과 같습니다.
const flipToast = toast => {
// FIRST
const first = Toaster.offsetHeight
// add new child to change container size
Toaster.appendChild(toast)
// LAST
const last = Toaster.offsetHeight
// INVERT
const invert = last - first
// PLAY
const animation = Toaster.animate([
{ transform: `translateY(${invert}px)` },
{ transform: 'translateY(0)' }
], {
duration: 150,
easing: 'ease-out',
})
}
CSS 그리드가 레이아웃을 들어 올립니다. 새 토스트가 추가되면 그리드는 토스트를 시작 부분에 배치하고 다른 토스트와 간격을 둡니다. 한편 웹 애니메이션은 이전 위치에서 컨테이너의 애니메이션을 만드는 데 사용됩니다.
모든 JavaScript 조합
Toast('my first toast')
가 호출되면 토스트가 생성되고 페이지에 추가됩니다(새 토스트를 수용하기 위해 컨테이너에 애니메이션이 적용될 수도 있음). promise가 반환되고 생성된 토스트는 promise 해결을 위해 CSS 애니메이션 완료 (세 개의 키프레임 애니메이션)를 감시합니다.
const Toast = text => {
let toast = createToast(text)
addToast(toast)
return new Promise(async (resolve, reject) => {
await Promise.allSettled(
toast.getAnimations().map(animation =>
animation.finished
)
)
Toaster.removeChild(toast)
resolve()
})
}
이 코드에서 혼란스러운 부분은 Promise.allSettled()
함수와 toast.getAnimations()
매핑입니다. 토스트에 여러 키프레임 애니메이션을 사용했으므로 모두 완료되었는지 확실히 알기 위해 각 애니메이션을 JavaScript에서 요청하고 완료될 때까지 각 finished
약속을 관찰해야 합니다.
allSettled
는 모든 프로미스가 처리되면 완료된 것으로 자체 해결됩니다. await Promise.allSettled()
를 사용하면 다음 코드 줄에서 요소를 확실하게 삭제하고 토스트의 수명 주기가 완료되었다고 가정할 수 있습니다. 마지막으로 resolve()
를 호출하면 상위 수준의 Toast 약속이 실행되므로 개발자는 토스트가 표시된 후 정리하거나 다른 작업을 할 수 있습니다.
export default Toast
마지막으로 Toast
함수는 다른 스크립트에서 가져와 사용할 수 있도록 모듈에서 내보내집니다.
Toast 구성요소 사용
토스트 또는 토스트의 개발자 환경을 사용하려면 Toast
함수를 가져와 메시지 문자열로 호출하면 됩니다.
import Toast from './toast.js'
Toast('Wizard Rose added to cart')
개발자가 토스트가 표시된 후 정리 작업 등을 수행하려면 async 및 await를 사용하면 됩니다.
import Toast from './toast.js'
async function example() {
await Toast('Wizard Rose added to cart')
console.log('toast finished')
}
결론
이제 제가 어떻게 했는지 알았으니 어떻게 하시겠어요? 🙂
접근 방식을 다양화하고 웹에서 빌드하는 모든 방법을 알아보겠습니다. 데모를 만들어 트윗해 주시면 아래의 커뮤니티 리믹스 섹션에 추가해 드리겠습니다.
커뮤니티 리믹스
- HTML/CSS/JS를 사용한 @_developit: 데모 및 코드
- HTML/CSS/JS를 사용한 Joost van der Schee: 데모 및 코드