سایه DOM 301

مفاهیم پیشرفته و API های DOM

این مقاله بیشتر از کارهای شگفت انگیزی که می توانید با Shadow DOM انجام دهید بحث می کند! این بر اساس مفاهیم مورد بحث در Shadow DOM 101 و Shadow DOM 201 است.

استفاده از چندین ریشه سایه

اگر میزبان مهمانی هستید، اگر همه در یک اتاق جمع شوند، خفه می شود. شما می خواهید گزینه توزیع گروه های افراد در چندین اتاق را داشته باشید. عناصر میزبان Shadow 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> ) هستند زیرا جای جای هستند. با این حال، به جای اینکه مکان‌هایی برای محتوای میزبان باشند، میزبان درخت‌های سایه دیگر هستند. این Shadow DOM Inception است!

همانطور که احتمالاً می توانید تصور کنید، هر چه بیشتر سوراخ خرگوش را سوراخ کنید، همه چیز پیچیده تر می شود. به همین دلیل، مشخصات در مورد اینکه چه اتفاقی می‌افتد وقتی چندین عنصر <shadow> در حال بازی هستند بسیار واضح است:

با نگاهی به مثال اصلی خود، اولین 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. توجه داشته باشید که اکنون یک نقطه درج <content> در root1 وجود دارد. این باعث می شود که گره متن "Light DOM" برای سوار شدن رندر همراه شود.

چه چیزی در <shadow> رندر شده است؟

گاهی اوقات دانستن درخت سایه قدیمی که در یک <shadow> رندر می شود مفید است. می توانید از طریق .olderShadowRoot به آن درخت ارجاع دهید:

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

به دست آوردن ریشه سایه میزبان

اگر عنصری میزبان Shadow DOM است، می توانید با استفاده از .shadowRoot به جوانترین ریشه سایه آن دسترسی داشته باشید:

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

اگر نگران ورود افراد به سایه های شما هستید، .shadowRoot مجدداً به صورت null تعریف کنید:

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

کمی هک است، اما کار می کند. در پایان، مهم است که به یاد داشته باشید که با وجود شگفت‌انگیز بودن، Shadow DOM برای یک ویژگی امنیتی طراحی نشده است . برای جداسازی کامل محتوا به آن تکیه نکنید.

ساخت Shadow DOM در JS

اگر ترجیح می دهید DOM را در JS بسازید، 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>

این مثال تقریباً مشابه نمونه در بخش قبل است. تنها تفاوت این است که اکنون من از select برای بیرون کشیدن <span> جدید اضافه شده استفاده می کنم.

کار با نقاط درج

گره هایی که از عنصر میزبان انتخاب می شوند و در درخت سایه "توزیع" می شوند، گره های توزیع شده ... Drumroll نامیده می شوند! هنگامی که نقاط درج آنها را به داخل دعوت می کند، اجازه دارند از مرز سایه عبور کنند.

چیزی که از نظر مفهومی در مورد نقاط درج عجیب است این است که آنها به صورت فیزیکی DOM را حرکت نمی دهند. گره های میزبان دست نخورده می مانند. نقاط درج صرفاً گره ها را از میزبان به درخت سایه بازپخش می کند. این یک چیز ارائه / ارائه است: "این گره ها را به اینجا منتقل کنید" "این گره ها را در این مکان رندر کنید."

به عنوان مثال:

<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> شویم، اما API .getDistributedNodes() به ما اجازه می‌دهد تا گره‌های توزیع‌شده را در یک نقطه درج جستجو کنیم:

<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 Visualizer
Shadow DOM Visualizer را اجرا کنید

آن را امتحان کنید و به من بگویید که چه فکر می کنید!

مدل رویداد

برخی رویدادها از مرز سایه عبور می کنند و برخی نه. در مواردی که رویدادها از مرز عبور می کنند، هدف رویداد به منظور حفظ کپسولاسیونی که مرز بالایی ریشه سایه فراهم می کند تنظیم می شود. به این معنا که رویدادها دوباره هدف‌گذاری می‌شوند تا به‌نظر برسند که از عنصر میزبان به‌جای عناصر داخلی به Shadow DOM آمده‌اند .

بازی اکشن 1

  • این یکی جالبه شما باید یک mouseout از عنصر میزبان ( <div data-host> ) به گره آبی را ببینید. حتی اگر یک گره توزیع شده است، هنوز در میزبان است، نه ShadowDOM. ماوس کردن دوباره به سمت پایین و به رنگ زرد باعث ایجاد mouseout روی گره آبی می شود.

بازی اکشن 2

  • یک mouseout وجود دارد که روی میزبان ظاهر می شود (در پایان). معمولاً می‌بینید که رویدادهای mouseout برای همه بلوک‌های زرد فعال می‌شوند. با این حال، در این مورد، این عناصر در Shadow DOM داخلی هستند و رویداد از مرز بالایی خود حباب نمی‌کند.

بازی اکشن 3

  • توجه داشته باشید که وقتی روی ورودی کلیک می کنید، focusin در ورودی ظاهر نمی شود، بلکه در خود گره میزبان ظاهر می شود. دوباره هدف گذاری شده است!

رویدادهایی که همیشه متوقف می شوند

رویدادهای زیر هرگز از مرز سایه نمی گذرند:

  • سقط
  • خطا
  • انتخاب کنید
  • تغییر دهید
  • بار
  • تنظیم مجدد
  • تغییر اندازه
  • اسکرول کنید
  • انتخاب شروع

نتیجه گیری

امیدوارم موافق باشید که Shadow DOM فوق العاده قدرتمند است . برای اولین بار، ما کپسوله سازی مناسب را بدون توشه اضافی <iframe> یا سایر تکنیک های قدیمی تر داریم.

Shadow DOM مطمئناً جانوری پیچیده است، اما حیوانی است که ارزش افزودن به پلتفرم وب را دارد. مدتی را با آن بگذرانید. آن را یاد بگیرید. سوال بپرسید.

اگر می‌خواهید بیشتر بدانید، به مقاله مقدماتی Dominic Shadow DOM 101 و Shadow DOM 201: CSS & Styling مراجعه کنید.