Shadow DOM 301

উন্নত ধারণা ও DOM API

Shadow DOM-এর মাধ্যমে আপনি আর কী কী দারুণ কাজ করতে পারবেন, এই নিবন্ধে সেই বিষয়ে আরও আলোচনা করা হয়েছে! এটি Shadow DOM 101 এবং Shadow DOM 201-এ আলোচিত ধারণার উপর ভিত্তি করে তৈরি করা হয়েছে।

একাধিক শ্যাডো রুট ব্যবহার করা হচ্ছে

আপনি যদি কোনও পার্টির আয়োজন করেন, তাহলে সবাই একই ঘরে থাকলে দমবন্ধ হয়ে যেতে পারে। আপনি একাধিক রুমে লোকজনকে ভাগ করে দেওয়ার বিকল্প চান। এলিমেন্ট হোস্টিং শ্যাডো DOM-ও এটি করতে পারে, অর্থাৎ, এটি একসাথে একাধিক শ্যাডো রুট হোস্ট করতে পারে।

আসুন দেখা যাক, কোনও হোস্টে একাধিক শ্যাডো রুট অ্যাটাচ করার চেষ্টা করলে কী হয়:

<div id="example1">Light DOM</div>
<script>
  var container = document.querySelector('#example1');
  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<div>Root 1 FTW</div>';
  root2.innerHTML = '<div>Root 2 FTW</div>';
</script>

আমরা আগে থেকেই একটি শ্যাডো ট্রি অ্যাটাচ করে রাখলেও, "Root 2 FTW" রেন্ডার হয়। এর কারণ হল, কোনও হোস্টে যোগ করা শেষ শ্যাডো ট্রিটিই জয়ী হয়। রেন্ডারিংয়ের ক্ষেত্রে এটি একটি LIFO স্ট্যাক। DevTools পরীক্ষা করে এই আচরণ যাচাই করা যায়।

তাহলে, একাধিক শ্যাডো ব্যবহার করার কী প্রয়োজন যদি শুধুমাত্র শেষটিকে রেন্ডারিং পার্টিতে আমন্ত্রণ জানানো হয়? শ্যাডো ইনসার্শন পয়েন্ট লিখুন।

শ্যাডো ইনসার্শন পয়েন্ট

"শ্যাডো ইনসার্শন পয়েন্ট" (<shadow>) সাধারণ ইনসার্শন পয়েন্ট (<content>)-এর মতোই, কারণ সেগুলিও প্লেসহোল্ডার। তবে, হোস্টের কন্টেন্টের জন্য প্লেসহোল্ডার হওয়ার পরিবর্তে, তারা অন্যান্য শ্যাডো ট্রির জন্য হোস্ট হিসেবে কাজ করে। এটি হল শ্যাডো ডিওএম (DOM) ইনসেপশন!

আপনি হয়ত বুঝতেই পারছেন, আপনি যত গভীরে যাবেন, পরিস্থিতি তত জটিল হবে। এই কারণে, একাধিক <shadow> এলিমেন্ট থাকলে কী হবে তা স্পেসিফিকেশনে খুব স্পষ্টভাবে উল্লেখ করা আছে:

আমাদের আসল উদাহরণে ফিরে গিয়ে দেখা যাক, প্রথম শ্যাডো root1 আমন্ত্রণের তালিকা থেকে বাদ পড়ে গেছে। <shadow> ইনসার্শন পয়েন্ট যোগ করলে এটি ফিরে আসে:

<div id="example2">Light DOM</div>
<script>
var container = document.querySelector('#example2');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();
root1.innerHTML = '<div>Root 1 FTW</div><content></content>';
**root2.innerHTML = '<div>Root 2 FTW</div><shadow></shadow>';**
</script>

এই উদাহরণের ব্যাপারে কয়েকটি আকর্ষণীয় বিষয় আছে:

  1. "Root 2 FTW" এখনও "Root 1 FTW"-এর উপরে রেন্ডার করে। এটি হওয়ার কারণ হল, আমরা যেখানে <shadow> ইনসার্শন পয়েন্ট প্লেস করেছি। আপনি যদি উল্টোটা চান, তাহলে ইনসার্শন পয়েন্ট সরান: root2.innerHTML = '<shadow></shadow><div>Root 2 FTW</div>';।
  2. লক্ষ্য করুন যে root1-এ এখন একটি <content> ইনসার্শন পয়েন্ট আছে। এর ফলে রেন্ডারিংয়ের সময় টেক্সট নোড "লাইট DOM" একসাথে কাজ করে।

<shadow>-এ কী রেন্ডার করা হয়েছে?

কখনও কখনও <shadow>-এ রেন্ডার করা পুরনো শ্যাডো ট্রি সম্পর্কে জানাটা কাজে দেয়। আপনি .olderShadowRoot-এর মাধ্যমে সেই ট্রি সম্পর্কে রেফারেন্স পেতে পারেন:

**root2.olderShadowRoot** === root1 //true

হোস্টের শ্যাডো রুট পাওয়া

কোনও এলিমেন্ট শ্যাডো DOM হোস্ট করলে, আপনি .shadowRoot ব্যবহার করে এর সবচেয়ে নতুন শ্যাডো রুট অ্যাক্সেস করতে পারবেন:

var root = host.createShadowRoot();
console.log(host.shadowRoot === root); // true
console.log(document.body.shadowRoot); // null

আপনার শ্যাডোতে লোকজন ঢুকে পড়া নিয়ে চিন্তিত হলে, .shadowRoot-কে নাল হিসেবে আবার সংজ্ঞায়িত করুন:

Object.defineProperty(host, 'shadowRoot', {
  get: function() { return null; },
  set: function(value) { }
});

একটু হ্যাক, তবে এটি কাজ করে। পরিশেষে, এটি মনে রাখা গুরুত্বপূর্ণ যে, অবিশ্বাস্যভাবে চমৎকার হলেও, Shadow DOM-কে নিরাপত্তা ফিচার হিসেবে ডিজাইন করা হয়নি। সম্পূর্ণ কন্টেন্ট আইসোলেশনের জন্য এটির উপর নির্ভর করবেন না।

JS-এ Shadow DOM তৈরি করা

আপনি JS-এ DOM তৈরি করতে চাইলে, HTMLContentElement এবং HTMLShadowElement-এ তার জন্য ইন্টারফেস আছে।

<div id="example3">
  <span>Light DOM</span>
</div>
<script>
var container = document.querySelector('#example3');
var root1 = container.createShadowRoot();
var root2 = container.createShadowRoot();

var div = document.createElement('div');
div.textContent = 'Root 1 FTW';
root1.appendChild(div);

 // HTMLContentElement
var content = document.createElement('content');
content.select = 'span'; // selects any spans the host node contains
root1.appendChild(content);

var div = document.createElement('div');
div.textContent = 'Root 2 FTW';
root2.appendChild(div);

// HTMLShadowElement
var shadow = document.createElement('shadow');
root2.appendChild(shadow);
</script>

এই উদাহরণটি আগের বিভাগে দেওয়া উদাহরণের প্রায় অনুরূপ। একমাত্র পার্থক্য হল, এখন আমি নতুন যোগ করা <span> বের করতে select ব্যবহার করছি।

ইনসার্শন পয়েন্ট নিয়ে কাজ করা

হোস্ট এলিমেন্ট থেকে বেছে নেওয়া এবং শ্যাডো ট্রিতে "ডিস্ট্রিবিউট" করা নোডকে বলা হয়…ড্রামরোল…ডিস্ট্রিবিউটেড নোড! ইনসার্শন পয়েন্ট আমন্ত্রণ জানালে তারা শ্যাডো বাউন্ডারি অতিক্রম করতে পারে।

ইনসার্শন পয়েন্টের ব্যাপারে ধারণাগতভাবে অদ্ভুত বিষয় হল যে এগুলি শারীরিকভাবে DOM সরায় না। হোস্টের নোড অপরিবর্তিত থাকে। ইনসার্শন পয়েন্ট শুধুমাত্র হোস্ট থেকে শ্যাডো ট্রিতে নোড রি-প্রোজেক্ট করে। It's a presentation/rendering thing: "Move these nodes over here" "Render these nodes at this location."

যেমন:

<div><h2>Light DOM</h2></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = '<content select="h2"></content>';

var h2 = document.querySelector('h2');
console.log(root.querySelector('content[select="h2"] h2')); // null;
console.log(root.querySelector('content').contains(h2)); // false
</script>

Voilà! h2 শ্যাডো DOM-এর চাইল্ড নয়। এর থেকে আরেকটি তথ্য জানা যায়:

Element.getDistributedNodes()

আমরা <content>-এ ট্রাভার্স করতে পারি না, তবে .getDistributedNodes() API আমাদের ইনসার্শন পয়েন্টে ডিস্ট্রিবিউটেড নোড কোয়েরি করার অনুমতি দেয়:

<div id="example4">
  <h2>Eric</h2>
  <h2>Bidelman</h2>
  <div>Digital Jedi</div>
  <h4>footer text</h4>
</div>

<template id="sdom">
  <header>
    <content select="h2"></content>
  </header>
  <section>
    <content select="div"></content>
  </section>
  <footer>
    <content select="h4:first-of-type"></content>
  </footer>
</template>

<script>
var container = document.querySelector('#example4');

var root = container.createShadowRoot();

var t = document.querySelector('#sdom');
var clone = document.importNode(t.content, true);
root.appendChild(clone);

