টেক্সট রেঞ্জ হাইলাইট করুন

ওয়েব ফিচার: কাস্টম হাইলাইট

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

কোর ইমপ্লিমেন্টেশন

টেক্সট রেঞ্জ হাইলাইট করতে, আপনাকে অবশ্যই টার্গেট টেক্সট নোড সংগ্রহ করতে হবে, Range এবং Highlight অবজেক্ট তৈরি করতে হবে, HighlightRegistry-এ সেগুলি রেজিস্টার করতে হবে এবং তারপরে ::highlight() ছদ্ম-এলিমেন্ট দিয়ে স্টাইল করতে হবে।

১. টেক্সট নোড সংগ্রহ করে রেঞ্জ তৈরি করুন

টার্গেট এলিমেন্টে সব টেক্সট নোড সংগ্রহ করতে TreeWalker ব্যবহার করুন, তারপর আপনি যে ক্যারেক্টার অফসেট হাইলাইট করতে চান সেটির দিকে পয়েন্ট করে Range অবজেক্ট তৈরি করুন।

const article = document.querySelector("article");

// MANDATORY: Use TreeWalker to collect text nodes — do not manipulate innerHTML.
const treeWalker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
const allTextNodes = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
  allTextNodes.push(currentNode);
  currentNode = treeWalker.nextNode();
}

// MANDATORY: Set range start/end on text nodes, not element nodes.
const range = new Range();
range.setStart(textNode, matchStartIndex);
range.setEnd(textNode, matchEndIndex);

টেক্সট-নোড লিস্ট ক্যাশে করুন এবং DOM কন্টেন্ট আসলে পরিবর্তন হলে তবেই এটি আবার তৈরি করুন, কারণ ট্রি ওয়াকিংয়ের জন্য অনেক খরচ হয়।

২. রেঞ্জ থেকে হাইলাইট তৈরি করুন

এক বা একাধিক Range অবজেক্টকে Highlight-এ গ্রুপ করুন। একই স্টাইল শেয়ার করে এমন একাধিক রেঞ্জ একটি হাইলাইটের মধ্যে পড়ে।

const searchHighlight = new Highlight(...matchingRanges);

৩. রেজিস্ট্রিতে হাইলাইট রেজিস্টার করুন

CSS.highlights ব্যবহার করে প্রতিটি Highlight কাস্টম নামে রেজিস্টার করুন, যা Map-এর মতো HighlightRegistry।

// MANDATORY: Clear previous highlights before registering new ones
// to avoid stale ranges persisting on the page.
CSS.highlights.clear();

CSS.highlights.set("search-results", searchHighlight);

একাধিক হাইলাইট ওভারল্যাপ করলে, স্ট্যাকিং অর্ডার নিয়ন্ত্রণ করতে priority প্রপার্টি ব্যবহার করুন। বেশি অগ্রাধিকারের হাইলাইট উপরে দেখানো হয়।

const primary = new Highlight(...primaryRanges);
primary.priority = 1;

const secondary = new Highlight(...secondaryRanges);
secondary.priority = 0; // painted first (behind primary)

CSS.highlights.set("primary", primary);
CSS.highlights.set("secondary", secondary);

৪. ::highlight() সহ স্টাইল

নাম অনুযায়ী প্রতিটি রেজিস্টার করা হাইলাইট স্টাইল করতে CSS-এ ::highlight() ছদ্ম-এলিমেন্ট ব্যবহার করুন।

::highlight(search-results) {
  background-color: #ffdd00;
  color: black;
}

::highlight()-এর মধ্যে CSS প্রপার্টির সীমিত সেট কাজ করে: color, background-color, text-decoration এবং এর লংহ্যান্ড, text-shadow, -webkit-text-stroke-color, -webkit-text-fill-color এবং -webkit-text-stroke-width। background-image, font-size বা padding-এর মতো প্রপার্টি উপেক্ষা করা হয়।

