Shadow DOM 201

CSS و استایل

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

معرفی

بیا با آن روبرو شویم. هیچ چیز جذابی در مورد نشانه گذاری بدون استایل وجود ندارد. برای ما خوش شانس بود، افراد باهوش پشت کامپوننت های وب این را پیش بینی کردند و ما را معلق نگذاشتند. CSS Scoping Module گزینه های زیادی را برای استایل دادن به محتوا در درخت سایه تعریف می کند.

کپسوله سازی سبک

یکی از ویژگی های اصلی Shadow DOM مرز سایه است. این ویژگی های بسیار خوبی دارد، اما یکی از بهترین ها این است که کپسوله سازی سبک را به صورت رایگان ارائه می دهد. به گونه ای دیگر بیان کرد:

<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
  <style>
    h3 {
      color: red;
    }
  </style>
  <h3>Shadow DOM</h3>
`;
</script>

دو نکته جالب در مورد این دمو وجود دارد:

  • h3 های دیگری نیز در این صفحه وجود دارند، اما تنها موردی که با انتخابگر h3 مطابقت دارد و به همین دلیل استایل قرمز دارد، همانی است که در ShadowRoot قرار دارد. باز هم به صورت پیش‌فرض، سبک‌های scoped.
  • سایر قوانین سبک تعریف شده در این صفحه که h3s را هدف قرار می دهند در محتوای من وارد نمی شوند. دلیلش این است که انتخابگرها از مرز سایه عبور نمی کنند .

اخلاق داستان؟ ما کپسولاسیون سبک از دنیای خارج داریم. با تشکر Shadow DOM!

سبک دادن به عنصر میزبان

:host به شما امکان می دهد عنصری را که میزبان درخت سایه است انتخاب و استایل دهید:

<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
  <style>
    :host {
      text-transform: uppercase;
    }
  </style>
  <content></content>
`;
</script>

یک نکته مهم این است که قوانین در صفحه والد دارای ویژگی بالاتری نسبت به قوانین :host تعریف شده در عنصر هستند، اما ویژگی کمتری نسبت به ویژگی style تعریف شده در عنصر میزبان دارند. این به کاربران این امکان را می‌دهد که از بیرون استایل شما را نادیده بگیرند. :host همچنین فقط در زمینه ShadowRoot کار می کند، بنابراین نمی توانید از آن خارج از Shadow DOM استفاده کنید.

شکل عملکردی :host(<selector>) به شما امکان می دهد عنصر میزبان را در صورت مطابقت با <selector> مورد هدف قرار دهید.

مثال - فقط در صورتی مطابقت داشته باشید که خود عنصر دارای کلاس .different باشد (به عنوان مثال <x-foo class="different"></x-foo> ):

:host(.different) {
    ...
}

واکنش به وضعیت کاربر

یک مورد رایج برای :host زمانی است که شما در حال ایجاد یک عنصر سفارشی هستید و می خواهید به حالت های مختلف کاربر (:hover، :focus، :active، و غیره) واکنش نشان دهید.

<style>
  :host {
    opacity: 0.4;
    transition: opacity 420ms ease-in-out;
  }
  :host(:hover) {
    opacity: 1;
  }
  :host(:active) {
    position: relative;
    top: 3px;
    left: 3px;
  }
</style>

موضوع بندی یک عنصر

کلاس شبه :host-context(<selector>) با عنصر میزبان مطابقت دارد اگر عنصر یا یکی از اجدادش با <selector> مطابقت داشته باشد.

استفاده رایج از :host-context() برای قالب بندی یک عنصر بر اساس محیط اطراف آن است. به عنوان مثال، بسیاری از افراد با اعمال یک کلاس در <html> یا <body> قالب بندی را انجام می دهند:

<body class="different">
  <x-foo></x-foo>
</body>

شما می توانید :host-context(.different) به <x-foo> استایل دهید، زمانی که این عنصر از نسل عنصری با کلاس .different است:

:host-context(.different) {
  color: red;
}

این به شما این امکان را می دهد که قوانین سبک را در Shadow DOM یک عنصر محصور کنید که به طور منحصر به فرد آن را بر اساس زمینه آن استایل کنید.

پشتیبانی از چندین نوع میزبان از درون یک ریشه سایه

یکی دیگر از کاربردهای :host این است که شما در حال ایجاد یک کتابخانه موضوعی هستید و می خواهید از استایل سازی بسیاری از عناصر میزبان از داخل همان Shadow DOM پشتیبانی کنید.

:host(x-foo) {
    /* Applies if the host is a <x-foo> element.*/
}