var html = [];
[].forEach.call(root.querySelectorAll('content'), function(el) {
  html.push(el.outerHTML + ': ');
  var nodes = el.getDistributedNodes();
  [].forEach.call(nodes, function(node) {
    html.push(node.outerHTML);
  });
  html.push('\n');
});
</script>

Element.getDestinationInsertionPoints()

.getDistributedNodes()-এর মতো, আপনি .getDestinationInsertionPoints() কল করে চেক করতে পারবেন যে একটি নোড কোন ইনসার্শন পয়েন্টে ডিস্ট্রিবিউট করা হয়েছে:

<div id="host">
  <h2>Light DOM
</div>

<script>
  var container = document.querySelector('div');

  var root1 = container.createShadowRoot();
  var root2 = container.createShadowRoot();
  root1.innerHTML = '<content select="h2"></content>';
  root2.innerHTML = '<shadow></shadow>';

  var h2 = document.querySelector('#host h2');
  var insertionPoints = h2.getDestinationInsertionPoints();
  [].forEach.call(insertionPoints, function(contentEl) {
    console.log(contentEl);
  });
</script>

টুল: Shadow DOM Visualizer

Shadow DOM-এর মতো ব্ল্যাক ম্যাজিক বোঝা কঠিন। আমার মনে আছে, প্রথমবার আমি এটি বোঝার চেষ্টা করছিলাম।

Shadow DOM রেন্ডারিং কীভাবে কাজ করে তা ভিজ্যুয়ালাইজ করতে সাহায্য করার জন্য, আমি d3.js ব্যবহার করে একটি টুল তৈরি করেছি। বাঁদিকে থাকা দুটি মার্কআপ বক্সই এডিট করা যায়। আপনার নিজস্ব মার্কআপ পেস্ট করতে এবং জিনিসগুলি কীভাবে কাজ করে তা দেখতে নির্দ্বিধায় খেলুন এবং ইনসার্শন পয়েন্ট শ্যাডো ট্রিতে হোস্ট নোডগুলিকে সুইজল করে।

Shadow DOM ভিজ্যুয়ালাইজার
Shadow DOM Visualizer লঞ্চ করুন

এটি ব্যবহার করে দেখুন এবং আপনার মতামত আমাকে জানান!

ইভেন্ট মডেল

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

Play অ্যাকশন ১

  • এটি আকর্ষণীয়। হোস্ট এলিমেন্ট (<div data-host>) থেকে নীল নোড পর্যন্ত একটি mouseout দেখতে পাবেন। এটি ডিস্ট্রিবিউটেড নোড হলেও, এটি এখনও হোস্টে আছে, ShadowDOM-এ নয়। হলুদ রঙের আরও নিচে মাউস নিয়ে গেলে আবার নীল নোডে mouseout হয়।

Play অ্যাকশন ২

  • হোস্টে একটি mouseout দেখা যায় (একেবারে শেষে)। সাধারণত আপনি সব হলুদ ব্লকের জন্য mouseoutটি ইভেন্ট ট্রিগার দেখতে পাবেন। তবে, এই ক্ষেত্রে এইসব এলিমেন্ট Shadow DOM-এর ইন্টার্নাল এবং ইভেন্ট এর উপরের বাউন্ডারির মাধ্যমে বাবল হয় না।

অ্যাকশন ৩ চালান

  • লক্ষ্য করুন যে আপনি ইনপুটে ক্লিক করলে, focusin ইনপুটে নয়, হোস্ট নোডে দেখা যায়। এটি আবার টার্গেট করা হয়েছে!

যেসব ইভেন্ট সবসময় বন্ধ থাকে

নিম্নলিখিত ইভেন্টগুলি কখনই শ্যাডো বাউন্ডারি অতিক্রম করে না:

  • বন্ধ কর
  • সমস্যা
  • বেছে নিন
  • পরিবর্তন
  • লোড
  • রিসেট করো
  • ছোট বড় করা
  • স্ক্রল
  • selectstart

উপসংহার

আশা করি আপনিও একমত হবেন যে Shadow DOM অত্যন্ত শক্তিশালী। প্রথমবারের মতো, আমাদের কাছে <iframe> বা অন্যান্য পুরনো টেকনিকের অতিরিক্ত বোঝা ছাড়াই সঠিক এনক্যাপসুলেশন রয়েছে।

Shadow DOM অবশ্যই জটিল, তবে এটি ওয়েব প্ল্যাটফর্মে যোগ করার মতো একটি ফিচার। এটির সাথে কিছুটা সময় কাটান। এটির বিষয়ে জানুন। প্রশ্ন জিজ্ঞাসা করুন।

আপনি আরও জানতে চাইলে, ডমিনিকের লেখা Shadow DOM 101 এবং আমার লেখা Shadow DOM 201: CSS & Styling নিবন্ধটি পড়ুন।