stacks of toast
by Jason Miller
HTML
<div class="toasts">
<div class="toasts-container">
<template id="toast">
<div class="toast">
<span tpl="message"></span>
</div>
</template>
</div>
</div>
<button onClick="showToast('Toast ' + new Date().getMilliseconds())">Show Toast</button>
CSS
.toasts {
position: fixed;
left: 0;
bottom: 0;
height: 0;
width: 100%;
overflow: visible;
}
.toasts-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
/* justify-content: flex-end; */
flex: 1;
transition: transform 250ms ease;
overflow: hidden;
}
.toast {
display: flex;
flex: 1;
padding: 10px 20px;
margin: 0 0 10px;
border-radius: 5px;
background: rgba(0,0,0,0.95);
color: #fff;
z-index: 999;
animation: slideUp 500ms ease forwards 1;
}
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
}
JavaScript
const parent = self.toast.parentNode;
const tpl = self.toast.content.firstElementChild;
self.toast.remove();
function showToast(message, { delay = 5000 } = {}) {
let options = { message };
let toast = tpl.cloneNode(true);
for (let node of toast.querySelectorAll('[tpl]')) {
node.textContent = options[node.getAttribute('tpl')];
}
const oldHeight = parent.offsetHeight;
parent.appendChild(toast);
const newHeight = parent.offsetHeight;
const height = newHeight - oldHeight;
parent.height = newHeight;
parent.animate({ transform: `translateY(-${newHeight}px)` }, { duration: 250, fill: 'forwards' });
setTimeout(async () => {
await toast.animate({ transform: 'translateY(50%)', opacity: 0 }, { duration: 500, fill: 'forwards' }).finished;
toast.remove();
const newHeight = parent.height - height;
parent.height = newHeight;
parent.animate({ transform: `translateY(-${newHeight}px)` }, { duration: 0, fill: 'forwards' });
}, delay);
}