:host(x-foo:host) {
    /* Same as above. Applies if the host is a <x-foo> element. */
}

:host(div) {
    /* Applies if the host element is a <div>. */
}

طراحی داخلی Shadow DOM از بیرون

شبه عنصر ::shadow و ترکیب کننده /deep/ مانند داشتن یک شمشیر Vorpal با قدرت CSS هستند. آنها اجازه می دهند از مرز Shadow DOM عبور کنند تا عناصر را در درختان سایه قرار دهند.

شبه عنصر ::shadow

اگر عنصری حداقل یک درخت سایه داشته باشد، شبه عنصر ::shadow با ریشه سایه مطابقت دارد. این به شما امکان می دهد انتخابگرهایی بنویسید که گره های داخلی را در سایه یک عنصر استایل می دهند.

برای مثال، اگر عنصری میزبان یک ریشه سایه باشد، می‌توانید #host::shadow span {} بنویسید تا به تمام دهانه‌های درون درخت سایه آن استایل دهید.

<style>
  #host::shadow span {
    color: red;
  }
</style>

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

<script>
  var host = document.querySelector('div');
  var root = host.createShadowRoot();
  root.innerHTML = `
    <span>Shadow DOM</span>
    <content></content>
  `;
</script>

مثال (عناصر سفارشی) - <x-tabs> دارای <x-panel> در Shadow DOM خود است. هر پانل میزبان درخت سایه خود است که شامل سرفصل های h2 است. برای استایل دادن به آن عناوین از صفحه اصلی، می توان نوشت:

x-tabs::shadow x-panel::shadow h2 {
    ...
}

ترکیب کننده /deep/

ترکیب کننده /deep/ شبیه ::shadow است، اما قدرتمندتر است. به طور کامل تمام مرزهای سایه را نادیده می گیرد و به هر تعداد درخت سایه رد می شود. به زبان ساده، /deep/ به شما این امکان را می‌دهد که درون یک عنصر سوراخ کنید و هر گره‌ای را هدف قرار دهید.

ترکیب‌کننده /deep/ مخصوصاً در دنیای عناصر سفارشی که معمولاً چندین سطح Shadow DOM وجود دارد مفید است. نمونه های اصلی عبارتند از تودرتو کردن یک دسته از عناصر سفارشی (هر کدام میزبان درخت سایه خود هستند) یا ایجاد عنصری که از دیگری با استفاده از <shadow> به ارث می برد.

مثال (عناصر سفارشی) - همه عناصر <x-panel> را که از فرزندان <x-tabs> هستند، در هر نقطه از درخت انتخاب کنید:

x-tabs /deep/ x-panel {
    ...
}

مثال - به همه عناصر با کلاس .library-theme ، در هر نقطه از درخت سایه، استایل دهید:

body /deep/ .library-theme {
    ...
}

کار با querySelector()

درست مانند .shadowRoot درخت‌های سایه را برای پیمایش DOM باز می‌کند، ترکیب‌کننده‌ها نیز درخت‌های سایه را برای پیمایش انتخابگر باز می‌کنند. به جای نوشتن یک زنجیره تو در تو از جنون، می توانید یک جمله بنویسید:

// No fun.
document.querySelector('x-tabs').shadowRoot
        .querySelector('x-panel').shadowRoot
        .querySelector('#foo');

// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');

سبک دادن به عناصر بومی

کنترل‌های HTML بومی چالشی برای استایل کردن هستند. بسیاری از مردم به سادگی تسلیم می شوند و خود را می اندازند. با این حال، با ::shadow و /deep/ ، هر عنصری در پلتفرم وب که از Shadow DOM استفاده می کند، می تواند استایل دهی شود. نمونه های عالی عبارتند از انواع <input> و <video> :

video /deep/ input[type="range"] {
  background: hotpink;
}

ایجاد قلاب های سبک

سفارشی سازی خوب است. در موارد خاص، ممکن است بخواهید سوراخ هایی را در سپر استایل Shadow خود ایجاد کنید و قلاب هایی را برای دیگران ایجاد کنید.

استفاده از ::shadow و /deep/

قدرت زیادی پشت /deep/ نهفته است. این به نویسندگان مؤلفه راهی می‌دهد تا عناصر فردی را به‌عنوان سبک‌پذیر یا تعدادی از عناصر را به‌عنوان موضوع‌دار تعیین کنند.

مثال - به تمام عناصری که دارای کلاس .library-theme هستند، سبک دهید، بدون توجه به همه درختان سایه:

body /deep/ .library-theme {
    ...
}

