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);
}