notifications

الگوهای حرفه‌ای برای انیمیشن Toast، Snackbar و اعلان‌های موقت

این Skill الگوهای آماده برای ورود، خروج، چیدمان و حذف تعاملی Toast، Snackbar، Alert و Banner ارائه می‌دهد؛ از انیمیشن‌های Slide و Fade تا Spring، Swipe و مدیریت اعلان‌های روی هم.

همچنین زمان‌بندی، نوار پیشرفت، وضعیت‌های موفقیت و خطا، دسترس‌پذیری با ARIA و پشتیبانی از prefers-reduced-motion را پوشش می‌دهد تا اعلان‌های محصول روان، قابل‌فهم و سازگار با استانداردهای UI باشند.

چه چیزی می‌گیرید

  • الگوهای CSS، GSAP و Framer Motion برای ورود و خروج اعلان‌ها
  • مدیریت Stack با رفتار Push یا Replace و انیمیشن جابه‌جایی
  • تعاملات Swipe، بازگشت با Elastic و حذف خودکار با Progress Indicator
  • انیمیشن‌های اختصاصی برای وضعیت‌های Success، Error، Warning و Info
  • راهنمای ARIA، مدیریت Focus و حالت Reduced Motion برای دسترس‌پذیری

برای چه کاری خوب است

  • توسعه‌دهندگان React و Frontend که سیستم Toast یا Snackbar می‌سازند
  • محصولاتی با اعلان‌های موفقیت، خطا، هشدار یا پیام‌های موقت متعدد
  • طراحان و تیم‌هایی که می‌خواهند موشن اعلان‌ها با استانداردهای Material Design 3 هماهنگ باشد

برای چه کاری نیست

  • اعلان‌های دائمی و پیچیده‌ای که به سیستم کامل مدیریت پیام یا Notification Center نیاز دارند
  • پروژه‌هایی که فقط به طراحی بصری اعلان بدون پیاده‌سازی انیمیشن نیاز دارند

دربارهٔ این اسکیل

Notification Animations

Patterns for toasts, snackbars, alerts, and temporary messages.

Notification Types

TypeDurationPositionDismissal
Toast3-5sTop/BottomAuto + swipe
Snackbar5-10sBottomAction + swipe
AlertUntil actionCenterButton only
BannerPersistentTopClose button

Entrance Patterns

Slide In (Recommended)

