সরাসরি পর্বে যান
পর্ব ৩৮ · ৫৯-এর মধ্যে · মডিউল ৬

Transition — মসৃণ পরিবর্তন আর timing function

CSS Transitions — Smooth Changes & Timing Functions

Taught in Bangla, this lesson covers CSS transitions: when motion helps and when it harms, the four parts property, duration, timing function and delay, the shorthand and multiple transitions, which properties can animate including discrete values and display, easing from ease to cubic-bezier(), steps() and linear() springs, performance-friendly properties, transitionend, timing choices and touch screens.

এই পর্বে যা শিখবেন

  • মোশন কখন ব্যবহারকারীকে সাহায্য করে (feedback, ধারাবাহিকতা, মনোযোগ), আর কখন ক্ষতি করে
  • transition-এর চারটি অংশ, shorthand-এর নিয়ম, একাধিক transition আর delay-এর কৌশল
  • কোন property-র মাঝের মান হয়, কোনটি discrete; display ও height: auto-র পুরোনো সমস্যা আর @starting-style-এর নতুন সমাধান
  • cubic-bezier()-এর বক্ররেখা পড়া, steps() দিয়ে টাইপরাইটার, linear() দিয়ে স্প্রিং
  • rendering pipeline ধরে কোন transition সস্তা, কোনটি দামি; will-change, transitionend আর touch-পর্দার বাস্তবতা

১ · মোশন কেন — আর কখন সেটি ক্ষতি করে

মোবাইল ব্যাংকিং অ্যাপে “Send Money” বোতামে আঙুল রাখলে বোতামটি একটু গাঢ় হয়। নতুন পাতা হঠাৎ হাজির হয় না, পাশ থেকে সরে আসে। টিভিতে ক্রিকেটের স্কোরবোর্ড এক ঝটকায় বদলায় না — নতুন রান গড়িয়ে এসে বসে। এই ছোট ছোট নড়াচড়াই ব্যবহারকারীকে বলে দেয়, “আপনার কাজ পৌঁছেছে, আর এই জিনিসটি এখানে বদলেছে।”

CSS-এ এর সবচেয়ে সহজ হাতিয়ার transition. সাধারণ নিয়মে কোনো property-র মান বদলালে — যেমন hover-এ বোতামের রং — ব্রাউজার সাথে সাথে নতুন মান এঁকে দেয়। transition দিলে ব্রাউজার নির্দিষ্ট সময় ধরে মাঝের মানগুলো এঁকে দেখায়, যেন পুরোনো রং ধীরে ধীরে নতুন রঙে গলে যাচ্ছে। মাঝের এই মান হিসাব করার নাম interpolation.

👆 Feedback — সাড়া

বোতাম চাপলে, hover বা focus করলে সাথে সাথে একটি দৃশ্যমান সাড়া। ব্যবহারকারী বোঝেন, যন্ত্র তাঁর কথা শুনেছে — দোকানদারের মাথা নাড়ার মতো।

বোতাম, লিংক, checkbox

🔗 Continuity — ধারাবাহিকতা

মেনু কোথা থেকে খুলল, কার্ড কোথায় গেল — নড়াচড়া চোখকে পথ দেখায়, তাই পাতার অবস্থা বদলালেও মানুষ দিশা হারান না।

মেনু, ড্রয়ার, accordion

👀 Attention — মনোযোগ

নতুন একটি বার্তা আলতো করে ফুটে উঠলে চোখ সেদিকে যায়। কিন্তু সাবধান — সব কিছু নড়লে কোনোটিই আর আলাদা চোখে পড়ে না।

toast, ভুলের বার্তা

একই নড়াচড়া ক্ষতিও করে। প্রতিবার মেনু খুলতে এক সেকেন্ড অপেক্ষা করতে হলে কাজ ধীর লাগে। পাতার এক কোণে সারাক্ষণ কিছু নাচলে পড়ায় মন বসে না। আর সবচেয়ে গুরুতর ব্যাপারটি শরীরের।

নড়াচড়ায় অনেকে অসুস্থ বোধ করেন। কানের ভেতরের ভারসাম্য-ব্যবস্থার সমস্যা — vestibular disorder — আছে এমন মানুষের কাছে বড় জায়গা-বদল, zoom বা parallax মাথা ঘোরা, বমিভাব, এমনকি মাইগ্রেন ডেকে আনতে পারে; ADHD বা মাইগ্রেনের রোগীরাও কষ্ট পান। তাঁরা অপারেটিং সিস্টেমে “অ্যানিমেশন কমান” সেটিং চালু রাখেন, যা CSS-এ পৌঁছায় prefers-reduced-motion: reduce হিসেবে। পুরো আলোচনা পর্ব ৪৫-এ; এই পর্বের প্রতিটি উদাহরণে অন্তত একটি reduced-motion নিয়ম থাকবে।

নিয়মটি “অ্যানিমেশন বন্ধ” নয়, বরং “বড় নড়াচড়া কমাও।” রঙের হালকা বদল সাধারণত কারও ক্ষতি করে না; সরে যাওয়া, বড় হওয়া, ঘোরা — এগুলোই কমাতে হয়। এই ছাঁচটি মনে রাখুন:

.card {
  transition: transform 250ms ease-out, box-shadow 250ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .card {
    transition: box-shadow 250ms ease-out;   /* ছায়া বদলাক, সরে যাওয়া নয় */
  }
  .card:hover { transform: none; }
}

২ · transition-এর চারটি অংশ — কী, কতক্ষণ, কীভাবে, কখন

প্রথম যে কথাটি বোঝা দরকার: transition নিজে কোনো অবস্থা তৈরি করে না। “স্বাভাবিক অবস্থায় বোতাম সবুজ, hover-এ গাঢ় সবুজ” — এই দুই অবস্থা লেখে সাধারণ নিয়ম আর pseudo-class. transition শুধু বলে, এক অবস্থা থেকে আরেকটিতে যাওয়ার পথ কেমন হবে। ঢাকা থেকে চট্টগ্রাম — গন্তব্য আগেই ঠিক; transition ঠিক করে ট্রেনে যাবেন না বাসে, কতক্ষণে, আর কখন রওনা দেবেন।

Propertyপ্রশ্নউদাহরণডিফল্ট
transition-propertyকোন property মসৃণভাবে বদলাবে?background-colorall
transition-durationকতক্ষণ ধরে?200ms বা 0.2s0s
transition-timing-functionকোন গতির ধরনে?ease-outease
transition-delayকত পরে শুরু?50ms0s

ডিফল্ট duration 0s — তাই কিছু না লিখলে কোনো transition হয় না, মান সাথে সাথে বদলায়। আরেকটি বিষয় খেয়াল করুন: ব্রাউজার transition চালায় তখনই, যখন একটি element-এর computed মান বদলায় — hover, class যোগ, JavaScript দিয়ে style বদল, যেকোনো কারণে। ভেতরে ঠিক কী ঘটে, ধাপে ধাপে দেখুন:

1.btn {2  transform: translateY(0);3  transition: transform 300ms ease;4}5.btn:hover {6  transform: translateY(-8px);7}8/* JS: 'transitionend' ঘটনা */
ব্রাউজারের হিসাব
সময়
সময়ের অগ্রগতি
ease-এর পরে
transform
  1. শান্ত অবস্থা। বোতামের transform হলো translateY(0), আর তার নিয়মে লেখা আছে কীভাবে বদলাতে হবে: transform 300ms ease. শুধু লিখে রাখা — কিছুই চলছে না।
  2. মান বদলাল। মাউস বোতামের ওপর এল, :hover মিলল, নতুন computed মান translateY(-8px). ব্রাউজার পুরোনো ও নতুন মান তুলনা করে দেখে transform বদলেছে, আর নতুন অবস্থার transition নিয়মে এই property আছে। তাই সে একটি transition তৈরি করে: শুরু 0, শেষ -8px, সময় 300ms.
  3. প্রতি ফ্রেমে মাঝের মান। 100ms পরে সময়ের হিসাবে এক-তৃতীয়াংশ পথ। কিন্তু ease সময়ের এই অগ্রগতিকে বদলে দেয় — শুরুতে দ্রুত — তাই মান তখন পুরো পথের 57.6%, অর্থাৎ -4.6px. ব্রাউজার প্রতিটি ফ্রেমে (৬০Hz পর্দায় সেকেন্ডে প্রায় ৬০ বার) এই হিসাব করে আঁকে।
  4. শেষের দিকে ধীর। 200ms-এ সময়ের দুই-তৃতীয়াংশ, কিন্তু মান 92.5% — বোতাম প্রায় পৌঁছে গেছে, এখন আলতো করে থামছে। এই “শুরুতে জোরে, শেষে আস্তে” ভাবই ease-কে স্বাভাবিক দেখায়।
  5. শেষ — একটি ঘটনা। 300ms-এ মান ঠিক -8px. ব্রাউজার element-এর ওপর transitionend নামের একটি DOM ঘটনা পাঠায়; JavaScript চাইলে এটি শুনে পরের কাজ করতে পারে (অংশ ৮-এ দেখবেন)।
  6. মাঝপথে উল্টো দিক। ধরুন 200ms-এ মাউস সরিয়ে নিলেন। ব্রাউজার শূন্য থেকে শুরু করে না — যেখানে আছে (-7.4px) সেখান থেকেই ফেরে। আর spec-এর একটি নিয়মে ফেরার সময়ও ছোট হয়: যতটা পথ এসেছিল, প্রায় ততটুকু অনুপাতে, এখানে 300ms-এর 92.5% ≈ 278ms. না হলে অল্প একটু নড়া বোতামকে ফিরতেও পুরো 300ms লাগত।

এবার নিজে চালান। একটি “টাকা পাঠান” বোতাম — চারটি longhand আলাদা করে লেখা, যাতে প্রতিটির কাজ চোখে পড়ে। পাশে তুলনার জন্য transition ছাড়া একই বোতাম:

body { padding: 24px 12px; }
.btn {
  padding: 12px 22px; margin: 4px;
  font: 600 16px sans-serif; color: #fff;
  background-color: #0f766e;
  border: 0; border-radius: 8px; cursor: pointer;
  transition-property: background-color, transform;
  transition-duration: 200ms;
  transition-timing-function: ease;
  transition-delay: 0s;
}
.btn:hover, .btn:focus-visible {
  background-color: #134e4a;
  transform: translateY(-3px);
}
.btn.none { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .btn:hover, .btn:focus-visible { transform: none; }
}

