একই ডকুমেন্ট ট্রানজিশন

ওয়েব ফিচার: ট্রানজিশন দেখুন

সমস্যা

ওয়েবসাইট প্রায়ই কোনও অবজেক্টের একাধিক ভিউ প্রদান করে, যেমন প্রোডাক্টের তালিকা এবং তারপর প্রতিটি প্রোডাক্টের জন্য একটি বিবরণ পৃষ্ঠা। দুটি ভিউয়ের মধ্যে নেভিগেট করার সময় প্রায়ই ডিসকানেক্ট হয়ে যায়। কোনও ব্যবহারকারী কোনও প্রোডাক্টের থাম্বনেলে ক্লিক করে তার বিবরণ দেখার সময়, থাম্বনেলটি অদৃশ্য হয়ে যায় এবং স্ক্রিনের অন্য কোথাও সাথে সাথে একটি নতুন, বড় ছবি দেখা যায়। এই ধারাবাহিকতার অভাবের কারণে ব্যবহারকারীদের পক্ষে এলিমেন্টের মধ্যে সম্পর্ক ট্র্যাক করা কঠিন হয়ে পড়ে।

সমাধান

View Transitions API আপনাকে এলিমেন্ট পেয়ার নির্দিষ্ট করার অনুমতি দেয় যা ট্রানজিশনের আগে ও পরে বিভিন্ন অবস্থায় থাকে। সিঙ্গেল পেজ নেভিগেশনে (SPA) document.startViewTransition()-এর মাধ্যমে ট্রানজিশন ট্রিগার করার সময়, ব্রাউজার এইসব শেয়ার করা এলিমেন্টকে তাদের শেয়ার করা অনন্য view-transition-name-এর মাধ্যমে শনাক্ত করে। তারপর এটি অটোমেটিক তাদের অবস্থান, সাইজ ও স্টাইলিংয়ের পার্থক্য গণনা করে এবং পুরনো অবস্থা থেকে নতুন অবস্থায় মসৃণভাবে অ্যানিমেট করে। এই ট্রানজিশন সবচেয়ে উপরের লেয়ারে হয়, এমনকি বেশি z-index ভ্যালু থাকা এলিমেন্টের উপরেও।

প্রয়োগ করার নির্দেশিকা

ধাপ ১: startViewTransition-এ স্টেট পরিবর্তন র‍্যাপ করুন

সিঙ্গেল-পেজ অ্যাপ্লিকেশন (SPA) বা সাধারণ স্ট্যাটাস পরিবর্তনের জন্য, document.startViewTransition-এ DOM আপডেট করে এমন লজিক র‍্যাপ করুন। ব্রাউজার বর্তমান অবস্থার একটি স্ন্যাপশট ক্যাপচার করে, আপডেট রান করে এবং তারপর নতুন অবস্থা ক্যাপচার করে।

function navigate(view) {
  // MANDATORY: Wrap the update in startViewTransition
  document.startViewTransition(() => updateDOM(view));
}

ধাপ ২: শেয়ার করা ট্রানজিশনের নাম অ্যাসাইন করা

কোন এলিমেন্ট মর্ফ করা উচিত তা ব্রাউজারকে জানাতে view-transition-name CSS প্রপার্টি ব্যবহার করুন। নাম যেকোনও কিছু হতে পারে (none ছাড়া)। বাধ্যতামূলক: প্রদত্ত view-transition-name-এর আগে ও পরে ১টির বেশি এলিমেন্ট থাকলে চলবে না। প্রদত্ত view-transition-name-এর সাথে ২ বা তার বেশি এলিমেন্ট থাকলে, কোনও ট্রানজিশন ছাড়াই DOM অবিলম্বে নতুন অবস্থায় আপডেট করা হবে।

একাধিক এলিমেন্টের জোড়াকে মর্ফ করতে আপনি একাধিক view-transition-name ব্যবহার করতে পারবেন। যেমন, আপনি হয়ত প্রোডাক্টের ছবি ও নাম, দুটিতেই আলাদা আলাদা ট্রানজিশন ব্যবহার করতে চান।