অ্যাক্সেসিবিলিটি

AVOID: সিম্যান্টিক HTML-এর পরিবর্তে কাস্টম হাইলাইট ব্যবহার করা।

কাস্টম হাইলাইট শুধুমাত্র প্রেজেন্টেশনের জন্য এবং অ্যাক্সেসিবিলিটি ট্রিতে দেখানো হয় না। হাইলাইট করা টেক্সট যদি ডকুমেন্টের সাথে প্রাসঙ্গিক হয় (যেমন, ব্যবহারকারীর বেছে নেওয়া অনুচ্ছেদ), তাহলে তার পরিবর্তে <mark> ব্যবহার করুন। সার্চ ফলাফল বা সিনট্যাক্স কালারিংয়ের মতো ক্ষণস্থায়ী, শুধুমাত্র ভিজ্যুয়াল এফেক্টের জন্য কাস্টম হাইলাইট রিজার্ভ করুন।

অর্থ বোঝানোর জন্য হাইলাইট শুধুমাত্র রঙের উপর নির্ভর করা উচিত নয়। কোনও হাইলাইট যদি কোনও ত্রুটি নির্দেশ করে, তাহলে সেটিকে অন্য কোনও ভিজ্যুয়াল ইন্ডিকেটর যেমন text-decoration: wavy underline বা সংলগ্ন টেক্সট লেবেলের সাথে পেয়ার করুন। WCAG 2.1-এর প্রয়োজনীয়তা পূরণ করতে হাইলাইট ব্যাকগ্রাউন্ড ও টেক্সটের রঙের মধ্যে পর্যাপ্ত কন্ট্রাস্ট আছে কিনা তা নিশ্চিত করুন (সাধারণ টেক্সটের জন্য অন্তত ৪.৫:১)।

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

যেসব ব্রাউজার CSS কাস্টম হাইলাইট API-কে সমর্থন করে না, সেগুলির জন্য আপনাকে একটি ফাংশনাল বেস অভিজ্ঞতা প্রদান করতে হবে যেখানে ভিজ্যুয়াল হাইলাইট ছাড়াই টেক্সট এখনও পাঠযোগ্য।

API ব্যবহার করার আগে আপনি সহায়তা শনাক্ত করতে পারবেন:

if (CSS.highlights) {
  // CSS Custom Highlight API is supported.
} else {
  // Fallback: wrap matches in <mark> elements.
}

ব্যবহারকারীর অভিজ্ঞতার জন্য হাইলাইট করা গুরুত্বপূর্ণ হলে, <mark> এলিমেন্টে ম্যাচ করা টেক্সট র‍্যাপ করার পদ্ধতিতে ফিরে যান। এটি DOM-এ পরিবর্তন করে, তাই ইভেন্ট লিসনারকে সুরক্ষিত রাখতে এবং ডকুমেন্টের স্ট্রাকচার যাতে ভেঙে না যায় সেই ব্যাপারে সতর্ক থাকুন।

if (!CSS.highlights) {
  // Walk text nodes and wrap matches in <mark>, preserving structure.
  const walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
  const nodes = [];
  for (let n = walker.nextNode(); n; n = walker.nextNode()) nodes.push(n);

  const term = searchTerm.toLowerCase();
  for (const textNode of nodes) {
    const text = textNode.textContent;
    let pos = text.toLowerCase().indexOf(term);
    if (pos === -1) continue;

    const frag = document.createDocumentFragment();
    let last = 0;
    while (pos !== -1) {
      frag.append(text.slice(last, pos));
      const mark = document.createElement("mark");
      // textContent assignment avoids HTML injection.
      mark.textContent = text.slice(pos, pos + term.length);
      frag.append(mark);
      last = pos + term.length;
      pos = text.toLowerCase().indexOf(term, last);
    }
    frag.append(text.slice(last));
    textNode.replaceWith(frag);
  }
}