CSS والتصميم
تتناول هذه المقالة المزيد من الميزات الرائعة التي يمكنك الاستفادة منها في Shadow DOM. ويستند إلى المفاهيم التي تمت مناقشتها في أساسيات Shadow DOM. إذا كنت تبحث عن مقدمة، يمكنك الاطّلاع على هذه المقالة.
مقدمة
دعونا نواجه الأمر. لا يوجد شيء مثير حول الترميز غير المصمم. لحسن الحظ، توقّع الأشخاص الرائعون الذين طوّروا مكوّنات الويب هذا الأمر ولم يتركونا في حيرة من أمرنا. تحدّد وحدة تحديد نطاق CSS العديد من الخيارات لتنسيق المحتوى في شجرة ظل.
تغليف الأنماط
من الميزات الأساسية في 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. مرة أخرى، الأنماط محددة النطاق بشكل تلقائي.
- قواعد الأنماط الأخرى المحددة في هذه الصفحة والتي تستهدف 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 مع جذر الظل نفسه.
تتيح لك كتابة أدوات اختيار تحدّد نمط العُقد الداخلية في نموذج العناصر في المستند (DOM) المخفي لعنصر معيّن.
على سبيل المثال، إذا كان أحد العناصر يستضيف جذر ظل، يمكنك كتابة #host::shadow span {} لتنسيق جميع العناصر 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]. يمكنك تصميم شريط التمرير <span style="color:blue">blue</span> من خلال استهداف ::-webkit-slider-thumb:
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);
}
بعد ذلك، يحدّد مضمِّن العنصر هذه القيم حسب رغبته. ربما لتتناسب مع مظهر 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>
فهم .resetStyleInheritance أصعب قليلاً، ويرجع ذلك في الأساس إلى أنّه
يؤثر فقط في خصائص CSS التي يمكن توريثها. وهي تعني: عندما تبحث عن سمة لتوريثها، عند الحدود بين الصفحة وShadowRoot، لا تكتسب القيم من المضيف ولكن استخدِم القيمة initial بدلاً من ذلك (وفقًا لمواصفات CSS).
إذا لم تكن متأكدًا من الخصائص التي يتم اكتسابها في CSS، يمكنك الاطّلاع على هذه القائمة المفيدة أو وضع علامة في مربّع الاختيار "عرض الخصائص المكتسَبة" في لوحة "العنصر".
تنسيق العُقَد الموزَّعة
العُقد الموزّعة هي عناصر يتم عرضها في نقطة إدراج (عنصر <content>). يتيح لك العنصر <content> اختيار عُقد من Light DOM وعرضها في مواقع محدّدة مسبقًا في Shadow DOM. ولا تكون هذه العناصر منطقيًا في Shadow DOM، بل تظلّ عناصر ثانوية للعنصر المضيف. نقاط الإدراج هي مجرد شيء عرض.
تحتفظ العُقَد الموزَّعة بالأنماط من المستند الرئيسي. أي أنّ قواعد الأنماط من الصفحة الرئيسية يستمر تطبيقها على العناصر، حتى عند عرضها في نقطة إدراج. مرة أخرى، لا تزال العُقد الموزّعة منطقيًا في نموذج DOM الخفيف ولا تتحرك. بل يتم عرضها في مكان آخر. ومع ذلك، عندما يتم توزيع العُقَد في Shadow DOM، يمكن أن تتخذ أنماطًا إضافية محدّدة داخل شجرة الظل.
العنصر الزائف ::content
العُقَد الموزَّعة هي عناصر ثانوية للعنصر المضيف، فكيف يمكننا استهدافها من داخل Shadow DOM؟ الإجابة هي العنصر الزائف ::content في CSS.
وهي طريقة لاستهداف عُقد 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، يمكن للمؤلفين تزويد الجهات الخارجية بأدوات تنسيق ملائمة لتخصيص المحتوى بشكل أكبر. بشكل عام، يتحكّم مؤلفو الويب بشكل كامل في كيفية تمثيل المحتوى الخاص بهم.