তালিকা ভিউতে একাধিক আইটেম থাকার কারণে, আপনি সবকটিতে একই view-transition-name দিতে পারবেন না। SPA-তে এটি দুটি উপায়ে সমাধান করা যেতে পারে।

  1. ডায়নামিক বিবরণ পৃষ্ঠা: তালিকা পৃষ্ঠার প্রতিটি আইটেমে একটি অনন্য view-transition-name অ্যাসাইন করুন এবং তারপরে এখানে দেখানো তালিকা আইটেমটি বেছে নেওয়া হলে বিবরণ পৃষ্ঠায় ম্যাচিং এলিমেন্টে সেই নামটি ডায়নামিকভাবে প্রয়োগ করুন।

    /* In the list view, give each */
    #product-1 { view-transition-name: p1 }
    #product-2 { view-transition-name: p2 }
    #product-3 { view-transition-name: p3 }
    
    function updateDOM(clickedTransitionName){
      const hero = document.getElementById("hero");
      hero.style.viewTransitionName = clickedTransitionName;
    }
    
  2. ডায়নামিক তালিকার আইটেম: বিবরণ পৃষ্ঠার এলিমেন্টকে view-transition-name হিসেবে অ্যাসাইন করুন এবং তালিকা পৃষ্ঠার আইটেমটি বেছে নেওয়া হলে সেটিতে এই নাম প্রয়োগ করুন। তালিকা পৃষ্ঠায় ফিরে আসার সময় তালিকা পৃষ্ঠার আইটেম থেকে view-transition-name সরিয়ে দিন।

বিবরণ পৃষ্ঠার #hero এলিমেন্ট এবং তালিকা পৃষ্ঠার বেছে নেওয়া .thumbnail এলিমেন্ট একটি view-transition-name শেয়ার করে।

#hero{
  view-transition-name: hero;
}
.thumbnail.selected {
  view-transition-name: hero;
}

থাম্বনেলে ক্লিক করা হলে, .selected ক্লাস সিলেক্টর ব্যবহার করে view-transition-name অ্যাসাইন করার মাধ্যমে আমাদের তালিকা ভিউ প্রস্তুত করতে হবে এবং ট্রানজিশন শুরু করার আগে DOM-এ যেকোনও পরিবর্তন করতে হবে।

তারপর, আপনি document.startViewTransition()-কে কল করতে পারেন এবং পৃষ্ঠাটি বিস্তারিত থেকে তালিকা ভিউতে পরিবর্তন করার জন্য পরিবর্তনগুলি প্রয়োগ করতে পারেন।

তালিকা ভিউতে ফিরে যাওয়ার পরে, পরবর্তী নেভিগেশনে যাতে কোনও সমস্যা না হয় তার জন্য আপনাকে ভিউ ট্রানজিশন ক্লাস ক্লিন-আপ করতে হবে। ট্রানজিশনের finished প্রমিস সমাধান হওয়ার পরে আপনি এই ক্লিন-আপ করতে পারবেন।

// Function called when a thumbnail is clicked
function goFromListToDetail(e){
  e.currentTarget.classList.add("selected");
  const hero = document.getElementById("hero");
  const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
  hero.style.background = bgColor;

  // Trigger the transition, checking for support
  if (!document.startViewTransition) {
    document.body.classList.add("detail");
    // MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
    document.getElementById("detail-heading")?.focus();
    return; // MANDATORY: End function execution if view transitions are not supported.  
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.add("detail");
  });
  // MANDATORY Accessibility Routing: Route focus after the view transition resolves
  transition.finished.finally(() => {
    document.getElementById("detail-heading")?.focus();
  });
}

// Function called when navigating from detail back to list view
function goFromDetailToList() {
  if (!document.startViewTransition) {
    document.body.classList.remove("detail");
    document.getElementById("list-heading")?.focus();
    return;
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.remove("detail");
  });
  // Clean up the list view and route focus
  transition.finished.finally(() => {
    // Route focus back to list view
    document.getElementById("list-heading")?.focus();
    // Remove selected classList to remove view-transition-names
    document.querySelectorAll(".selected").forEach(
      (element) => {
        element.classList.remove("selected");
      },
    );
  });
}

আপনি কোন পদ্ধতি বেছে নেবেন তা ব্যবহারের উপর নির্ভর করবে। ডায়নামিক তালিকার আইটেমে কম CSS রিপিট করতে হয়, তবে ম্যানুয়ালি জাভাস্ক্রিপ্ট ক্লিন-আপ বেশি করতে হয়।

ধাপ ৩: অ্যাসপেক্ট রেশিও "স্ট্রেচিং" সংক্রান্ত সমস্যার সমাধান করা

