টপ লেয়ারে ও সেখান থেকে এলিমেন্ট অ্যানিমেট করা

"টপ লেয়ারে" রেন্ডার করা এলিমেন্ট (যেমন <dialog>, popover অ্যাট্রিবিউট সহ এলিমেন্ট বা টুলটিপ) অ্যানিমেট করা ঐতিহাসিকভাবে কঠিন কারণ সেগুলি display: none এবং দৃশ্যমান অবস্থার মধ্যে টগল করে। আধুনিক CSS-এ @starting-style, transition-behavior: allow-discrete এবং overlay প্রপার্টি আছে যা এইসব এলিমেন্টের জন্য মসৃণ এন্ট্রি ও এক্সিট ট্রানজিশন চালু করে। মনে রাখবেন, নিচে দেওয়া উদাহরণে নেটিভ CSS নেস্টিং ব্যবহার করা হয়েছে।

প্রয়োগ

১. ডিসক্রিট ট্রানজিশন চালু করুন

display প্রপার্টি অ্যানিমেট করতে, আপনাকে অবশ্যই transition-behavior: allow-discrete সেট করতে হবে। এটি এক্সিট ট্রানজিশনের সময় এলিমেন্টকে দৃশ্যমান থাকতে দেয়। ট্রানজিশন শর্টহ্যান্ড ব্যবহার করলে, শর্টহ্যান্ড যাতে এটিকে বাতিল না করে দেয় তার জন্য পরে transition-behavior: allow-discrete যোগ করতে ভুলবেন না।

২. overlay প্রপার্টি

কোনও এলিমেন্ট টপ লেয়ারের মধ্যে বা বাইরে সরানো হলে, সেটিকে অবশ্যই overlay প্রপার্টি ট্রানজিশন করতে হবে। এটি নিশ্চিত করে যে অ্যানিমেশনের সময়কালের জন্য এলিমেন্টটি উপরের লেয়ারে থাকে, এটি অন্য এলিমেন্ট বা ভিউপোর্ট দ্বারা অকালে ক্লিপ হওয়া থেকে আটকায়।

৩. @starting-style-এর সাথে এন্ট্রি অ্যানিমেশন

@starting-style at-রুল ব্যবহার করে সেই স্টাইলগুলি নির্ধারণ করুন যেগুলি কোনও এলিমেন্ট প্রথমবার রেন্ডার করার সময় বা তার display none থেকে পরিবর্তিত হওয়ার সময় থেকে ট্রানজিশন করা উচিত।

৪. ব্যাকড্রপ অ্যানিমেট করা হচ্ছে

::backdrop ছদ্ম-এলিমেন্টকে একইভাবে অ্যানিমেট করা যেতে পারে, এর জন্য এর নিজস্ব প্রপার্টিতে ট্রানজিশন প্রয়োগ করতে হবে।

উদাহরণ

/* 1. Define the visible (open) state */
dialog[open],
[popover]:popover-open {
  opacity: 1;
  transform: scale(1);

  /* 2. Define the starting state for entry (must come after open state) */
  @starting-style {
    opacity: 0;
    transform: scale(0.9);
  }
}

/* 3. Define the base (closed/exit) state and transitions */
dialog,
[popover] {
  opacity: 0;
  transform: scale(0.9);

  /* MANDATORY: transition display and overlay for top-layer elements */
  transition-property: opacity, transform, display, overlay;
  transition-duration: 0.3s;
  transition-timing-function: ease-out;
  /* Applies to discrete properties like display and overlay */
  transition-behavior: allow-discrete; /* Note: be sure to write this after the shorthand */
}

/* 4. Animate the backdrop */
dialog::backdrop,
[popover]::backdrop {
  background-color: rgba(0, 0, 0, 0);
  transition-property: background-color, display, overlay;
  transition-duration: 0.3s;
  transition-timing-function: ease-out;
  transition-behavior: allow-discrete;
}

dialog[open]::backdrop,
[popover]:popover-open::backdrop {
  background-color: rgba(0, 0, 0, 0.5);

  @starting-style {
    background-color: rgba(0, 0, 0, 0);
  }
}

/* 5. Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
  dialog,
  [popover] {
    /* Disable movement and shorten duration for a simple fade */
    transform: none;
    transition-duration: 0.1s;
  }

  @starting-style {
    dialog[open],
    [popover]:popover-open {
      transform: none;
    }
  }
}