استفاده از عناصر شبه سفارشی

هر دو WebKit و Firefox عناصر شبه را برای استایل دادن به قطعات داخلی عناصر مرورگر بومی تعریف می کنند. یک مثال خوب input[type=range] است. با هدف قرار دادن ::-webkit-slider-thumb : می توانید به انگشت شست اسلایدر <span style="color:blue">blue</span> استایل دهید.

input[type=range].custom::-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: blue;
  width: 10px;
  height: 40px;
}

مشابه نحوه ارائه قلاب‌های استایل توسط مرورگرها به برخی از قسمت‌های داخلی، نویسندگان محتوای Shadow DOM می‌توانند عناصر خاصی را به‌عنوان قابل استایل توسط افراد خارجی تعیین کنند. این کار از طریق عناصر شبه سفارشی انجام می شود.

با استفاده از ویژگی pseudo می توانید یک عنصر را به عنوان یک عنصر شبه سفارشی تعیین کنید. مقدار یا نام آن باید با "x-" پیشوند شود. انجام این کار ارتباطی با آن عنصر در درخت سایه ایجاد می کند و به افراد خارجی یک خط تعیین شده برای عبور از مرز سایه می دهد.

در اینجا مثالی از ایجاد یک ویجت لغزنده سفارشی و اجازه دادن به شخصی برای استایل آبی انگشت شست اسلایدر خود آورده شده است:

<style>
  #host::x-slider-thumb {
    background-color: blue;
  }
</style>
<div id="host"></div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <div>
      <div pseudo="x-slider-thumb"></div>' +
    </div>
  `;
</script>

استفاده از متغیرهای CSS

یک راه قدرتمند برای ایجاد قلاب های موضوعی از طریق متغیرهای CSS خواهد بود. اساساً، ایجاد "جایگزین‌های سبک" برای سایر کاربران برای پر کردن.

یک نویسنده عنصر سفارشی را تصور کنید که متغیرهای متغیر را در Shadow DOM خود علامت گذاری می کند. یکی برای استایل دادن به فونت یک دکمه داخلی و دیگری برای رنگ آن:

button {
  color: var(--button-text-color, pink); /* default color will be pink */
  font-family: var(--button-font);
}

سپس، embedder عنصر آن مقادیر را به دلخواه خود تعریف می کند. شاید برای مطابقت با موضوع فوق العاده جالب Comic Sans صفحه خودشان:

#host {
  --button-text-color: green;
  --button-font: "Comic Sans MS", "Comic Sans", cursive;
}

با توجه به روشی که متغیرهای CSS به ارث می برند، همه چیز هلویی است و به زیبایی کار می کند! کل تصویر به این صورت است:

<style>
  #host {
    --button-text-color: green;
    --button-font: "Comic Sans MS", "Comic Sans", cursive;
  }
</style>
<div id="host">Host node</div>
<script>
  var root = document.querySelector('#host').createShadowRoot();
  root.innerHTML = `
    <style>
      button {
        color: var(--button-text-color, pink);
        font-family: var(--button-font);
      }
    </style>
    <content></content>
  `;
</script>

بازنشانی سبک ها

سبک های ارثی مانند فونت ها، رنگ ها و ارتفاع خط همچنان بر عناصر موجود در Shadow DOM تأثیر می گذارند. اما برای حداکثر انعطاف پذیری، Shadow DOM ویژگی resetStyleInheritance را به ما می دهد تا آنچه را که در مرز سایه اتفاق می افتد کنترل کنیم. به آن به عنوان راهی برای شروع تازه در هنگام ایجاد یک جزء جدید فکر کنید.

resetStyleInheritance

  • false - پیش فرض. ویژگی های CSS ارثی همچنان به ارث می برند.
  • true - ویژگی های ارثی را به initial در مرز بازنشانی می کند.

در زیر یک نسخه نمایشی است که نشان می دهد چگونه درخت سایه با تغییر resetStyleInheritance تحت تاثیر قرار می گیرد:

<div>
  <h3>Light DOM</h3>
</div>

<script>
  var root = document.querySelector('div').createShadowRoot();
  root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
  root.innerHTML = `
    <style>
      h3 {
        color: red;
      }
    </style>
    <h3>Shadow DOM</h3>
    <content select="h3"></content>
  `;
</script>

<div class="demoarea" style="width:225px;">
  <div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
  <button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>

<script>
  var container = document.querySelector('#style-ex-inheritance');
  var root = container.createShadowRoot();
  //root.resetStyleInheritance = false;
  root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';

  document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
    root.resetStyleInheritance = !root.resetStyleInheritance;
    e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
    document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
  });
</script>
ویژگی‌های DevTools به ارث رسیده است

درک .resetStyleInheritance کمی پیچیده‌تر است، در درجه اول به این دلیل که فقط روی ویژگی‌های CSS که قابل ارثی هستند تأثیر می‌گذارد. می گوید: هنگامی که به دنبال یک ویژگی برای ارث بردن هستید، در مرز بین صفحه و ShadowRoot، مقادیر را از میزبان به ارث نبرید بلکه از مقدار initial استفاده کنید (بر اساس مشخصات CSS).

اگر مطمئن نیستید که کدام ویژگی‌ها در CSS به ارث می‌برند، این فهرست مفید را بررسی کنید یا کادر انتخاب «نمایش به ارث رسیده» را در پانل عنصر تغییر دهید.

یک ظاهر طراحی شده برای گره های توزیع شده

گره های توزیع شده عناصری هستند که در یک نقطه درج (یک عنصر <content> ) ارائه می شوند. عنصر <content> به شما این امکان را می دهد که گره ها را از Light DOM انتخاب کرده و آنها را در مکان های از پیش تعریف شده در Shadow DOM خود رندر کنید. آنها به طور منطقی در Shadow DOM نیستند. آنها هنوز هم فرزندان عنصر میزبان هستند. نقاط درج فقط یک چیز رندر هستند.

گره های توزیع شده سبک های سند اصلی را حفظ می کنند. یعنی قوانین استایل از صفحه اصلی همچنان در مورد عناصر اعمال می شود، حتی زمانی که آنها در یک نقطه درج ارائه می شوند. باز هم، گره های توزیع شده هنوز به طور منطقی در نور dom هستند و حرکت نمی کنند. آنها فقط در جای دیگر رندر می دهند. با این حال، هنگامی که گره ها در Shadow DOM توزیع می شوند، می توانند سبک های اضافی تعریف شده در درخت سایه را به خود بگیرند.

:: عنصر شبه محتوا

گره های توزیع شده فرزندان عنصر میزبان هستند، بنابراین چگونه می توانیم آنها را از داخل Shadow DOM هدف قرار دهیم؟ پاسخ عنصر شبه CSS ::content است. این راهی برای هدف قرار دادن گره های Light DOM است که از یک نقطه درج عبور می کنند. مثلا:

::content > h3 هر تگ h3 را که از یک نقطه درج عبور می کند استایل می کند.

بیایید یک مثال را ببینیم:

<div>
  <h3>Light DOM</h3>
  <section>
    <div>I'm not underlined</div>
    <p>I'm underlined in Shadow DOM!</p>
  </section>
</div>

<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
  <style>
    h3 { color: red; }
      content[select="h3"]::content > h3 {
      color: green;
    }
    ::content section p {
      text-decoration: underline;
    }
  </style>
  <h3>Shadow DOM</h3>
  <content select="h3"></content>
  <content select="section"></content>
`;
</script>