ডিফল্ট হিসেবে, ব্রাউজার গ্রুপের মধ্যে পুরনো ও নতুন স্ন্যাপশট ক্রস-ফেড করে যা দুটিকেই ফিট করার জন্য স্ট্রেচ করে। আপনি টেক্সট ট্রানজিশন করলে, পুরনো ও নতুন ভিউতে টেক্সট এলিমেন্টের প্রস্থ fit-content-এ সেট করুন, যাতে ট্রানজিশন করা এলিমেন্টের অ্যাস্পেক্ট রেশিও স্থিতিশীল থাকে।

#list-page .title {
  width: fit-content;
}

#detail-page #title {
  width: fit-content;
}

আপনি যদি এমন এলিমেন্ট ট্রানজিশন করেন যা অ্যাস্পেক্ট রেশিও পরিবর্তন করে, তাহলে আপনাকে পুরনো এবং নতুন ছদ্ম-এলিমেন্টের উচ্চতা ::view-transition-pair() ছদ্ম-এলিমেন্টের ১০০% হিসেবে সেট করতে হতে পারে।

::view-transition-old(hero),
::view-transition-new(hero){
  height: 100%;
}

সিউডো-এলিমেন্ট হল লাইভ এলিমেন্টের স্ন্যাপশট, তাই ট্রানজিশন এফেক্টের আরও বেশি কন্ট্রোলের জন্য আপনি object-fit এবং object-position ডিক্লারেশনও ব্যবহার করতে পারবেন।

পেশাদার পদ্ধতি

  • খুব বেশি ট্রানজিশন নির্দিষ্ট করবেন না। ব্যবহারকারী অ্যাক্টিভভাবে ট্র্যাক করছেন এমন প্রাথমিক কন্টেন্টের জন্য শুধুমাত্র শেয়ার করা এলিমেন্ট ব্যবহার করুন (যেমন, অনেক বড় ছবি, হেডিং)।
  • ভবিষ্যতের ট্রানজিশনে সাইড এফেক্ট এড়াতে ট্রানজিশন শেষ হওয়ার পরে অস্থায়ী view-transition-name মানগুলি সরিয়ে দিন।
  • অ্যাক্টিভ অ্যানিমেশন সহ এলিমেন্ট ট্রানজিশন করবেন না। ভিউ ট্রানজিশন স্ন্যাপশটে কাজ করে, তাই ভিউ ট্রানজিশনের সময় যেকোনও অ্যানিমেশন পজ করা আছে বলে মনে হবে।
  • prefers-reduced-motion মিডিয়া কোয়েরি ব্যবহার করে রিডিউসড মোশনের জন্য ব্যবহারকারীর পছন্দকে সম্মান করুন।
  • বাধ্যতামূলক অ্যাক্সেসিবিলিটি রাউটিং: ভিউ ট্রানজিশন ডায়নামিকভাবে পৃষ্ঠার লেআউট পরিবর্তন করে, তবে প্রোগ্রাম্যাটিক ফোকাস ম্যানেজ করে না। ট্রানজিশনের সময় লুকানো বা সরিয়ে দেওয়া কোনও এলিমেন্টের উপর ফোকাস থাকলে, ফোকাস বাতিল হয়ে যায় এবং কীবোর্ড ও সহায়ক প্রযুক্তি ব্যবহারকারীরা কোনও প্রসঙ্গ ছাড়াই থেকে যান। DOM আপডেট হওয়ার সাথে সাথে বা ভিউ ট্রানজিশনের finished প্রমিস রিজলভ হলে, প্রোগ্রাম্যাটিক্যালি ফোকাস সরিয়ে আপডেট করা পৃষ্ঠার হেডিং বা ভিউ কন্টেনারে (tabindex="-1" ব্যবহার করে) নিয়ে যান।
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

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

View Transitions API প্রোগ্রেসিভ এনহ্যান্সমেন্টের জন্য ডিজাইন করা হয়েছে। যেসব ব্রাউজারে এটি কাজ করে না, সেগুলি অ্যানিমেশন ছাড়াই DOM আপডেট এক্সিকিউট করবে।

function navigate(){
  if (!document.startViewTransition) {
    // Fallback: Just update the DOM
    updateDOM();
  } else {
    document.startViewTransition(() => updateDOM());
  }
}