Trang chủ
Toastify
Save
Settings
Sign Up
Log In
Save
Settings
HTML
Copy
<div class="toast"> <div id="ft"></div> <button>Show Toast!</button> </div>
CSS
Copy
#ft { position: fixed; inset: 32px; z-index: 9999; pointer-events: none; } .t { position: absolute; display: flex; justify-content: flex-end; transition: all 0.23s cubic-bezier(0.21, 1.02, 0.73, 1); } .t.show .tb { animation: showTop 0.35s cubic-bezier(0.21, 1.02, 0.73, 1); } .t.hide .tb { animation: hideTop 0.45s cubic-bezier(0.06, 0.71, 0.55, 1); } .tb { background: #fff; border-radius: 8px; padding: 16px; min-width: 200px; max-width: 450px; box-shadow: 0 10px 15px -3px rgba(0,0,0,.1); } .tb.icon { padding-left: 48px; } .tb h3 { font-weight: 500; margin-bottom: 8px; } .tc { position: absolute; top: 16px; right: 16px; cursor: pointer; color: #999; transition: .2s; } .tc:hover { color: #333; } .ti { position: absolute; top: 16px; left: 16px; width: 24px; height: 24px; } .ts svg { color: #1DC071; } .te svg { color: #ef4444; } .ti svg { color: #0ea5e9; } .tw svg { color: #f59e0b; } @keyframes showTop { 0% { transform: translateY(-200%) scale(.6); opacity: .5; } 100% { transform: translateY(0) scale(1); opacity: 1; } } @keyframes hideTop { 0% { transform: translateY(0) scale(1); opacity: 1; } 100% { transform: translateY(-150%) scale(.6); opacity: 0; } }
JS
Copy
const FT_DELAY = 450; const toastHTML = ({ title, msg, icon, close }) => ` <div class="tb${icon ? ' icon' : ''}"> ${title || close ? ` <div class="th"> ${title ? `<h3>${title}</h3>` : ''} ${close ? `<div class="tc">✖</div>` : ''} </div>` : ''} ${icon ? `<div class="ti">${icon}</div>` : ''} <div class="td">${msg}</div> </div>`; const toastType = { success: { cls: 'ts', icon: '✅' }, error: { cls: 'te', icon: '❌' }, info: { cls: 'ti', icon: 'ℹ️' }, warning: { cls: 'tw', icon: '⚠️' }, }; const genId = () => Math.random().toString(36).slice(2) + Date.now(); const Toast = () => { let el = document.getElementById('ft'); let list = []; let timer = []; let pauseAt; const space = 16; let opt = { duration: 3000 }; const add = (data) => { const toast = { ...opt, ...data }; list.unshift(toast); const div = document.createElement('div'); div.className = `t show ${toast.cls || ''}`; div.dataset.id = toast.id; div.innerHTML = toastHTML({ title: toast.title, msg: toast.msg, icon: toast.icon, close: toast.close }); el.prepend(div); pos(); setDuration(); const btn = div.querySelector('.tc'); if (btn) btn.onclick = () => remove(toast.id); }; const pos = () => { let y = 0; list.forEach((t, i) => { const d = el.querySelector(`[data-id="${t.id}"]`); d && d.setAttribute('style', `transform: translateY(${i * space + y}px)`); y += d?.offsetHeight || 0; }); }; const remove = (id) => { const d = el.querySelector(`[data-id="${id}"]`); if (!d) return; d.classList.add('hide'); setTimeout(() => { d.remove(); list = list.filter(t => t.id !== id); pos(); }, FT_DELAY); }; const setDuration = (paused) => { timer.forEach(clearTimeout); timer = []; if (paused) return; const now = Date.now(); timer = list.map(t => { if (t.duration === Infinity) return null; const left = (t.duration || 0) + t.pause - (now - t.create); if (left < 0) return remove(t.id); return setTimeout(() => remove(t.id), left); }); }; el.onmouseenter = () => { pauseAt = Date.now(); setDuration(pauseAt); }; el.onmouseleave = () => { const diff = Date.now() - pauseAt; list = list.map(t => ({ ...t, pause: t.pause + diff })); pauseAt = null; setDuration(); }; return { show: (msg, type = 'info', opts = {}) => { const st = toastType[type] || {}; const toast = { ...opts, id: genId(), msg, icon: opts.icon || st.icon, cls: st.cls, create: Date.now(), pause: 0 }; add(toast); }, config: (o) => (opt = { ...opt, ...o }), }; }; const FT = Toast(); // Example usage FT.config({ duration: 4000 }); document.querySelector('.toast button').onclick = () => FT.show('Hello! Toast active.', 'success', { title: 'Success', close: true });