/* From bottom */
@keyframes toast-enter {
  from {
    transform: translateY(100%);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.toast-enter {
  animation: toast-enter 250ms ease-out forwards;
}
// GSAP
gsap.from('.toast', {
  y: 100,
  opacity: 0,
  duration: 0.25,
  ease: 'power2.out'
});

Scale + Fade

@keyframes toast-scale-enter {
  from {
    transform: scale(0.9);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

.toast-scale-enter {
  animation: toast-scale-enter 200ms ease-out forwards;
}

Spring Entrance (Playful)

// Framer Motion
<motion.div
  initial={{ y: 50, opacity: 0 }}
  animate={{ y: 0, opacity: 1 }}
  transition={{
    type: 'spring',
    damping: 20,
    stiffness: 300
  }}
/>

Exit Patterns

Fade Out

@keyframes toast-exit {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

.toast-exit {
  animation: toast-exit 150ms ease-in forwards;
}

Slide Out

// Exit in same direction as entry
gsap.to('.toast', {
  y: -50,
  opacity: 0,
  duration: 0.15,
  ease: 'power2.in',
  onComplete: () => toast.remove()
});

Swipe Dismiss

// Gesture-based dismissal
const swipeThreshold = 100;
const velocityThreshold = 500;

function handleSwipe(gesture) {
  if (Math.abs(gesture.deltaX) > swipeThreshold ||
      Math.abs(gesture.velocityX) > velocityThreshold) {
    // Dismiss in swipe direction
    gsap.to('.toast', {
      x: gesture.deltaX > 0 ? 300 : -300,
      opacity: 0,
      duration: 0.2,
      ease: 'power2.in'
    });
  } else {
    // Snap back
    gsap.to('.toast', {
      x: 0,
      duration: 0.3,
      ease: 'elastic.out(1, 0.5)'
    });
  }
}

Stacking Behavior

Push Stack

// New toasts push existing ones up
function addToast(toast) {
  // Animate existing toasts up
  gsap.to('.toast:not(:last-child)', {
    y: '-=70',  // Height + gap
    duration: 0.2,
    stagger: 0.05
  });

  // Insert new toast
  container.appendChild(toast);
  animateIn(toast);
}

Replace Stack

// Fade out old, fade in new
async function replaceToast(newToast) {
  const existing = document.querySelector('.toast');

  if (existing) {
    await gsap.to(existing, {
      opacity: 0,
      y: -10,
      duration: 0.15
    });
    existing.remove();
  }

  container.appendChild(newToast);
  animateIn(newToast);
}

Status-Specific Animations

Success

const successToast = {
  enter: {
    opacity: [0, 1],
    y: [20, 0],
    scale: [0.95, 1]
  },
  icon: {
    // Checkmark draw animation
    strokeDashoffset: [24, 0],
    duration: 0.3,
    delay: 0.2
  },
  timing: {
    duration: 250,
    easing: 'ease-out'
  }
};

Error

const errorToast = {
  enter: {
    opacity: [0, 1],
    x: [-10, 5, -3, 0]  // Subtle shake
  },
  timing: {
    duration: 300,
    easing: 'ease-out'
  },
  borderPulse: {
    // Attention-grabbing pulse
    boxShadow: [
      '0 0 0 0 rgba(239, 68, 68, 0.5)',
      '0 0 0 8px rgba(239, 68, 68, 0)',
    ],
    duration: 500,
    iterations: 2
  }
};

Warning

const warningToast = {
  enter: {
    opacity: [0, 1],
    y: [20, 0]
  },
  icon: {
    // Pulse animation for warning icon
    scale: [1, 1.1, 1],
    duration: 400,
    iterations: 2
  }
};

Info

const infoToast = {
  enter: {
    opacity: [0, 1],
    y: [10, 0]
  },
  timing: {
    duration: 200,
    easing: 'ease-out'
  }
};

Progress Indicator

/* Auto-dismiss countdown */
.toast-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  background: currentColor;
  animation: toast-countdown 5s linear forwards;
}

@keyframes toast-countdown {
  from { width: 100%; }
  to { width: 0%; }
}
// Pause on hover
toast.addEventListener('mouseenter', () => {
  progressBar.style.animationPlayState = 'paused';
});

toast.addEventListener('mouseleave', () => {
  progressBar.style.animationPlayState = 'running';
});

Accessibility

ARIA Announcements

<div
  role="alert"
  aria-live="polite"
  aria-atomic="true"
>
  {message}
</div>

Reduced Motion

@media (prefers-reduced-motion: reduce) {
  .toast-enter {
    animation: toast-fade-only 150ms ease-out;
  }

  @keyframes toast-fade-only {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}

Focus Management

// Return focus after dismissal
const previousFocus = document.activeElement;

toast.addEventListener('dismiss', () => {
  previousFocus?.focus();
});

React Implementation

import { AnimatePresence, motion } from 'framer-motion';

function ToastContainer({ toasts }) {
  return (
    <div className="toast-container">
      <AnimatePresence>
        {toasts.map(toast => (
          <motion.div
            key={toast.id}
            initial={{ opacity: 0, y: 50 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -20 }}
            transition={{
              duration: 0.25,
              ease: [0.2, 0, 0, 1]  // M3 standard
            }}
            layout  // Smooth reordering
          >
            <Toast {...toast} />
          </motion.div>
        ))}
      </AnimatePresence>
    </div>
  );
}

Timing Guidelines

AspectDurationNotes
Entrance200-250msFast enough to not interrupt
Exit150-200msSlightly faster than entrance
Auto-dismiss3000-5000msBased on content length
Swipe dismiss200msMatch gesture velocity
Stagger (stack)50msBetween existing toasts

Related Skills

  • Dependencies: easing, duration, entrance-patterns, exit-patterns
  • See also: accessibility, gesture-motion
  • Components: Toast, Snackbar, Alert, Banner

دانلود ZIP