অতীতে, CSS ট্রানজিশন এলিমেন্টকে অ্যানিমেট করতে পারত না যখন সেগুলি প্রথমবার DOM-এ যোগ করা হত বা যখন তাদের display প্রপার্টি none থেকে পরিবর্তিত হত। @starting-style অ্যাট-রুল ও transition-behavior: allow-discrete, স্মুথ এন্ট্রি ও এক্সিট অ্যানিমেশন তৈরি করার জন্য ডিক্লারেটিভ উপায় প্রদান করে।
প্রয়োগ
১. display: none টগল অ্যানিমেট করা হচ্ছে
কোনও অ্যাট্রিবিউটের মাধ্যমে (যেমন, display: none-এর সাথে hidden) কোনও এলিমেন্টের দৃশ্যমানতা টগল করার সময় সেটিকে অ্যানিমেট করতে:
- দৃশ্যমান অবস্থা নির্ধারণ করুন: মূল ক্লাসে চূড়ান্ত প্রপার্টি ভ্যালু (যেমন,
opacity: 1) সেট করুন। - এন্ট্রি শুরুর অবস্থা নির্ধারণ করুন: এলিমেন্ট দৃশ্যমান হলে
@starting-styleব্যবহার করে ট্রানজিশন থেকে ভ্যালু নির্দিষ্ট করুন। - ডিসক্রিট ট্রানজিশন চালু করুন:
transitionপ্রপার্টিতেdisplayঅন্তর্ভুক্ত করুন এবংtransition-behavior: allow-discreteব্যবহার করুন। - এক্সিট স্টেট নির্ধারণ করুন:
hiddenঅ্যাট্রিবিউটে টার্গেট ভ্যালু সেট করুন।
.card {
display: block;
opacity: 1;
translate: 0;
/* MANDATORY: Use transition-behavior: allow-discrete for display transition */
transition:
display 0.4s,
opacity 0.4s ease-out,
translate 0.4s ease-out;
transition-behavior: allow-discrete;
}
/* Entry animation: transition FROM these values when first rendered */
@starting-style {
.card {
opacity: 0;
translate: 0 -20px;
}
}
/* Exit animation: transition TO these values when hidden */
.card[hidden] {
display: none;
opacity: 0;
translate: 0 -20px;
}
/* Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
.card {
/* Disable movement and shorten duration for a simple fade */
translate: none;
transition-duration: 0.1s;
}
@starting-style {
.card {
translate: none;
}
}
.card[hidden] {
translate: none;
}
}
২. DOM ইনসার্ট ও সরিয়ে দেওয়ার অ্যানিমেশন
appendChild()-এর মাধ্যমে যোগ করা বা remove()-এর মাধ্যমে সরিয়ে দেওয়া এলিমেন্টের জন্য:
- এন্ট্রি: উপরে দেখানো অনুযায়ী
@starting-styleব্যবহার করুন। ব্রাউজার অটোমেটিক "কিছুই নয়" থেকে এলিমেন্টের প্রাথমিক স্টাইলে পরিবর্তন শনাক্ত করবে এবং@starting-styleভ্যালু থেকে ট্রানজিশন ট্রিগার করবে। - সরানো: যেহেতু
element.remove()তাৎক্ষণিক এবং নিজে থেকে CSS ট্রানজিশন ট্রিগার করে না, তাই DOM থেকে নোড সরানোর আগে আপনাকে প্রথমে এক্সিট ট্রানজিশন ট্রিগার করতে হবে এবং এটি শেষ হওয়ার জন্য অপেক্ষা করতে হবে।
// 1. Trigger exit transition
element.hidden = true;
// 2. Wait for all active transitions/animations to finish,
// with a failsafe timeout in case an animation never ends (e.g. for looping animations)
const animations = element.getAnimations();
if (animations.length > 0) {
await Promise.race([
// Promise.allSettled ensures we wait even if some animations fail
Promise.allSettled(animations.map(a => a.finished)),
new Promise(r => setTimeout(r, 2000))
]);
}
// 3. Finally remove the node from the DOM
element.remove();
সীমাবদ্ধতা ও অ্যাক্সেসিবিলিটি
- বাধ্যতামূলক:
displayট্রানজিশন করার সময়transition-behavior: allow-discreteব্যবহার করুন। এটি ছাড়া, বেরিয়ে আসার সময় এলিমেন্টটি সাথে সাথে অদৃশ্য হয়ে যাবে। transitionশর্টহ্যান্ডেallow-discreteব্যবহার করবেন না — এটি করলে পুরনো ব্রাউজার সম্পূর্ণtransitionডিক্লারেশন উপেক্ষা করবে। যেসব ক্ষেত্রে এটি বাঞ্ছনীয়, সেগুলি ছাড়া অন্য ক্ষেত্রে আলাদাtransition-behavior: allow-discreteঘোষণা ব্যবহার করুন।- বাধ্যতামূলক: এন্ট্রি অ্যানিমেশনের জন্য
@starting-styleব্যবহার করুন। এটি প্রদান করা না হলে, ব্রাউজার কোনও এলিমেন্টের প্রথম স্টাইল আপডেটে (প্রাথমিক রেন্ডার বাdisplay: noneপরিবর্তন) ট্রানজিশন এড়িয়ে যায়। - করণীয়:
<dialog>বাpopover-এর মতো টপ-লেয়ার এলিমেন্ট অ্যানিমেট করার সময়transitionতালিকায়overlayঅন্তর্ভুক্ত করুন যাতে এক্সিট অ্যানিমেশনের সময় সেগুলি টপ লেয়ারে থাকে। - করণীয়:
prefers-reduced-motionমিডিয়া কোয়েরি ব্যবহার করে কম মোশনের জন্য ব্যবহারকারীর পছন্দকে সম্মান করুন। - করবেন না: এক্সিট অ্যানিমেশনের জন্য
@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;
}
ডিসক্রিট display ট্রানজিশন কাজ না করলে ফলব্যাক থেকে বেরিয়ে আসুন
@starting-style ব্যবহার করে এন্ট্রি অ্যানিমেশন জাভাস্ক্রিপ্ট ছাড়াই সব আধুনিক ব্রাউজার জুড়ে কাজ করে। ডিসক্রিট display ট্রানজিশন কাজ না করলে (!canTransitionDisplay()), hidden সেটিং অবিলম্বে display: none প্রয়োগ করে এবং এক্সিট অ্যানিমেশন (লুকানোর সময় এবং element.remove()-এর আগে, উভয় ক্ষেত্রেই) এড়িয়ে যায়। ভিজ্যুয়াল এক্সিট স্টেট ([data-closing]) display: none ([hidden]) থেকে আলাদা করুন যাতে এলিমেন্ট লুকানো বা সরানোর আগে opacity এবং translate অ্যানিমেশন শেষ করে। ধাপ ১-এর .card[hidden] নিয়ম (prefers-reduced-motion ওভাররাইড সহ) এর সাথে পরিবর্তন করুন:
.card:where([hidden], [data-closing]) {
opacity: 0;
translate: 0 -20px;
}
.card[hidden] {
display: none;
}
@media (prefers-reduced-motion: reduce) {
.card:where([hidden], [data-closing]) {
translate: none;
}
}
async function hideElement(el) {
if (canTransitionDisplay()) {
el.hidden = true;
} else {
el.setAttribute('data-closing', '');
}
const animations = el.getAnimations();
if (animations.length > 0) {
await Promise.race([
Promise.allSettled(animations.map((a) => a.finished)),
new Promise((r) => setTimeout(r, 2000)),
]);
}
// Skipped if showElement() cancelled the close mid-animation.
if (el.hasAttribute('data-closing')) {
el.removeAttribute('data-closing');
el.hidden = true;
}
}
function showElement(el) {
el.removeAttribute('data-closing');
el.hidden = false;
}
async function removeElement(el) {
await hideElement(el);
el.remove();
}