সীমাবদ্ধতা ও অ্যাক্সেসিবিলিটি

  • বাধ্যতামূলক: টপ লেয়ারে কোনও এলিমেন্ট যোগ করা বা সেখান থেকে সরানো হলে, আপনার transition তালিকায় overlay অন্তর্ভুক্ত করুন।
  • বাধ্যতামূলক: display প্রপার্টি ট্রানজিশনের জন্য allow-discrete ব্যবহার করুন।
  • বাধ্যতামূলক: ট্রানজিশন সহজ করে (যেমন, ট্রান্সফর্ম সরিয়ে দেওয়া এবং সময়সীমা কমানো) prefers-reduced-motion ব্যবহার করে কম মোশনের জন্য ব্যবহারকারীর পছন্দকে সম্মান করুন।
  • করণীয়: সঠিক ক্যাসকেডিং নিশ্চিত করতে "খোলা" স্টেট সিলেক্টরের ভিতরে বা পরে @starting-style ব্লকটি রাখুন।
  • করবেন না: এক্সিট অ্যানিমেশনের জন্য @starting-style ব্যবহার করবেন না; এক্সিট অ্যানিমেশন বেস (বন্ধ) অবস্থায় ট্রানজিশন দ্বারা নির্ধারিত হয়।

ফলব্যাক স্ট্র্যাটেজি

ট্রানজিশন-বিহেভিয়ারের জন্য ফলব্যাক ও ব্রাউজার সাপোর্ট

Firefox 129+ ভার্সন display প্রপার্টি (Firefox বাগ 1882408) ট্রানজিশন না করেই transition-behavior: allow-discrete (CSS.supports('transition-behavior', 'allow-discrete') true রিটার্ন করে) পার্স করে, যার ফলে এক্সিট করার সময় এলিমেন্টগুলি সঙ্গে সঙ্গে অদৃশ্য হয়ে যায়।

ডিসক্রিট display ট্রানজিশন সাপোর্ট নির্ভরযোগ্যভাবে শনাক্ত করতে, শুধুমাত্র CSS.supports('transition-behavior', 'allow-discrete')-এর উপর নির্ভর না করে, none-এ ট্রানজিশন করার সময় কোনও অস্থায়ী এলিমেন্টের কম্পিউট করা display দৃশ্যমান থাকে কিনা তা পরীক্ষা করুন:

let supportsDisplayTransition;
function canTransitionDisplay() {
  if (supportsDisplayTransition !== undefined) return supportsDisplayTransition;
  if (!window.CSS?.supports?.('transition-behavior', 'allow-discrete') || !document.body) {
    return false;
  }
  const probe = document.createElement('div');
  // The shorthand is intentional here: browsers that don't parse allow-discrete
  // drop the whole declaration, so display: none applies instantly and the probe
  // correctly returns false. !important guards against global reduced-motion
  // resets like `* { transition: none !important }`.
  probe.style.cssText = 'transition: display 1s allow-discrete !important; display: block;';
  document.body.appendChild(probe);
  getComputedStyle(probe).display;
  probe.style.display = 'none';
  supportsDisplayTransition = getComputedStyle(probe).display === 'block';
  probe.remove();
  return supportsDisplayTransition;
}

টপ-লেয়ার এক্সিট ফলব্যাক

এন্ট্রি অ্যানিমেশন @starting-style সাপোর্ট করে এমন সব ব্রাউজারে পিওর CSS-এ কাজ করে—কোনও .is-opening ক্লাসের প্রয়োজন নেই কারণ এন্ট্রি ট্রানজিশন overlay বা ডিসক্রিট display ট্রানজিশনের উপর নির্ভর করে না।

এক্সিট অ্যানিমেশনের জন্য overlay এবং ডিসক্রিট display ট্রানজিশন সাপোর্ট, দুটিই প্রয়োজন। দুটির মধ্যে কোনওটিই কাজ না করলে (যেমন Firefox ও Safari-তে), ওপেন-স্টেট সিলেক্টরকে :is()-এ র‍্যাপ করুন এবং :where(:not([data-closing])) (নেস্টিং &::backdrop ও @starting-style) অ্যাপেন্ড করুন যাতে data-closing সেট করলে এক্সিট ট্রানজিশন ট্রিগার হয় এবং এলিমেন্টটি টপ লেয়ারে থাকে, তারপর .close() বা .hidePopover() কল করার আগে getAnimations() সেটেল হওয়ার জন্য অপেক্ষা করুন:

:is(dialog[open], [popover]:popover-open):where(:not([data-closing])) {
  opacity: 1;
  transform: scale(1);

  @starting-style {
    opacity: 0;
    transform: scale(0.9);
  }

  &::backdrop {
    background-color: rgb(0 0 0 / 0.5);

    @starting-style {
      background-color: transparent;
    }
  }
}
// Evaluate lazily: canTransitionDisplay() needs document.body, so a top-level
// const would be permanently false if this script runs in <head>.
function supportsTopLayerExit() {
  return window.CSS?.supports?.('overlay', 'auto') && canTransitionDisplay();
}

async function closeTopLayer(element) {
  if (!supportsTopLayerExit()) {
    element.setAttribute('data-closing', '');
    const animations = element.getAnimations({ subtree: true });
    if (animations.length > 0) {
      await Promise.race([
        Promise.allSettled(animations.map((a) => a.finished)),
        new Promise((r) => setTimeout(r, 2000)),
      ]);
    }
    if (!element.hasAttribute('data-closing')) return;
    element.removeAttribute('data-closing');
  }

  element.close();
  // Or for popover:
  // element.hidePopover();
}

// Route native close requests (Esc, closedby light dismiss) through the same
// helper so they animate too, instead of closing instantly.
dialog.addEventListener('cancel', (event) => {
  event.preventDefault();
  closeTopLayer(dialog);
});

পপওভার লাইট ডিসমিস এবং popovertarget টগল ইন্টারসেপ্ট করা যাবে না (beforetoggle শুধুমাত্র খোলার সময় বাতিল করা যাবে), তাই যেসব ব্রাউজারে ফলব্যাক প্রয়োজন সেগুলিতে এই এক্সিটগুলি ইনস্ট্যান্ট হয়। এক্সিট অ্যানিমেশন গুরুত্বপূর্ণ হলে, closeTopLayer() কল করে এমন একটি এক্সপ্লিসিট ক্লোজ কন্ট্রোল প্রদান করুন।

পপওভারের জন্য ফলব্যাক ও ব্রাউজার সাপোর্ট

পপওভার API মূলত প্রোগ্রেসিভ এনহ্যান্সমেন্ট, তবে এর সংজ্ঞায়িত আচরণ — টপ-লেয়ার প্রোমোশন, লাইট-ডিসমিস এবং popovertarget ইনভোকেশন — শুধুমাত্র CSS-এর সমতুল্য নয়। পুরনো ব্রাউজারের ক্ষেত্রে পলিফিল বা ম্যানুয়াল ফলব্যাক প্রয়োজন হয়, যদি আপনি কোনওটিই শিপ করতে না চান।

Polyfill: পুরনো ব্রাউজারে popover অ্যাট্রিবিউট কাজ করানোর জন্য, শর্তসাপেক্ষে @oddbird/popover-polyfill লোড করুন। বাধ্যতামূলক: HTMLElement.prototype-এ popover প্রপার্টি চেক করে ফিচার শনাক্ত করুন এবং নেটিভ সাপোর্ট না থাকলে শুধুমাত্র তখনই পলিফিল লোড করুন — শর্তহীনভাবে এটি লোড করবেন না।

বান্ডলার বা ইমপোর্ট ম্যাপের মাধ্যমে:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

বান্ডলার ছাড়া, <script type="module">-এর মধ্যে CDN থেকে ইমপোর্ট করুন:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

স্টাইলিং সংক্রান্ত সতর্কতা: পলিফিল আসল :popover-open ছদ্ম-ক্লাসকে সংজ্ঞায়িত করতে পারে না, তাই এটি পরিবর্তে .\:popover-open ক্লাস প্রয়োগ করে। বাধ্যতামূলক: :is() বা :where()-এর সাথে দুটিকে একত্রিত করুন, অন্যথায় :popover-open নেই এমন ব্রাউজার সম্পূর্ণ নিয়মটি বাতিল করে দেয়:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

বিকল্প হিসেবে, পলিফিল ছাড়া লিগ্যাসি ফলব্যাকের জন্য, position: fixed ব্যবহার করুন এবং getBoundingClientRect()-এর মাধ্যমে ম্যানুয়ালি কোঅর্ডিনেট হিসেব করুন অথবা ব্যবহারের ক্ষেত্রে গ্রহণযোগ্য হলে inset: auto-এর সাথে ডিফল্ট পজিশনিংয়ের উপর নির্ভর করুন।