بازنشانی سبک ها در نقاط درج

هنگام ایجاد ShadowRoot، می توانید سبک های ارثی را بازنشانی کنید. نقاط درج <content> و <shadow> نیز این گزینه را دارند. هنگام استفاده از این عناصر، یا .resetStyleInheritance را در JS تنظیم کنید یا از ویژگی بولین reset-style-inheritance در خود عنصر استفاده کنید.

  • برای نقاط درج ShadowRoot یا <shadow> : reset-style-inheritance به این معنی است که ویژگی‌های CSS قابل initial بر روی میزبان تنظیم می‌شوند، قبل از اینکه به محتوای سایه شما ضربه بزنند. این مکان به عنوان مرز بالا شناخته می شود .

  • برای نقاط درج <content> : reset-style-inheritance به این معنی است که ویژگی‌های CSS قابل ارث بری قبل از توزیع فرزندان میزبان در نقطه درج initial تنظیم می‌شوند. این مکان به عنوان مرز پایین شناخته می شود .

نتیجه

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

Shadow DOM به ما کپسوله‌سازی سبک و وسیله‌ای می‌دهد تا به همان اندازه (یا کمتر) از دنیای بیرون را که انتخاب می‌کنیم راه دهیم. با تعریف عناصر شبه سفارشی یا گنجاندن متغیرهای متغیر CSS، نویسندگان می‌توانند قلاب‌های سبک مناسب برای شخص ثالث برای سفارشی‌سازی بیشتر محتوای خود فراهم کنند. در مجموع، نویسندگان وب کنترل کاملی بر نحوه نمایش محتوای خود دارند.