দুটি সবুজ বোতাম পাশাপাশি। প্রথমটির ওপর মাউস রাখুন — রং মসৃণভাবে গাঢ় হয় আর বোতাম 3px উঠে যায়; সরালে আবার মসৃণভাবে ফেরে। দ্বিতীয়টি একই রং আর উচ্চতায় যায়, কিন্তু এক ঝটকায়। Tab চেপে কীবোর্ড দিয়ে এলেও প্রথমটি একইভাবে সাড়া দেয়, কারণ :focus-visible-এ একই নিয়ম আছে। “অ্যানিমেশন কমান” চালু থাকলে রং বদলাবে, কিন্তু বোতাম উঠবে না। চেষ্টা করুন: (১) 200ms-কে 2s করুন — মাঝপথে মাউস সরিয়ে দেখুন, বোতাম সেখান থেকেই ফেরে। (২) transition-property থেকে transform মুছে দিন — রং মসৃণ থাকবে, কিন্তু ওঠা হবে এক লাফে।

কোথায় লিখবেন

transition লিখুন মূল নিয়মে (.btn), শুধু :hover-এ নয়। ব্রাউজার ব্যবহার করে নতুন অবস্থার transition নিয়ম। :hover-এ লিখলে মাউস আসার সময় মসৃণ হবে, কিন্তু চলে যাওয়ার সময় নতুন অবস্থা হলো সাধারণ .btn, যেখানে transition নেই — তাই ফেরা হবে এক লাফে। এই নিয়মটি পরের অংশে একটি কৌশলেও কাজে লাগবে।

৩ · shorthand, একাধিক transition আর delay-এর কৌশল

চারটি longhand বারবার লেখা ক্লান্তিকর, তাই আছে shorthand transition. একটি property-র জন্য একটি “দল”, আর একাধিক property হলে কমা দিয়ে একাধিক দল:

/* property | duration | timing-function | delay */
.card {
  transition: transform 250ms ease-out,
              box-shadow 250ms ease-out 50ms;
}
/* একই কথা longhand-এ */
.card {
  transition-property: transform, box-shadow;
  transition-duration: 250ms, 250ms;
  transition-timing-function: ease-out, ease-out;
  transition-delay: 0s, 50ms;
}

তিনটি নিয়ম মনে রাখলেই shorthand পড়া সহজ:

  • প্রথম সময়-মান duration, দ্বিতীয়টি delay. বাকি অংশের ক্রম যেকোনো হতে পারে, কিন্তু দুটি সময়ের ক্রম বদলায় না। transition: opacity 1s 300ms মানে এক সেকেন্ড ধরে, 300ms দেরিতে।
  • shorthand সব longhand-কে নতুন করে বসায়। যা লেখেননি, তা ডিফল্টে ফেরে। তাই আগে transition-delay: 1s লিখে পরে transition: opacity 200ms লিখলে delay আবার 0s হয়ে যায় — cascade-এর সাধারণ নিয়ম।
  • longhand-এর তালিকা ছোট-বড় হলে ছোটটি পুনরাবৃত্ত হয়। transition-property: transform, opacity, color আর transition-duration: 200ms থাকলে তিনটিই 200ms পায়।

transition: all 0.3s খুব প্রচলিত, কিন্তু এড়িয়ে চলুন। এক, যে property বদলাতে চাননি, সেটিও অ্যানিমেট হবে — পরে কেউ width বা padding বদলালে হঠাৎ পুরো লেআউট নড়তে শুরু করবে। দুই, ব্রাউজারকে প্রতিটি বদলানো property-র জন্য transition তৈরি করতে হয়, যা দামি হতে পারে। যা বদলাবে, শুধু তার নাম লিখুন।

এবার বাস্তবের সবচেয়ে প্রচলিত নকশা — একটি অনলাইন দোকানের পণ্য-কার্ড, hover-এ যা একটু ভেসে ওঠে। ছায়া একটু দেরিতে গাঢ় হয়, তাই মনে হয় কার্ডটি সত্যিই টেবিল থেকে উঠছে:

<style>
  body { font-family: sans-serif; background: #f1f5f9; padding: 16px; }
  .card {
    max-width: 260px; padding: 14px 16px; border-radius: 12px;
    background: #fff; box-shadow: 0 1px 3px rgb(0 0 0 / 0.15);
    transition: transform 250ms ease-out, box-shadow 250ms ease-out 50ms;
  }
  @media (hover: hover) {
    .card:hover { transform: translateY(-6px);
      box-shadow: 0 14px 24px rgb(0 0 0 / 0.18); }
  }
  .card:has(a:focus-visible) { transform: translateY(-6px);
    box-shadow: 0 14px 24px rgb(0 0 0 / 0.18); }
  @media (prefers-reduced-motion: reduce) {
    .card:hover, .card:has(a:focus-visible) { transform: none; }
  }
</style>
<article class="card">
  <h3>জামদানি শাড়ি</h3>
  <p>৳ ৮,৫০০ · ঢাকায় ফ্রি ডেলিভারি</p>
  <a href="#buy">কিনুন</a>
</article>

হালকা ধূসর পটে একটি সাদা কার্ড। মাউস রাখুন — কার্ড 6px ওপরে ওঠে, আর ছায়া 50ms পরে শুরু হয়ে নিচে ছড়িয়ে পড়ে। Tab চেপে “কিনুন” লিংকে focus আনলেও কার্ড ওঠে — :has() দিয়ে কীবোর্ড ব্যবহারকারীর জন্য একই সাড়া। hover-এর নিয়মটি @media (hover: hover)-এর ভেতরে, তাই ফোনের touch-পর্দায় ট্যাপের পরে কার্ড উঠে আটকে থাকে না (অংশ ৮)। চেষ্টা করুন: (১) 50ms মুছে দিন — পার্থক্য সূক্ষ্ম, কিন্তু ছায়া আর ওঠা একসাথে শুরু হওয়ায় “ভাসা”-র অনুভূতি একটু কমে। (২) পুরো transition লাইনটিকে transition: all 250ms করুন — এখনই কোনো পার্থক্য নেই, আর এটাই বিপদ: পরে কেউ hover-এ padding যোগ করলে সেটিও নড়বে।

delay দিয়ে দুই দিকে দুই আচরণ

অংশ ২-এর নিয়মটি মনে করুন — ব্রাউজার নেয় নতুন অবস্থার transition. এতে একটি সুন্দর কৌশল সম্ভব। ড্রপডাউন মেনু থেকে মাউস এক মুহূর্তের জন্য পিছলে গেলেই মেনু বন্ধ হয়ে যাওয়া খুব বিরক্তিকর। সমাধান: খোলার সময় delay নেই, বন্ধের সময় একটু delay:

.submenu {
  opacity: 0; visibility: hidden;
  transition: opacity 150ms ease-in, visibility 0s linear 150ms;
  transition-delay: 300ms, 450ms;   /* বন্ধের আগে 300ms সুযোগ, fade শেষে লুকানো */
}
.nav-item:hover .submenu,
.nav-item:focus-within .submenu {
  opacity: 1; visibility: visible;
  transition-delay: 0s;             /* খুলবে সাথে সাথে */
}

hover-এ নতুন অবস্থা .nav-item:hover .submenu, যার delay 0s — মেনু সাথে সাথে খোলে। মাউস সরলে নতুন অবস্থা সাধারণ .submenu, যার delay 300ms — এই সময়ের মধ্যে মাউস ফিরে এলে মেনু বন্ধই হয় না। visibility এখানে একটি বিশেষ property: এটি discrete হলেও transition-এ এমনভাবে বদলায় যে পুরো সময় ধরে element দেখা যায়, আর লুকানো অবস্থায় সেটি ক্লিক বা Tab-ও পায় না। পরের অংশে এই “discrete” কথাটির পুরো মানে দেখবেন।

৪ · কোন property animate হয় — মাঝের মান, discrete আর display-এর পুরোনো সমস্যা

transition কাজ করে কেবল তখন, যখন ব্রাউজার দুই মানের মাঝামাঝি একটি মান হিসাব করতে পারে। 10px আর 20px-এর মাঝে 15px আছে; লাল আর নীলের মাঝে বেগুনি আছে। কিন্তু dashed আর solid বর্ডারের মাঝে “অর্ধেক dashed” বলে কিছু নেই; Arial আর Georgia-র মাঝেও কোনো ফন্ট নেই। প্রথম দলকে বলে interpolable, দ্বিতীয় দলকে discrete.

ধরনউদাহরণtransition-এ কী হয়
সংখ্যা, দৈর্ঘ্য, শতাংশopacity, width, padding, font-sizeমসৃণ
রংcolor, background-color, border-colorমসৃণ (রঙের জগতে মাঝের রং)
জটিল মানtransform, box-shadow, filterমসৃণ, যদি দুই মানের গঠন মেলে
keyworddisplay, border-style, font-family, position, gradient-এর background-imageডিফল্টে একেবারেই নয় — সাথে সাথে বদল
auto-র মতো মাপheight: auto, width: fit-contentঐতিহ্যগতভাবে নয়; নতুন opt-in দরকার

discrete property ডিফল্টে transition-এ যোগই দেয় না — মান সাথে সাথে বদলায়। নতুন property transition-behavior: allow-discrete বললে ব্রাউজার এদেরও transition-এ নেয়, তবে মাঝের মান তো নেই, তাই সময়ের ঠিক অর্ধেকে এক লাফে বদলায়। চোখে দেখুন:

<style>
  body { font-family: sans-serif; }
  .box {
    max-width: 280px; padding: 12px; margin: 8px 0; border-radius: 10px;
    background: #e0f2fe; border: 4px dashed #0369a1;
    transition: background-color 2s, border-style 2s;  /* শেখার জন্য ধীর */
  }
  .b2 { transition-behavior: allow-discrete; }
  #on:checked ~ .box { background-color: #fde68a; border-style: solid; }
  @media (prefers-reduced-motion: reduce) {
    .box { transition-duration: 0.01s; }
  }
</style>
<input type="checkbox" id="on"> <label for="on">অবস্থা বদলান</label>
<div class="box">সাধারণ: border-style সাথে সাথে বদলায়</div>
<div class="box b2">allow-discrete: border-style বদলায় ঠিক মাঝপথে</div>

দুটি হালকা নীল বাক্স, দুটির বর্ডারই ড্যাশ-দাগের। checkbox চাপুন — দুটিরই পটভূমি দুই সেকেন্ড ধরে মসৃণভাবে হলুদ হয়। কিন্তু প্রথম বাক্সের বর্ডার চাপার সাথে সাথেই অখণ্ড হয়ে যায়, আর দ্বিতীয়টির বর্ডার বদলায় ঠিক এক সেকেন্ড পরে — মাঝপথে, এক লাফে। চেষ্টা করুন: (১) .b2 নিয়মটি মুছে দিন — দুটি বাক্সই একরকম আচরণ করবে। (২) .b2-কে .box-এর আগে সরিয়ে দেখুন — .box-এর shorthand transition পরে আসায় transition-behavior-কে আবার normal করে দেয়, তাই allow-discrete হারিয়ে যায়।

display: none — বহু বছরের পুরোনো মাথাব্যথা

মেনু বা popup দেখানো-লুকানোর স্বাভাবিক উপায় display: none ↔ display: block. কিন্তু এখানে transition দুই দিক থেকেই আটকে যেত। দেখানোর সময়: display: none element-এর কোনো বাক্সই থাকে না, তার opacity কত ছিল — ব্রাউজারের কাছে কোনো “আগের মান” নেই, তাই শুরু করার জায়গাই নেই। লুকানোর সময়: display: none হওয়ামাত্র element উধাও; ধীরে মিলিয়ে যাওয়ার সুযোগই আসে না।

বহু বছর এর বিকল্প ছিল opacity: 0 + visibility: hidden + pointer-events: none-এর জোট, বা JavaScript-এ class যোগ করে setTimeout দিয়ে অপেক্ষা। এখন দুটি নতুন হাতিয়ার এই দুই দিকের সমস্যা মেটায়:

  • allow-discrete + display: display-এর জন্য ব্রাউজার একটি বিশেষ নিয়ম মানে — লুকানোর সময় none হয় একেবারে শেষে, দেখানোর সময় block হয় একেবারে শুরুতে. তাই পুরো অ্যানিমেশনের সময় element দেখা যায়।
  • @starting-style: “প্রথমবার আঁকার আগে ধরে নাও মান এই ছিল।” এতে দেখানোর সময় ব্রাউজার একটি আগের মান পায় — যেমন opacity: 0 — আর সেখান থেকে transition শুরু করে।

এবার পর্ব ১১-এর popover দিয়ে একটি মেনু, যা খোলার সময় ওপর থেকে নেমে ফুটে ওঠে, আর বন্ধের সময় মিলিয়ে যায়:

<style>
  body { font-family: sans-serif; min-height: 220px; }
  #menu {
    inset: auto; top: 52px; left: 12px; margin: 0; padding: 6px;
    border: 1px solid #94a3b8; border-radius: 10px;
    --shift: -8px; opacity: 0; transform: translateY(var(--shift));
    transition: opacity 200ms ease-out, transform 200ms ease-out;  /* পুরোনো ব্রাউজার */
    transition: opacity 200ms ease-out, transform 200ms ease-out,
      display 200ms allow-discrete, overlay 200ms allow-discrete;
  }
  #menu:popover-open { opacity: 1; transform: none; }
  @starting-style {
    #menu:popover-open { opacity: 0; transform: translateY(var(--shift)); }
  }
  #menu button { display: block; width: 100%; margin: 2px 0; padding: 6px 14px; }
  @media (prefers-reduced-motion: reduce) { #menu { --shift: 0px; } }
</style>
<button popovertarget="menu">মেনু ▾</button>
<div id="menu" popover>
  <button popovertarget="menu" popovertargetaction="hide">আমার অর্ডার</button>
  <button popovertarget="menu" popovertargetaction="hide">ঠিকানা বদলান</button>
</div>

শুরুতে শুধু “মেনু ▾” বোতাম। চাপুন — Edge-এ (Chromium) মেনুটি 8px ওপর থেকে নেমে আসতে আসতে ফুটে ওঠে। বাইরে ক্লিক করুন বা Esc চাপুন — একইভাবে ওপরে উঠে মিলিয়ে যায়, হঠাৎ উধাও হয় না। “অ্যানিমেশন কমান” চালু থাকলে শুধু fade হবে, সরে আসা নয়। খেয়াল করুন, transition দুবার লেখা: যে পুরোনো ব্রাউজার allow-discrete চেনে না, সে দ্বিতীয় লাইনটিকে অবৈধ ধরে পুরোটা বাদ দেয় আর প্রথমটি রাখে — তখন মেনু খোলে-বন্ধ হয় fade ছাড়াই, কিন্তু কাজ ঠিক থাকে। চেষ্টা করুন: (১) পুরো @starting-style ব্লকটি মুছে দিন — খোলার অ্যানিমেশন চলে যাবে, মেনু এক ঝটকায় হাজির হবে, কিন্তু বন্ধের fade থেকে যাবে। (২) দ্বিতীয় transition থেকে display 200ms allow-discrete অংশটি মুছুন — এবার বন্ধ হবে সাথে সাথে।

সাপোর্টের সৎ চিত্র: @starting-style আর transition-behavior ২০২৪ সালে Chrome, Edge, Safari আর Firefox-এর নতুন সংস্করণে এসেছে — Baseline Newly available. কিন্তু overlay property — যা বন্ধের অ্যানিমেশন শেষ হওয়া পর্যন্ত popover বা dialog-কে top layer-এ ধরে রাখে — এখনো মূলত Chromium-এ; যেখানে নেই, সেখানে transition-এর তালিকায় অচেনা নামটি শুধু উপেক্ষিত হয়, ক্ষতি নেই। আর ব্রাউজারভেদে display-এর বন্ধের অ্যানিমেশনের খুঁটিনাটি একটু আলাদা হতে পারে, তাই অন্তত দুটি ইঞ্জিনে নিজে পরীক্ষা করুন।

height: auto — তিনটি পথ

accordion বা FAQ-এর উত্তর মসৃণভাবে খুলতে চাইলে সমস্যা auto: 0 আর auto-র মাঝামাঝি ব্রাউজার হিসাব করত না। পর্ব ৩৭-এ দেখেছেন interpolate-size: allow-keywords আর calc-size() — সবচেয়ে পরিষ্কার সমাধান, কিন্তু এখনো শুধু Chromium-এ, not yet Baseline. তাই আজকের বাস্তব পথ:

  • সবখানে চলে: grid-এর সারি 0fr থেকে 1fr-এ transition — grid track-এর মাপ interpolable, আর এটি সব আধুনিক ব্রাউজারে কাজ করে।
  • বাড়তি সুবিধা হিসেবে: তার ওপর interpolate-size, যাতে Chromium-এ সরাসরি height: auto.
  • এড়িয়ে চলুন: max-height: 1000px-এর কৌশল — আসল উচ্চতা ছোট হলে অ্যানিমেশনের সময় ভুল হয়, অদ্ভুত দেরি দেখা যায়।
.answer-wrap {
  display: grid;
  grid-template-rows: 0fr;               /* বন্ধ */
  transition: grid-template-rows 300ms ease;
}
.faq.open .answer-wrap { grid-template-rows: 1fr; }   /* খোলা */
.answer-wrap > div { overflow: hidden; }  /* ভেতরের একটিমাত্র child */

৫ · Timing function — গতির চরিত্র: ease থেকে cubic-bezier()

একই 300ms, একই পথ — তবু একটি বোতাম “জীবন্ত” লাগে, আরেকটি “যন্ত্রের মতো।” পার্থক্য গড়ে timing function (easing). এটি একটি ছোট গাণিতিক যন্ত্র: ইনপুট “সময়ের কত অংশ গেছে” (0 থেকে 1), আউটপুট “পথের কত অংশ পেরিয়েছে” (সাধারণত 0 থেকে 1).

ঢাকার মেট্রোরেলের কথা ভাবুন। স্টেশন ছাড়ার সময় ট্রেন হঠাৎ পূর্ণ গতিতে ছোটে না — আস্তে শুরু করে, গতি বাড়ায়, পরের স্টেশনের আগে আবার আস্তে থামে। হঠাৎ শুরু বা হঠাৎ থামা হলে যাত্রীরা হুমড়ি খেয়ে পড়তেন। বাস্তব জগতে ভরযুক্ত কোনো জিনিস এক মুহূর্তে গতি বদলায় না, তাই চোখও এমন গতিকেই স্বাভাবিক মনে করে। linear মানে ট্রেন যেন স্টেশনে পূর্ণ গতিতে ঢুকে দেয়ালে ধাক্কা খেয়ে থামল।

keywordসমানচরিত্রকোথায় মানায়
ease (ডিফল্ট)cubic-bezier(0.25, 0.1, 0.25, 1)দ্রুত শুরু, লম্বা ধীর শেষসাধারণ রং-বদল, hover
linearcubic-bezier(0, 0, 1, 1)সমান গতিরং বা opacity-র ঘূর্ণন, progress bar
ease-incubic-bezier(0.42, 0, 1, 1)আস্তে শুরু, জোরে শেষপর্দা থেকে বেরিয়ে যাওয়া
ease-outcubic-bezier(0, 0, 0.58, 1)জোরে শুরু, আস্তে থামাপর্দায় ঢোকা, মেনু খোলা
ease-in-outcubic-bezier(0.42, 0, 0.58, 1)দুই দিকেই আস্তেপর্দার ভেতরে জায়গা বদল

“ঢুকতে ease-out, বেরোতে ease-in” — এর পেছনের যুক্তি: নতুন জিনিস দ্রুত হাজির হলে ব্যবহারকারী সাথে সাথে সাড়া পান, আর শেষের ধীরগতি চোখকে তার থামার জায়গা দেখায়। বেরিয়ে যাওয়া জিনিসের থামার জায়গা দেখার দরকার নেই, তাই সেটি ধীরে শুরু করে দ্রুত চলে যায়।

সবগুলো keyword আসলে একটি সাধারণ যন্ত্রের রূপ — cubic-bezier(x1, y1, x2, y2). চারটি সংখ্যা মানে দুটি “টানার বিন্দু” (control point). বক্ররেখা শুরু হয় (0,0) থেকে, শেষ হয় (1,1)-এ, আর মাঝের দুটি বিন্দু তাকে নিজের দিকে টানে — চুম্বকের মতো, ছোঁয় না। ছকে আঁকা দেখুন:

সময় → অগ্রগতি → 0 1 1 শেষ মান পেরিয়ে যায় শুরু (0,0) শেষ (1,1)
  1. ছকটি পড়া। বাঁ থেকে ডানে সময় (0 = শুরু, 1 = শেষ), নিচ থেকে ওপরে অগ্রগতি। বক্ররেখা যত খাড়া, সেই মুহূর্তে গতি তত বেশি; সমতল মানে প্রায় থেমে আছে। linear একটি সোজা কর্ণ — সব জায়গায় সমান খাড়া, তাই সমান গতি।
  2. ease-in. প্রথম টানার বিন্দু (লাল) (0.42, 0) — নিচের দাগ বরাবর ডানে টানা। তাই শুরুতে রেখা প্রায় সমতল (আস্তে), পরে খাড়া হয়ে দ্রুত শেষ। দ্বিতীয় বিন্দু (1, 1) শেষ বিন্দুর ওপরেই বসে আছে (লাল বৃত্ত)।
  3. ease-out. উল্টো ছবি: প্রথম বিন্দু শুরুতেই (লাল বৃত্ত), দ্বিতীয়টি (0.58, 1) — ওপরের দাগ বরাবর বাঁয়ে টানা। শুরুতে খাড়া (দ্রুত সাড়া), শেষে সমতল (আলতো থামা)।
  4. ease-in-out. দুটি টান একসাথে — দুই প্রান্তেই সমতল, মাঝে খাড়া। রেখাটি মাঝ বরাবর প্রতিসম, ইংরেজি S অক্ষরের মতো। মেট্রোরেলের যাত্রা ঠিক এমন।
  5. ease — ডিফল্ট। প্রথম বিন্দু (0.25, 0.1) সামান্য ওপরে, দ্বিতীয়টি (0.25, 1) অনেক বাঁয়ে। তাই শুরু তুলনামূলক দ্রুত, আর শেষের ধীর অংশ লম্বা — অর্ধেক সময়েই পথের প্রায় 80% শেষ। অংশ ২-এর বোতাম এই কারণেই 100ms-এ 57.6% পৌঁছেছিল।
  6. শেষ পেরিয়ে ফেরা — overshoot. x-এর মান 0 থেকে 1-এর মধ্যে থাকতেই হবে (সময় পেছনে যায় না), কিন্তু y-এর কোনো সীমা নেই। প্রথম বিন্দু y = 1.56 — ছকের অনেক ওপরে। ফলে মান লক্ষ্য পেরিয়ে প্রায় 110%-এ যায়, তারপর ফিরে এসে থামে। বোতাম যেন একটু “লাফিয়ে” বসে। মনে রাখুন, opacity-র মতো 0–1-এর property-তে বাড়তি মান সীমায় আটকে যায়।

এবার সবগুলো একসাথে দৌড়াতে দিন। ছয়টি বল, একই দূরত্ব, একই 2 সেকেন্ড — শুধু timing function আলাদা। ডানের পাতলা দাগটি শেষ-রেখা:

<style>
  body { font: 14px sans-serif; }
  .row { display: grid; grid-template-columns: 8.5em 1fr; align-items: center; gap: 8px; margin: 6px 0; }
  .track { container-type: inline-size; height: 26px; border-radius: 13px;
    background: linear-gradient(90deg, #f1f5f9 85%, #94a3b8 85% calc(85% + 2px), #f1f5f9 0); }
  .dot { width: 22px; height: 22px; margin: 2px; border-radius: 50%; background: #0f766e;
    transition: transform 2s var(--e); }
  #go:checked ~ .row .dot { transform: translateX(calc(85cqw - 24px)); }
  @media (prefers-reduced-motion: reduce) { .dot { transition-duration: 0.01s; } }
</style>
<input type="checkbox" id="go"> <label for="go">দৌড় শুরু / ফেরত</label>
<div class="row"><code>linear</code><div class="track"><div class="dot" style="--e: linear"></div></div></div>
<div class="row"><code>ease</code><div class="track"><div class="dot" style="--e: ease"></div></div></div>
<div class="row"><code>ease-in</code><div class="track"><div class="dot" style="--e: ease-in"></div></div></div>
<div class="row"><code>ease-out</code><div class="track"><div class="dot" style="--e: ease-out"></div></div></div>
<div class="row"><code>ease-in-out</code><div class="track"><div class="dot" style="--e: ease-in-out"></div></div></div>
<div class="row"><code>overshoot</code><div class="track"><div class="dot" style="--e: cubic-bezier(.34,1.56,.64,1)"></div></div></div>

ছয়টি ধূসর ট্র্যাকের বাঁ প্রান্তে ছয়টি সবুজ বল। “দৌড় শুরু” checkbox চাপুন — সবাই একসাথে রওনা দেয় আর ঠিক 2 সেকেন্ডে একসাথে শেষ-রেখায় পৌঁছায়, কিন্তু পথে কে কখন এগিয়ে, তা একদম আলাদা। ease-out আর ease শুরুতেই এগিয়ে যায়, ease-in প্রথম দিকে প্রায় নড়েই না, আর overshoot শেষ-রেখা পেরিয়ে একটু ফিরে আসে। আবার চাপলে উল্টো দিকে একই দৌড়। চেষ্টা করুন: (১) শেষ সারির মানটিকে steps(4) করুন — বলটি চার লাফে যাবে। (২) সব 2s-কে 300ms করুন — বাস্তবের মাপে পার্থক্য সূক্ষ্ম, কিন্তু অনুভূতিতে স্পষ্ট।

নিজের বক্ররেখা হাতে বানাতে হবে না। Edge বা Chrome-এর DevTools-এ Styles প্যানেলে যেকোনো timing function-এর পাশে ছোট বাঁকা-রেখার আইকনে চাপলে একটি easing editor খোলে — বিন্দু টেনে দেখুন, সংখ্যা নিজেই বদলাবে। Firefox-এও একই রকম সুবিধা আছে (পর্ব ৪৭)।

৬ · steps() আর linear() — ধাপে ধাপে লাফ আর স্প্রিংয়ের দোল

কখনো মসৃণতা চাই না, চাই স্পষ্ট ধাপ — যেমন ঘড়ির কাঁটার টিক টিক, বা পুরোনো টাইপরাইটারে একটি একটি করে অক্ষর। এর জন্য steps(n, অবস্থান): পুরো পথকে n-টি সমান ধাপে ভাগ করে, প্রতিটি ধাপে এক লাফ। দ্বিতীয় অংশটি বলে লাফ কখন:

  • jump-end বা end (ডিফল্ট) — প্রতিটি ধাপের শেষে লাফ; শুরুতে মান থাকে আগের জায়গায়।
  • jump-start বা start — প্রতিটি ধাপের শুরুতেই লাফ; প্রথম মুহূর্তেই প্রথম ধাপ।
  • jump-none — শুরু আর শেষ দুই মানই কিছুক্ষণ দেখা যায়; jump-both — দুই প্রান্তেই লাফ।

টাইপরাইটারের কৌশল সহজ: একটি monospace লেখার width 0 থেকে অক্ষর-সংখ্যার সমান ch-এ নিয়ে যান, আর ধাপ দিন অক্ষর-সংখ্যার সমান। ch একক হলো “0” অক্ষরের প্রস্থ (পর্ব ২০), আর monospace ফন্টে সব অক্ষরের প্রস্থ সমান:

<style>
  body { font-family: sans-serif; }
  .type {
    width: 0; overflow: hidden; white-space: nowrap;
    font: 22px/1.5 monospace; border-right: 2px solid #0f766e;
    transition: width 1.3s steps(13, end);
  }
  .smooth { transition-timing-function: linear; }
  #t:checked ~ .type { width: 13ch; }
  @media (prefers-reduced-motion: reduce) { .type { transition: none; } }
</style>
<input type="checkbox" id="t"> <label for="t">লিখুন / মুছুন</label>
<p class="type">Hello, Dhaka!</p>
<p class="type smooth">Hello, Dhaka!</p>

শুরুতে দুটি সরু সবুজ দাগ — লেখা লুকানো। checkbox চাপুন — প্রথম সারিতে “Hello, Dhaka!” একটি একটি পুরো অক্ষর করে ফুটে ওঠে, টাইপরাইটারের মতো; দ্বিতীয় সারিতে linear, তাই লেখা মসৃণভাবে বেরোয়, আর মাঝপথে অক্ষর অর্ধেক কাটা দেখা যায়। লেখায় স্পেস আর যতিচিহ্নসহ ১৩টি অক্ষর, তাই 13ch আর steps(13). চেষ্টা করুন: steps(13, end)-কে steps(5, end) করুন — এবার একসাথে দুই-তিনটি অক্ষর লাফিয়ে আসবে। সতর্কতা: বাংলা লেখায় এই কৌশল চলে না — যুক্তাক্ষর আর কার-চিহ্ন মিলে একটি দৃশ্যমান অক্ষরের প্রস্থ সমান থাকে না, monospace বাংলা ফন্টেও নয়।

steps()-এর আরেকটি ক্লাসিক ব্যবহার sprite sheet: একটি ছবিতে পাশাপাশি ৮টি ফ্রেম আঁকা, যেমন একটি দৌড়ানো চরিত্র — সিনেমার ফিল্ম-রিলের মতো। background-position-কে steps(8) দিয়ে সরালে প্রতিবার ঠিক একটি করে ফ্রেম জানালায় আসে, মাঝখানে দুই ফ্রেমের আধাআধি কখনো দেখা যায় না। এটি সাধারণত বারবার চলা @keyframes animation-এ লাগে — পরের পর্বে।

linear() — যেকোনো আকারের গতি, স্প্রিংসহ

cubic-bezier()-এর একটি সীমা আছে: দুটি টানার বিন্দু দিয়ে একবারের বেশি দোল আঁকা যায় না। আসল স্প্রিং বা লাফানো বল কয়েকবার এদিক-ওদিক দোলে। নতুন linear() ফাংশনের নাম শুনে ভুল বুঝবেন না — এটি linear keyword নয়। এতে আপনি বিন্দুর একটি তালিকা দেন, আর ব্রাউজার বিন্দুগুলোকে সোজা দাগে জোড়ে। বিন্দু যথেষ্ট হলে যেকোনো বক্ররেখা — স্প্রিং, বাউন্স — প্রায় নিখুঁতভাবে আঁকা যায়:

body { font-family: sans-serif; padding: 30px 24px; }
.cart {
  padding: 12px 20px; font-size: 16px; color: #fff;
  background: #0f766e; border: 0; border-radius: 999px;
  cursor: pointer;
  transition: transform 600ms ease-out;   /* পুরোনো ব্রাউজার */
  transition: transform 600ms
    linear(0, 0.7 20%, 1.12 35%, 0.96 55%, 1.02 72%, 1);
}
.cart:hover, .cart:focus-visible { transform: scale(1.15); }
@media (prefers-reduced-motion: reduce) {
  .cart:hover, .cart:focus-visible { transform: none; }
}
.note { display: none; color: #b91c1c; }
@supports not (transition-timing-function: linear(0, 1)) {
  .note { display: block; }
}

একটি গোলাকার সবুজ বোতাম। মাউস রাখুন বা Tab দিয়ে focus আনুন — বোতাম বড় হয়ে একটু বেশি ফুলে যায় (112%), তারপর ছোট হয়, আবার সামান্য বড় হয়ে থিতু হয় — স্প্রিংয়ের মতো দোল। তালিকার 1.12 35% মানে “সময়ের 35%-এ পথের 112%”. লাল নোট দেখা যাবে শুধু linear() না-চেনা ব্রাউজারে, যেখানে প্রথম transition লাইনটি কাজ করবে। চেষ্টা করুন: (১) 1.12-কে 1.3 করুন — দোল অনেক বড় আর খেলনার মতো হবে; বাস্তবে সংযম ভালো। (২) linear(0, 1) লিখুন — দুটি মাত্র বিন্দু, অর্থাৎ ঠিক linear keyword-এর মতো সমান গতি।

হাতে এত বিন্দু লেখার দরকার নেই — অনলাইনে “linear() easing generator” জাতীয় টুলে স্প্রিংয়ের শক্তি আর ঘর্ষণ বসিয়ে তালিকাটি বানিয়ে নেওয়া যায়। সাপোর্ট: linear() ২০২৩ সালের শেষ নাগাদ চারটি প্রধান ব্রাউজারে এসে Baseline Newly available হয়েছে; ৩০ মাসের হিসাবে ২০২৬-এর মাঝামাঝি নাগাদ এটি Widely available হওয়ার কথা — MDN-এ একবার মিলিয়ে নিন। তবুও উপরের মতো আগে একটি সাধারণ transition লিখে রাখা সস্তা বিমা।

৭ · পারফরম্যান্স — কোন property সরালে ফোন হাঁপায় না

পর্ব ১-এ দেখেছেন, HTML আর CSS থেকে পর্দার ছবি তৈরি হয় কয়েকটি ধাপে: DOM আর CSSOM মিলে render tree, তারপর style হিসাব, layout (কোন বাক্স কোথায়, কত বড়), paint (রং, লেখা, ছায়া আঁকা), আর শেষে compositing (আলাদা স্তরগুলো GPU দিয়ে জোড়া)। transition চলার সময় এই কাজ প্রতিটি ফ্রেমে আবার করতে হতে পারে — আর কতটুকু করতে হবে, তা নির্ভর করে কোন property বদলাচ্ছেন তার ওপর:

width · top · marginজ্যামিতি বদলায়
background-color · box-shadowচেহারা বদলায়
transform · opacityশুধু ছবি সরে বা স্বচ্ছ হয়
Styleকোন নিয়ম, কোন মান
Layoutকোথায়, কত বড়
Paintপিক্সেল আঁকা
Compositeস্তর জোড়া (GPU)
  1. প্রথমবার আঁকা। পাতা খোলার সময় ব্রাউজার সব ধাপ একবার করে (চিহ্নিত চারটি ধাপ)। এখানে কোনো সমস্যা নেই — প্রশ্ন হলো transition চলার সময়, প্রতি ফ্রেমে, এর কতটুকু আবার করতে হয়।
  2. width, top, margin-এর transition. বাক্সের মাপ বা জায়গা বদলালে layout আবার হিসাব করতে হয় — আর একটি বাক্স চওড়া হলে পাশের লেখা, নিচের কার্ড সবাই সরে, তাদেরও হিসাব লাগে। তারপর paint, তারপর composite — প্রতি ফ্রেমে চারটি ধাপ। কম দামের ফোনে এখানেই অ্যানিমেশন ঝাঁকুনি দেয়, যাকে বলে jank.
  3. background-color, box-shadow. জ্যামিতি বদলায় না, তাই layout বাদ। কিন্তু পিক্সেল নতুন করে আঁকতে হয় — paint. বড় ঝাপসা ছায়া আঁকা বেশ দামি, তাই বড় এলাকায় ছায়ার transition সাবধানে।
  4. transform আর opacity. এরা আঁকা ছবিটিকেই সরায়, বড়-ছোট করে বা স্বচ্ছ করে — নতুন করে আঁকা লাগে না। element-টি নিজের compositor layer-এ থাকলে GPU শুধু ছবিটি অন্য জায়গায় জোড়ে। আরও ভালো: Chromium, Firefox আর Safari এমন transition প্রায়ই আলাদা compositor thread-এ চালায়, তাই JavaScript-এ main thread আটকে থাকলেও নড়াচড়া মসৃণ থাকে।
  5. will-change: transform. ব্রাউজারকে আগাম জানানো: “এটি শিগগিরই সরবে।” সে আগে থেকেই আলাদা স্তর বানিয়ে রাখে, তাই প্রথম ফ্রেমে দেরি হয় না। কিন্তু প্রতিটি স্তর GPU-র মেমরি খায়; শত শত element-এ দিলে ফোন উল্টো ধীর হয়ে যায়। এটি ওষুধ, ভিটামিন নয় — সমস্যা মেপে পেলে তবেই।
  6. এক ফ্রেমের বাজেট। ৬০Hz পর্দায় প্রতি সেকেন্ডে ৬০টি ছবি, অর্থাৎ এক ফ্রেমে প্রায় 16.7ms; ১২০Hz ফোনে মাত্র 8.3ms. এর মধ্যে ব্রাউজারের নিজের কাজও আছে। প্রতি ফ্রেমে layout লাগে এমন transition এই বাজেট ভাঙে, তখন ফ্রেম বাদ পড়ে আর নড়াচড়া কাঁপে।

তাই বাস্তবের নিয়ম: সরাতে চাইলে top/left/margin নয়, transform: translate(); বড়-ছোট করতে width/height নয়, transform: scale(); দেখানো-লুকাতে opacity. একটি বাড়তি কৌশল: ছায়ার transition দামি হলে ছায়াটি একটি ::after pseudo-element-এ আগেই এঁকে রাখুন, আর শুধু তার opacity বদলান।

/* ✗ প্রতি ফ্রেমে layout */
.drawer { left: -300px; transition: left 300ms ease-out; }
.drawer.open { left: 0; }

/* ✓ শুধু compositor */
.drawer { transform: translateX(-100%); transition: transform 300ms ease-out; }
.drawer.open { transform: none; }

/* ✓ দামি ছায়ার বদলে আগে-আঁকা ছায়ার opacity */
.card { position: relative; }
.card::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: 0 14px 24px rgb(0 0 0 / 0.18);
  opacity: 0; transition: opacity 250ms ease-out;
}
.card:hover::after { opacity: 1; }

will-change-এর সঠিক ব্যবহার: (১) আগে মাপুন — DevTools-এর Performance প্যানেলে রেকর্ড করে দেখুন সত্যিই প্রথম ফ্রেমে দেরি হচ্ছে কি না। (২) শুধু সেই element-এ দিন, যা নিশ্চিতভাবে নড়বে — * { will-change: transform; } কখনো নয়। (৩) মনে রাখুন, will-change: transform element-কে একটি নতুন stacking context আর position: fixed সন্তানের জন্য নতুন containing block বানায় — তাই এটি শুধু গতির ইঙ্গিত নয়, লেআউটেও প্রভাব ফেলতে পারে। DevTools-এর Rendering প্যানেলে “Paint flashing” চালু করলে কোন অংশ আবার আঁকা হচ্ছে তা সবুজ ঝলকে দেখা যায় (পর্ব ৪৭, ৪৯)।

৮ · transitionend, সঠিক সময়ের মাপ আর touch-এর পর্দা

কখনো transition শেষ হলে কিছু করতে হয় — যেমন “অর্ডার নেওয়া হয়েছে” বার্তা মিলিয়ে গেলে সেটি DOM থেকে সরানো, বা স্ক্রিন রিডারের জন্য focus সরানো। এজন্য ব্রাউজার চারটি DOM ঘটনা পাঠায়: transitionrun (transition তৈরি হলো, delay-এর আগে), transitionstart (delay শেষে নড়া শুরু), transitionend (শেষ), আর transitioncancel (মাঝপথে বাতিল)। ঘটনা শোনার কৌশল JavaScript কোর্সের ইভেন্টের পর্বে বিস্তারিত; এখানে শুধু দেখুন কখন কী আসে:

<style>
  body { font-family: sans-serif; }
  .toast {
    width: fit-content; padding: 10px 14px; border-radius: 8px;
    background: #0f172a; color: #fff;
    opacity: 0; transform: translateY(10px);
    transition: opacity 300ms ease-out, transform 300ms ease-out;
  }
  .toast.show { opacity: 1; transform: none; }
  @media (prefers-reduced-motion: reduce) {
    .toast { transform: none; transition-duration: 1ms; }
  }
</style>
<button id="b">অর্ডার নিশ্চিত করুন</button>
<p class="toast" id="toast" role="status">✓ অর্ডার নেওয়া হয়েছে</p>
<script>
  const toast = document.getElementById('toast');
  toast.addEventListener('transitionrun', e => console.log('run  ' + e.propertyName));
  toast.addEventListener('transitionend', e =>
    console.log('end  ' + e.propertyName + ' (' + e.elapsedTime + 's)'));
  document.getElementById('b').addEventListener('click', () => toast.classList.toggle('show'));
</script>

শুরুতে শুধু একটি বোতাম; বার্তাটি অদৃশ্য। বোতাম চাপুন — কালো বার্তাটি নিচ থেকে উঠে ফুটে ওঠে, আর প্রিভিউর নিচে চারটি লাইন আসে: প্রথমে transform আর opacity-র দুটি run লাইন (ক্রম ব্রাউজারভেদে আলাদা হতে পারে), তারপর 300ms পরে end opacity (0.3s) আর end transform (0.3s). লক্ষ করুন — প্রতিটি property-র জন্য আলাদা ঘটনা; দুটি property বদলালে transitionend আসে দুবার, তাই কোডে e.propertyName দেখে একবারই কাজ করুন। আবার চাপলে উল্টো যাত্রা আর একই ঘটনা। চেষ্টা করুন: (১) পরপর খুব দ্রুত দুবার চাপুন — প্রথম transition মাঝপথে বাতিল হয়, তাই প্রথমবারের জন্য end আসবে না। (২) reduced-motion নিয়মে 1ms-কে 0s করার কথা ভাবুন: duration শূন্য হলে transition তৈরিই হয় না, transitionend-ও আসে না — যে কোড এই ঘটনার অপেক্ষায় থাকে, সে চিরকাল আটকে থাকবে।

কতক্ষণ? — সময়ের একটি মোটা দাগের নিয়ম

কোনো আইন নেই, তবে UI-ডিজাইনে দীর্ঘদিনের অভিজ্ঞতা থেকে একটি মোটা দাগের নিয়ম চলে আসছে — নিজের পাতায় পরীক্ষা করে ঠিক করুন:

কাজমোটামুটি সময়কেন
বোতামের রং, hover, focus100–200msসাড়া প্রায় তাৎক্ষণিক মনে হবে
ছোট toggle, checkbox, কার্ড ওঠা150–250msচোখে পড়ে, কিন্তু অপেক্ষা নয়
মেনু, dropdown, ছোট dialog খোলা200–300msকোথা থেকে এল, বোঝার মতো সময়
বন্ধ বা বেরিয়ে যাওয়াখোলার চেয়ে একটু কমব্যবহারকারী আর এটি দেখতে চান না
বড় এলাকা, লম্বা দূরত্ব (পুরো পর্দার drawer)300–400msদূরত্ব বেশি, খুব দ্রুত হলে ঝাঁকুনি লাগে

500ms-এর বেশি হলে সাধারণ UI-তে বেশিরভাগ মানুষের কাছে ধীর লাগে, বিশেষ করে যে কাজ দিনে বহুবার করা হয়। আর নিজের ডেভেলপমেন্টের সময় ধীর গতি দিয়ে পরীক্ষা করলে বাস্তবের মাপে ফিরতে ভুলবেন না — DevTools-এর Animations প্যানেলে গতি ১০% বা ২৫%-এ কমিয়ে খুঁটিনাটি দেখা যায়, কোড না বদলেই।

hover-নির্ভর transition আর touch-পর্দা

বাংলাদেশে ওয়েবের বড় অংশ দেখা হয় ফোনে, যেখানে মাউস নেই, তাই আসল hover-ও নেই। মোবাইল ব্রাউজার পুরোনো সাইট ভাঙা এড়াতে একটি ট্যাপকে hover হিসেবেও ধরে, ফলে ট্যাপের পরে কার্ড “উঠে আটকে” থাকে যতক্ষণ না অন্য কোথাও ট্যাপ করছেন — একে বলে sticky hover. তিনটি নিয়ম মানুন:

  • শুধু সাজসজ্জার hover-কে @media (hover: hover) and (pointer: fine)-এর ভেতরে রাখুন — মাউস বা ট্র্যাকপ্যাড থাকলেই কাজ করবে।
  • hover-এর পাশে সবসময় :focus-visible — কীবোর্ড ব্যবহারকারীও একই সাড়া পাবেন।
  • জরুরি তথ্য বা কাজ কখনো শুধু hover-এর পেছনে নয়। দাম, “কিনুন” বোতাম, মেনুর লিংক — ট্যাপ আর কীবোর্ডে সরাসরি পৌঁছানো চাই। পর্দায় চাপার জায়গাও যথেষ্ট বড় রাখুন (touch target, অন্তত 24×24 CSS px).

আরও গভীরে: CSS Transitions spec ব্রাউজারকে প্রতিটি style বদলে দুটি ছবি তুলনা করতে বলে — “before-change style” আর “after-change style”. প্রথমটিতে চলমান transition-গুলোর বর্তমান মান ধরা থাকে, তাই মাঝপথে উল্টো দিকে গেলে নতুন transition ঠিক সেখান থেকে শুরু হয়। আর অংশ ২-এ দেখা ছোট ফেরার সময়ের নাম spec-এ “reversing shortening factor” — উল্টো transition-এর duration কে সেই অনুপাতে ছোট করা হয়। display: none element-এর কোনো before-change style থাকে না, আর পাতা প্রথমবার আঁকার সময়ও থাকে না — এ কারণেই @starting-style নামে একটি আলাদা “নকল আগের অবস্থা” ঘোষণার ব্যবস্থা লেগেছে। ইতিহাস: transition প্রথম আনে Apple-এর WebKit, ২০০৭ সালে -webkit-transition নামে, আর ২০০৯ সালে W3C-তে খসড়া জমা পড়ে; আজ prefix ছাড়া transition লিখলেই চলে।

পরীক্ষা ও ইন্টারভিউতে — বিশ্ববিদ্যালয়, চাকরি বা ফ্রন্ট-এন্ড ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে:

  • transition: opacity 1s 2s-এ দুটি সময়ের মানে কী? — প্রথমটি duration (1s), দ্বিতীয়টি delay (2s).
  • transition আর animation-এর মূল পার্থক্য? — transition চলে মান বদলালে, দুই অবস্থার মাঝে একবার; animation @keyframes দিয়ে নিজে থেকে, বহু ধাপে, বারবার চলতে পারে।
  • অ্যানিমেশনের জন্য transform কেন left-এর চেয়ে ভালো? — left প্রতি ফ্রেমে layout ও paint জাগায়; transform সাধারণত শুধু compositing-এ, প্রায়ই আলাদা thread-এ।
  • display: none থেকে fade-in কীভাবে করবেন? — transition-behavior: allow-discrete-সহ display-কে transition-এর তালিকায় রাখুন, আর @starting-style-এ শুরুর opacity: 0 দিন।
  • ease আর linear-এর পার্থক্য? — linear সমান গতি; ease (ডিফল্ট, cubic-bezier(0.25, 0.1, 0.25, 1)) দ্রুত শুরু করে আলতো থামে।

৯ · AI-এর যুগে — AI-এর দেওয়া transition যাচাই

“বোতামে সুন্দর hover effect দাও” — AI খুব দ্রুত কাজ করে দেয়। কিন্তু অভিজ্ঞতায় দেখা যায়, AI-এর উত্তরে কয়েকটি ভুল বারবার ফিরে আসে, কারণ সে শিখেছে বহু বছরের পুরোনো ব্লগ আর টিউটোরিয়াল থেকে:

AI যা দিতে পারেসমস্যাঠিক কী
transition: all 0.5sঅপ্রত্যাশিত property-ও নড়ে, বাড়তি খরচশুধু দরকারি property-র নাম
-webkit-transition, -moz-transitionএক দশকের বেশি পুরোনো prefixশুধু transition
left, width, margin দিয়ে নড়াচড়াপ্রতি ফ্রেমে layout, ফোনে ঝাঁকুনিtransform আর opacity
সব element-এ will-change বা translateZ(0)মেমরি নষ্ট, উল্টো ধীরমেপে, শুধু দরকারি জায়গায়
max-height: 9999px দিয়ে accordionসময় ভুল, অদ্ভুত দেরিgrid-template-rows বা <details> + interpolate-size
reduced motion-এর কোনো নিয়ম নেই; শুধু :hoverঅ্যাক্সেসিবিলিটি আর touch-এর ঘাটতিprefers-reduced-motion, :focus-visible, (hover: hover)

AI-কে কাজ দেওয়ার সময় এই শর্তগুলো প্রম্পটেই লিখে দিন — তখন সে শুরু থেকেই ভালো কোড দেয়, আর আপনার যাচাইও সহজ হয়:

একটি অনলাইন দোকানের পণ্য-কার্ডের hover আর focus effect বানাতে চাই, শুধু CSS দিয়ে। ১. শুধু transform আর opacity transition করো; width, top, margin বা transition: all ব্যবহার করবে না। ২. কোনো vendor prefix দেবে না; সময় 150–300ms-এর মধ্যে রাখো আর timing function বেছে নেওয়ার কারণ বলো। ৩. hover-কে @media (hover: hover)-এর ভেতরে রাখো, আর :focus-visible-এ একই সাড়া দাও। ৪. @media (prefers-reduced-motion: reduce)-এ নড়াচড়া বন্ধ করো, তবে রং বা ছায়ার বদল রাখতে পারো। ৫. নতুন কোনো ফিচার (@starting-style, linear()) ব্যবহার করলে তার Baseline অবস্থা বলো আর fallback দাও; নিশ্চিত না হলে "নিশ্চিত নই, MDN দেখুন" লেখো।

AI-এর দেওয়া transition হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলো আপনাকে শুধু syntax নয়, মোশনের পেছনের মানুষ, ইতিহাস আর প্রকৌশল বুঝতে সাহায্য করবে।

প্র ০১CSS-এর ডিফল্ট timing function linear না হয়ে ease কেন? আর “Bézier” নামটি এল কোথা থেকে?

গণিতের দিক থেকে linear সবচেয়ে সরল — তবু CSS-এর নকশাকারীরা ডিফল্ট রেখেছেন ease. কারণ মানুষের চোখ। আমাদের চারপাশের প্রতিটি ভরযুক্ত জিনিস — ফুটবল, রিকশা, দরজার পাল্লা — জড়তার কারণে ধীরে গতি নেয় আর ঘর্ষণে ধীরে থামে। এক মুহূর্তে পূর্ণ গতি আর এক মুহূর্তে শূন্য গতি প্রকৃতিতে প্রায় দেখাই যায় না, তাই linear নড়াচড়া চোখে “যান্ত্রিক” বা “সস্তা” লাগে।

এই উপলব্ধি অ্যানিমেশন-শিল্পে অনেক পুরোনো। Disney-র দুই অ্যানিমেটর Frank Thomas আর Ollie Johnston ১৯৮১ সালের বই “The Illusion of Life”-এ চরিত্র-অ্যানিমেশনের যে ১২টি মূলনীতি লিখেছিলেন, তার একটি “slow in and slow out” — শুরু আর শেষের কাছে বেশি ফ্রেম, মাঝে কম। CSS-এর ease-in-out ঠিক এই নীতির গাণিতিক রূপ। Apple যখন WebKit-এ transition আনে, তার আগেই Mac-এর নিজস্ব অ্যানিমেশন-ব্যবস্থায় এমন ease বক্ররেখা প্রচলিত ছিল; সেই অভিজ্ঞতা থেকেই CSS-এর keyword-গুলোর মান এসেছে বলে মনে করা হয়।

আর “Bézier” একজন মানুষের নাম। ১৯৬০-এর দশকে ফরাসি গাড়ি-কোম্পানি Renault-এর প্রকৌশলী Pierre Bézier গাড়ির মসৃণ বডির নকশা কম্পিউটারে বর্ণনা করার জন্য এই বক্ররেখাগুলো জনপ্রিয় করেন। প্রায় একই সময়ে আরেক প্রতিদ্বন্দ্বী কোম্পানি Citroën-এ গণিতবিদ Paul de Casteljau একই গণিত স্বাধীনভাবে বের করেছিলেন, কিন্তু কোম্পানির গোপনীয়তার কারণে তাঁর কাজ অনেক দিন প্রকাশ পায়নি — তাই নাম হয়ে গেল Bézier-এর। আজ এই বক্ররেখা সর্বত্র: ফন্টের অক্ষরের রেখা (TrueType ও OpenType), SVG-র path, Illustrator বা Figma-র pen tool — আর CSS-এর timing function.

একটি সূক্ষ্ম কথা: ease নিখুঁত প্রতিসম নয় — এটি দ্রুত শুরু করে আর অনেকক্ষণ ধরে থামে। ছোট hover-এ এটি চমৎকার, কারণ সাড়া তাৎক্ষণিক মনে হয়। কিন্তু লম্বা জায়গা-বদলে পেশাদার ডিজাইন-সিস্টেমগুলো সাধারণত নিজেদের মাপা cubic-bezier() রাখে, design token হিসেবে।

মূল উপলব্ধি: ভালো ডিফল্ট গণিতের সরলতা নয়, মানুষের অভিজ্ঞতা দেখে বাছা হয়। আর প্রযুক্তির অনেক “নতুন” ধারণা আসলে অন্য শিল্পের — অ্যানিমেশন ছবি, গাড়ির নকশা — দশকের পুরোনো জ্ঞান।

প্র ০২display আর height: auto-তে transition — এত সাধারণ চাওয়া পূরণ হতে প্রায় পনেরো বছর লাগল কেন?

২০০৯ সালের প্রথম খসড়া থেকেই ডেভেলপাররা চেয়েছেন মেনু display: none থেকে ধীরে ফুটে উঠুক, আর accordion height: auto পর্যন্ত মসৃণভাবে খুলুক। সমস্যাটি ইচ্ছার অভাব নয়, CSS-এর ভেতরের মডেল।

display-এর সমস্যা দুই দিকের। transition চালাতে ব্রাউজারের দরকার “আগের মান” আর “পরের মান।” display: none element-এর কোনো বাক্স নেই, render tree-তেও নেই — তার আগের opacity কত ছিল, এই প্রশ্নের কোনো অর্থপূর্ণ উত্তর নেই। আবার উল্টো দিকে, none একটি keyword, তাই মাঝের মান নেই; ব্রাউজার সেটি সাথে সাথে বসায়, আর element-এর সাথে তার fade-ও মুছে যায়। তাই বহু বছর মানুষ visibility আর opacity জুড়ে, বা JavaScript-এ class যোগ করে, তারপর একটি ফ্রেম অপেক্ষা করে (requestAnimationFrame), শেষে transitionend শুনে display: none বসাতেন — ভঙ্গুর আর ভুলে ভরা কৌশল।

সমাধান এল দুই টুকরোয়। transition-behavior: allow-discrete discrete property-কে transition-এ ঢোকার অনুমতি দিল, আর display-এর জন্য বিশেষ নিয়ম করল — দৃশ্যমান অবস্থাটি পুরো সময় ধরে থাকবে। @starting-style দিল “আগের মান” না থাকার সমস্যার উত্তর: লেখক নিজেই ঘোষণা করেন প্রথম আঁকার আগে মান কী ধরে নিতে হবে। ২০২৩ সালে Chromium-এ আসার পর ২০২৪ সালে Safari আর Firefox যোগ দেয়।

height: auto-র সমস্যা আলাদা। auto কোনো সংখ্যা নয়; এর মান বের করতে লেআউট চালাতে হয়, আর সেটি ভেতরের লেখা, পর্দার প্রস্থ, ফন্ট লোড — সবকিছুর ওপর নির্ভর করে। সব keyword-কে হঠাৎ interpolable করলে লক্ষ লক্ষ পুরোনো সাইটে যেখানে এক লাফ প্রত্যাশিত, সেখানে নড়াচড়া শুরু হতো। তাই নকশা হলো opt-in: interpolate-size: allow-keywords বা calc-size(). এটি এখনো শুধু Chromium-এ, বাকিরা আলোচনায় — এ কারণেই আজও grid-এর 0fr → 1fr কৌশল বাস্তবের ভরসা।

মূল উপলব্ধি: ওয়েব প্ল্যাটফর্মের সবচেয়ে বড় প্রতিশ্রুতি — “পুরোনো সাইট ভাঙবে না” — নতুন ফিচারকে ধীর করে, কিন্তু সেটিই ওয়েবকে টেকসই রাখে। অনেক “সহজ চাওয়া” আসলে মডেলের গভীরে লুকানো প্রশ্ন, আর সমাধান প্রায়ই আসে opt-in হয়ে।

প্র ০৩সুন্দর অ্যানিমেশন কীভাবে মানুষকে অসুস্থ করে — আর prefers-reduced-motion এল কীভাবে?

আমাদের ভারসাম্যের বোধ আসে তিনটি সূত্র মিলিয়ে: চোখ, কানের ভেতরের vestibular ব্যবস্থা, আর শরীরের পেশির অনুভূতি। চলন্ত বাসে বই পড়লে বমিভাব হয়, কারণ চোখ বলছে “স্থির আছি”, কান বলছে “নড়ছি।” পর্দার বড় নড়াচড়া উল্টো ঘটনা ঘটায় — চোখ দেখে পুরো জগৎ সরে যাচ্ছে, অথচ শরীর স্থির। বেশিরভাগ মানুষ এটি টেরও পান না, কিন্তু vestibular সমস্যা, মাইগ্রেন বা কিছু স্নায়বিক অবস্থা আছে এমন অনেকের জন্য এটি মাথা ঘোরা, বমিভাব, এমনকি কয়েক ঘণ্টার অসুস্থতা ডেকে আনে।

বিষয়টি বড় আলোচনায় আসে ২০১৩ সালে, Apple-এর iOS 7-এর সময়। নতুন নকশায় ছিল parallax ওয়ালপেপার আর অ্যাপ খোলার সময় বড় zoom অ্যানিমেশন। অনেক ব্যবহারকারী জানালেন, ফোন ব্যবহার করলেই তাঁদের অসুস্থ লাগছে। Apple অপারেটিং সিস্টেমে “Reduce Motion” সেটিং যোগ করে। পরে অন্য অপারেটিং সিস্টেমগুলোতেও এমন সেটিং আসে — Windows-এ “Animation effects”, Android-এ “Remove animations.”

কিন্তু ওয়েবসাইট এই সেটিং জানত না। WebKit দল ২০১৭ সালে Safari-তে prefers-reduced-motion media query আনে, আর পরের কয়েক বছরে সব প্রধান ব্রাউজার যোগ দেয় — আজ এটি বহু বছর ধরে Baseline Widely available. WCAG-ও বিষয়টি স্বীকার করে: 2.2.2 (Pause, Stop, Hide) বলে, নিজে থেকে চলা ৫ সেকেন্ডের বেশি নড়াচড়া থামানোর উপায় থাকতে হবে; আর 2.3.3 (Animation from Interactions, AAA স্তর) বলে, ব্যবহারকারীর কাজে শুরু হওয়া অ-জরুরি নড়াচড়া বন্ধ করা যেতে হবে।

সঠিক ব্যাখ্যা গুরুত্বপূর্ণ: “reduce” মানে “শূন্য” নয়। reduced-motion ব্যবহারকারীরও জানা দরকার বোতাম চাপা হয়েছে, মেনু খুলেছে। তাই বড় সরে যাওয়া, zoom, parallax, ঘূর্ণন বাদ দিন বা ছোট করুন; কিন্তু রঙের বদল, ছোট fade রেখে দিন। আর সাবধান — এক লাইনে * { transition: none !important } লিখলে transitionend-নির্ভর কোড ভেঙে যেতে পারে।

মূল উপলব্ধি: অ্যানিমেশন শুধু নান্দনিকতার প্রশ্ন নয়, শরীরেরও প্রশ্ন। ব্যবহারকারী তাঁর প্রয়োজন অপারেটিং সিস্টেমে একবার জানিয়ে রেখেছেন — সেই অনুরোধ সম্মান করা আপনার কোডের দায়িত্ব।

প্র ০৪“translateZ(0) দিলে অ্যানিমেশন দ্রুত হয়” — এই পুরোনো কৌশল কোথা থেকে এল, আর will-change কেন তার জায়গা নিল?

২০১০-এর দশকের শুরুতে ব্রাউজারগুলো GPU দিয়ে compositing শুরু করে। কিন্তু কোন element নিজের আলাদা স্তর (layer) পাবে, তা ঠিক করত ব্রাউজারের ভেতরের কিছু অনুমান — যেমন element-এ 3D transform আছে কি না। ডেভেলপাররা আবিষ্কার করলেন, যে element আসলে নড়েই না এমন 3D মান — transform: translateZ(0) বা translate3d(0,0,0) — দিলে ব্রাউজার তাকে আলাদা স্তরে তুলে দেয়। ফলে পরের অ্যানিমেশন মসৃণ হতো, বিশেষ করে সেই সময়ের iPhone-এ। কৌশলটির ডাকনাম হয়ে গেল “null transform hack”, অনেকে বলতেন “GPU acceleration চালু করা।”

সমস্যা হলো এটি ছিল হ্যাক — ব্রাউজারের ভেতরের একটি অনুমানের অপব্যবহার, কোনো প্রতিশ্রুতি নয়। মানুষ এটি সব জায়গায় ছড়িয়ে দিল, এমনকি * { transform: translateZ(0) } পর্যন্ত। প্রতিটি স্তর GPU-র মেমরিতে একটি আলাদা ছবি; শত শত স্তর মানে “layer explosion” — কম মেমরির ফোনে পাতা উল্টো ধীর হয়, কখনো ব্রাউজার ট্যাবটিই বন্ধ করে দেয়। তার ওপর transform stacking context বানায়, তাই z-index আর position: fixed-এর অদ্ভুত বাগ দেখা দিত।

will-change property আনা হয় এই হ্যাকের সৎ বিকল্প হিসেবে: “এই property শিগগিরই বদলাবে — ব্রাউজার নিজের বিবেচনায় প্রস্তুতি নিক।” এখন সিদ্ধান্ত ব্রাউজারের, অনুমানের ওপর নির্ভর নয়; আর ভবিষ্যতে ব্রাউজার আরও বুদ্ধিমান হলে একই ঘোষণা আরও ভালো ফল দেবে। spec-এ লেখকদের পরিষ্কার পরামর্শও আছে: এটি শেষ উপায়, পারফরম্যান্স সমস্যা মেপে পেলে তবেই, আর সম্ভব হলে অ্যানিমেশনের ঠিক আগে যোগ করে পরে সরিয়ে নিন।

আজকের ব্রাউজার নিজেই অনেক বুদ্ধিমান — transform বা opacity-র transition শুরু হলেই সাধারণত element-কে আলাদা স্তরে তোলে। তাই বেশিরভাগ সাধারণ hover-এ will-change লাগেই না। বাংলাদেশের মতো বাজারে, যেখানে কম দামের, কম RAM-এর Android ফোন প্রচুর, এই সংযম আরও জরুরি।

মূল উপলব্ধি: হ্যাক সমস্যা মেটায় আজ, কিন্তু ভবিষ্যতের ঋণ রেখে যায়। একটি স্পষ্ট ঘোষণামূলক (declarative) API — “আমি কী চাই” বলা, “কীভাবে করো” নয় — প্ল্যাটফর্মকে নিজে উন্নত হওয়ার সুযোগ দেয়।

প্র ০৫“100–300ms” — এই সংখ্যাগুলো কি কেবল রুচির ব্যাপার, নাকি মানুষের মস্তিষ্কের কোনো সীমা থেকে আসে?

পুরোপুরি রুচি নয়। কম্পিউটারের সাড়ার সময় নিয়ে গবেষণা বহু পুরোনো। ১৯৬৮ সালে IBM-এর গবেষক Robert Miller মানুষ-কম্পিউটার কথোপকথনে সাড়ার সময়ের কয়েকটি সীমা নিয়ে লেখেন; পরে Jakob Nielsen তাঁর ১৯৯৩ সালের “Usability Engineering” বইয়ে সেগুলো তিনটি সহজ সংখ্যায় জনপ্রিয় করেন: প্রায় 0.1 সেকেন্ড — মনে হয় যন্ত্র সাথে সাথে সাড়া দিল; প্রায় 1 সেকেন্ড — দেরি টের পাওয়া যায়, কিন্তু চিন্তার ধারা ভাঙে না; প্রায় 10 সেকেন্ড — মনোযোগ ধরে রাখার সীমা।

এখান থেকে transition-এর হিসাব বোঝা যায়। বোতামের রং-বদলের মতো সাড়া 100ms-এর কাছাকাছি হলে মনে হয় “আমার চাপাতেই হলো।” 200–300ms এমন একটি এলাকা, যেখানে নড়াচড়া চোখে স্পষ্ট পড়ে — কোথা থেকে কোথায় গেল, মস্তিষ্ক ধরতে পারে — অথচ ব্যবহারকারী অপেক্ষা করছেন বলে মনে হয় না। 500ms ছাড়ালে মানুষ অ্যানিমেশন দেখেন না, অ্যানিমেশনের জন্য অপেক্ষা করেন — বিশেষ করে দিনে বিশবার খোলা একটি মেনুতে।

বড় ডিজাইন-সিস্টেমগুলোও এই যুক্তিতে নিয়ম লেখে — যেমন Google-এর Material Design বা Apple-এর Human Interface Guidelines মোশনের সময়কে আকার আর দূরত্বের সাথে যুক্ত করে: ছোট জিনিস, ছোট দূরত্ব — কম সময়; পুরো পর্দা জুড়ে চলা — বেশি। ডেস্কটপে সাধারণত মোবাইলের চেয়ে একটু কম, কারণ ইঁদুরের পয়েন্টার দ্রুত, আর পর্দা থেকে চোখের দূরত্বে নড়াচড়া ছোট দেখায় — যদিও এসব খুঁটিনাটি প্রতিটি সিস্টেমে আলাদা।

তবে এগুলো মোটা দাগের নিয়ম, পদার্থবিদ্যার সূত্র নয়। একই 250ms ease-out-এ দ্রুত লাগে, ease-in-এ ধীর লাগে — কারণ ease-out শুরুতেই বেশিরভাগ পথ পার করে। তাই সংখ্যা মুখস্থ না করে সত্যিকারের ব্যবহারকারীর ফোনে, সত্যিকারের কাজে পরীক্ষা করুন।

মূল উপলব্ধি: ভালো মোশনের সময় ঠিক হয় মানুষের উপলব্ধির সীমা দিয়ে, কোডের সুবিধা দিয়ে নয়। অনুভূত গতি (perceived speed) প্রায়ই আসল গতির চেয়ে বেশি গুরুত্বপূর্ণ — আর easing সেই অনুভূতি বদলানোর সবচেয়ে সস্তা হাতিয়ার।

অনুশীলন

বেশিরভাগ কাজ দেখা আর ভাবার; একটিতে ছোট কোড লিখতে হবে।

  1. Shorthand ভেঙে পড়ুন: transition: opacity 0.3s ease-in 1s, transform 200ms; — প্রতিটি property-র duration, timing function আর delay কত? আর এই নিয়মের পরে transition-delay: 0s; লিখলে কী বদলাবে?

    opacity: duration 0.3s, timing ease-in, delay 1s (দ্বিতীয় সময়-মান)। transform: duration 200ms, timing ডিফল্ট ease, delay ডিফল্ট 0s. পরে transition-delay: 0s লিখলে তালিকায় একটিই মান, যা দুই property-তেই পুনরাবৃত্ত হয় — তাই opacity-র 1s delay মুছে গিয়ে সেটিও সাথে সাথে শুরু হবে। বাকি সব আগের মতো।

  2. DevTools-এ easing নিয়ে খেলুন: অংশ ৫-এর দৌড়ের উদাহরণটি চালান। তারপর DevTools খুলে একটি .dot বেছে নিন, Styles প্যানেলে transition-এর পাশের বাঁকা-রেখার আইকনে চেপে easing editor খুলুন। বিন্দু টেনে এমন একটি বক্ররেখা বানান যাতে বল প্রথমে একটু পিছিয়ে তারপর সামনে ছোটে। কোন সংখ্যাটি ০-এর নিচে নামাতে হলো?

    প্রথম টানার বিন্দুর y1-কে ঋণাত্মক করতে হয়, যেমন cubic-bezier(0.6, -0.4, 0.7, 1). y-এর মান 0-এর নিচে মানে পথের শুরুরও পেছনে — বল প্রথমে একটু উল্টো দিকে যায় (anticipation), তারপর ছোটে। x-এর মান কিন্তু 0–1-এর বাইরে নেওয়া যায় না; চেষ্টা করলে DevTools আটকে দেবে, আর হাতে লিখলে পুরো মানটি অবৈধ হয়ে যাবে। খেয়াল করুন, Chrome-এর easing editor-এ তৈরি-করা কিছু নমুনা (preset) বক্ররেখাও আছে।

  3. একটি খারাপ transition ঠিক করুন: নিচের কোডে পাশ থেকে একটি মেনু ঢোকে: .drawer { left: -280px; transition: all 0.8s linear; } .drawer.open { left: 0; }. এতে অন্তত চারটি সমস্যা খুঁজুন, আর পুরো ঠিক করা CSS লিখুন।

    সমস্যা: (১) left প্রতি ফ্রেমে layout জাগায়; (২) all অপ্রত্যাশিত property-ও নড়াবে; (৩) 0.8s একটি মেনুর জন্য অনেক ধীর; (৪) linear ঢোকার নড়াচড়ায় যান্ত্রিক লাগে, আর থামার জায়গায় ধাক্কা লাগে; (৫) reduced motion-এর কোনো ব্যবস্থা নেই।

    .drawer {
      position: fixed; inset-block: 0; left: 0; width: 280px;
      transform: translateX(-100%);
      transition: transform 300ms ease-in;       /* বন্ধ: বেরিয়ে যাওয়া */
    }
    .drawer.open {
      transform: none;
      transition: transform 300ms ease-out;      /* খোলা: ঢুকে থামা */
    }
    @media (prefers-reduced-motion: reduce) {
      .drawer, .drawer.open { transition: opacity 150ms; }
    }

    খোলার আর বন্ধের transition আলাদা করতে অংশ ২-এর নিয়মটিই কাজে লাগল — ব্রাউজার নেয় নতুন অবস্থার transition. reduced motion-এ এখানে সরে আসা বন্ধ হয়নি, শুধু তাৎক্ষণিক হয়েছে; চাইলে মেনুকে opacity দিয়ে ফুটিয়ে তোলার আলাদা নকশাও করতে পারেন। আর লুকানো অবস্থায় মেনুর লিংক যেন Tab না পায়, সেজন্য visibility: hidden বা inert লাগবে।

  4. ফোনে sticky hover দেখুন: অংশ ৩-এর কার্ডের উদাহরণ থেকে @media (hover: hover) মোড়কটি সরিয়ে শুধু .card:hover { … } রাখুন। তারপর নিজের ফোনে এই পাতা খুলে (বা DevTools-এর Device Toolbar-এ touch চালু করে) কার্ডে ট্যাপ করুন। কী দেখলেন? মোড়ক ফিরিয়ে দিলে কী হয়?

    মোড়ক ছাড়া, ট্যাপ করলে কার্ড উঠে যায় আর উঠেই থাকে — যতক্ষণ না পাতার অন্য কোথাও ট্যাপ করছেন। কারণ মোবাইল ব্রাউজার ট্যাপের পরে element-কে “hover অবস্থায়” রেখে দেয়। দেখতে মনে হয় কিছু বাছাই হয়ে আছে, যা বিভ্রান্তিকর। মোড়ক ফিরিয়ে দিলে touch-পর্দায় (hover: hover) মেলে না, তাই কার্ড ওঠেই না — কিন্তু “কিনুন” লিংক ঠিকই কাজ করে, কারণ জরুরি কাজ hover-এর ওপর নির্ভর করেনি। মাউসযুক্ত ল্যাপটপে আচরণ আগের মতোই থাকে।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমwidth-এর transition-এর একটি ফ্রেমে ব্রাউজারের কাজগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ⏱️ নতুন ফ্রেমের জন্য transition-এর মাঝের মান হিসাব
  2. 🎨 Style — কোন element-এর চূড়ান্ত মান কী
  3. 📐 Layout — বাক্সগুলোর জায়গা আর মাপ
  4. 🖌️ Paint — পিক্সেল আঁকা
  5. 🧩 Composite — স্তরগুলো জুড়ে পর্দায় দেখানো

CSS-এ transition: opacity 400ms ease-in 100ms; — এখানে 100ms কী?

  • transition-এর মোট সময় (duration)
  • ease-in বক্ররেখার শক্তি
  • transition শুরুর আগের দেরি (delay)
  • প্রতিটি ফ্রেমের সময়

একটি বোতামে শুধু .btn:hover { background: navy; transition: background 300ms; } লেখা আছে, মূল .btn-এ কোনো transition নেই। কী ঘটবে?

  • দুই দিকেই মসৃণ রং-বদল
  • মাউস আসার সময় মসৃণ, সরানোর সময় এক লাফে ফেরা
  • কোনো দিকেই transition হবে না, কারণ :hover-এ transition লেখা অবৈধ
  • মাউস আসার সময় এক লাফে, সরানোর সময় মসৃণ

কম দামের ফোনে মসৃণ নড়াচড়ার জন্য কোন property-র transition সবচেয়ে ভালো?

  • margin-left
  • width
  • top
  • transform

একটি popover খোলার সময় opacity: 0 থেকে ফুটিয়ে তুলতে চান। শুরুর মান কোথায় লিখবেন?

  • @starting-style { [popover]:popover-open { opacity: 0; } }
  • @supports (opacity: 0) { … }
  • transition-delay: 0s দিয়ে
  • will-change: opacity দিয়ে

transition: width 1.3s steps(13, end) — width কীভাবে বদলাবে?

  • মসৃণভাবে, শুধু শেষে একটু থামবে
  • পুরো সময়ের শেষে এক লাফে
  • ১৩টি সমান লাফে, প্রতিটি ধাপের শেষে
  • ১৩টি এলোমেলো মাপের লাফে

একটি element-এ opacity আর transform দুটিই transition হলো। শেষে transitionend ঘটনা কতবার আসবে?

  • একবার, element-টির জন্য
  • দুবার — প্রতিটি property-র জন্য একবার
  • প্রতি ফ্রেমে একবার
  • একবারও না, কারণ এটি শুধু animation-এর জন্য

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. transition অবস্থা বানায় না, পথ বানায়: property, duration, timing function, delay. মূল নিয়মে লিখুন — ব্রাউজার নেয় নতুন অবস্থার transition.
  2. Shorthand-এ প্রথম সময় duration, দ্বিতীয়টি delay; কমা দিয়ে একাধিক; all নয়, নির্দিষ্ট নাম।
  3. discrete property ডিফল্টে লাফায়; allow-discrete + @starting-style দিয়ে display-এর খোলা-বন্ধ অ্যানিমেশন (Baseline Newly available); height: auto-র জন্য আজ grid 0fr → 1fr.
  4. Timing function = চরিত্র: ঢুকতে ease-out, বেরোতে ease-in, নিজের মাপে cubic-bezier(), ধাপে steps(), স্প্রিংয়ে linear().
  5. transform ও opacity সরান, 100–300ms-এর আশপাশে রাখুন, will-change মেপে দিন, hover-এর পাশে :focus-visible আর prefers-reduced-motion কখনো ভুলবেন না।

পরের পর্বে: transition চলে শুধু দুই অবস্থার মাঝে, আর কেউ মান বদলালে তবেই। যদি চান লোডিং-চিহ্ন নিজে নিজে ঘুরুক, বা একটি নড়াচড়ায় পাঁচটি ধাপ থাকুক? তার উত্তর @keyframes animation.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান