<style> .progress { position: relative; border: solid 1px #000; padding: 1px; width: 100px; height: 1rem; } .progress > .bar { background: #9cf; height: 100%; } .progress > .label { position: absolute; top: 0; left: 0; width: 100%; text-align: center; font-size: 0.8rem; line-height: 1.1rem; } </style> <template id="progress-bar-template"> <div class="progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"> <div class="bar"></div> <div class="label">0%</div> </div> </template> <script> function createProgressBar() { var fragment = document.getElementById('progress-bar-template').content.cloneNode(true); var progressBar = fragment.querySelector('div'); progressBar.updateProgress = function (newPercentage) { this.setAttribute('aria-valuenow', newPercentage); this.querySelector('.label').textContent = newPercentage + '%'; this.querySelector('.bar').style.width = newPercentage + '%'; } return progressBar; } </script>
Pay attention to the template element , the use of which allows the author to include a snippet of HTML text to later be instantiated by creating a clone. This is the first feature of the “web component” that we implemented in WebKit; it was later included in the HTML5 specification . The template element in the document is allowed to appear anywhere (say, between table
and tr
), and the content inside the template is inert and does not execute scripts and download images or any other resources. Thus, it will be enough for the user of this progress bar to instantiate and update it as shown below: var progressBar = createProgressBar(); container.appendChild(progressBar); ... progressBar.updateProgress(10);
progress
will also be applied to the following HTML: <section class="project"> <p class="progress">Pending an approval</p> </section>
And the styles of other elements will override the appearance of the progress bar: <style> .label { font-weight: bold; } </style>
We could bypass these restrictions by giving the progression bar the name of the custom element, for example, custom-progressbar
to limit the scope of styles, and then initialize all the other properties to all: initial
, but there is a more elegant solution in the Shadow DOM world. The basic idea is to present the outer div as an extra layer of encapsulation so that users won't see what's going on inside (creating divs for the labels and the slider itself), the progress bar styles will not interfere with the rest of the page and vice versa. To do this, we need to first create a ShadowRoot
by calling the attachShadow({mode: 'closed'})
method on the progress bar, and then insert the DOM nodes necessary for our implementation into it. Suppose we continue to use the div to set the host to this shadow root, then we can create a new div in the following way and attach shadow root: <template id="progress-bar-template"> <style> .progress { position: relative; border: solid 1px #000; padding: 1px; width: 100px; height: 1rem; } .progress > .bar { background: #9cf; height: 100%; } .progress > .label { position: absolute; top: 0; left: 0; width: 100%; text-align: center; font-size: 0.8rem; line-height: 1.1rem; } </style> <div class="progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"> <div class="bar"></div> <div class="label">0%</div> </div> </template> <script> function createProgressBar() { var progressBar = document.createElement('div'); var shadowRoot = progressBar.attachShadow({mode: 'closed'}); shadowRoot.appendChild(document.getElementById('progress-bar-template').content.cloneNode(true)); progressBar.updateProgress = function (newPercentage) { shadowRoot.querySelector('.progress').setAttribute('aria-valuenow', newPercentage); shadowRoot.querySelector('.label').textContent = newPercentage + '%'; shadowRoot.querySelector('.bar').style.width = newPercentage + '%'; } return progressBar; } </script>
Note that the style element is inside the template and will be shadowed into the shadow root along with the divs. This will limit the scope of the styles to this very shadow root. Similarly, the styles on the outside do not apply to the elements inside.open
shadow DOM, in which the shadow root will be accessible through the property's shadowRoot property. For example, {mode: DEBUG ? 'open' : 'closed'}
{mode: DEBUG ? 'open' : 'closed'}
querySelectorAll
and getElementsByTagName
. Due to the fact that the default nodes inside shadow root are not detected by these APIs, component users may not think about the internal implementation of each component. Each component is represented as an opaque element, the implementation details of which are encapsulated inside its shadow DOM. Keep in mind that the shadow DOM in no way cares about cross-origin constraints as the iframe element does. If necessary, other scripts will be able to penetrate the shadow DOM. However, there is another reason why this mechanism appeared - composition. Suppose we have a list of contacts: <ul id="contacts"> <li> Commit Queue (<a href="mailto:commit-queue@webkit.org">commit-queue@webkit.org</a>)<br> One Infinite Loop, Cupertino, CA 95014 </li> <li> Niwa, Ryosuke (<a href="mailto:rniwa@webkit.org">rniwa@webkit.org</a>)<br> Two Infinite Loop, Cupertino, CA 95014 </li> </ul>
and we want to add to each item of contact information from the list of beautiful things with the included scripts: <template id="contact-template"> <style> :host { border: solid 1px #ccc; border-radius: 0.5rem; padding: 0.5rem; margin: 0.5rem; } b { display: inline-block; width: 5rem; } </style> <b>Name</b>: <slot name="fullName"><slot name="firstName"></slot> <slot name="lastName"></slot></slot><br> <b>Email</b>: <slot name="email">Unknown</slot><br> <b>Address</b>: <slot name="address">Unknown</slot> </template> <script> window.addEventListener('DOMContentLoaded', function () { var contacts = document.getElementById('contacts').children; var template = document.getElementById('contact-template').content; for (var i = 0; i < contacts.length; i++) contacts[i].attachShadow({mode: 'closed'}).appendChild(template.cloneNode(true)); }); </script>
Conceptually, slots are unfilled shadows in the shadow DOM that are filled by descendants of the host element. Each element is assigned to a slot with a name defined in the slot
attribute: <ul id="contacts"> <li> <span slot="fullName">Commit Queue</span> (<a slot="email" href="mailto:commit-queue@webkit.org">commit-queue@webkit.org</a>)<br> <span slot="address">One Infinite Loop, Cupertino, CA 95014</span> </li> </ul>
Thus, we attach our shadow root to li
, and each span
with a slot
attribute is assigned to a slot with a corresponding name inside the shadow DOM. Take a closer look at the shadow DOM template: <b>Name</b>: <slot name="fullName"> <slot name="firstName"></slot> <slot name="lastName"></slot> </slot><br> <b>Email</b>: <slot name="email">Unknown</slot><br> <b>Address</b>: <slot name="address">Unknown</slot>
This template has two slots with the names email
and address
, as well as a slot called fullName
, which contains two other slots, firstName
and lastName
. The fullName
slot uses the foldback technique when firstName
and lastName
displayed only if there are no nodes assigned to fullName
. Although in this case exactly one node is assigned to each slot, we could assign multiple elements with the same slot
attribute to the same slot, then they would be displayed in the same order in which they are located by the descendants of the host element. You can also use unnamed standard slots, those descendants of the host that do not have the slot
attribute specified will fill them. When the browser renders this component, the content of li is replaced with the shadow DOM, and the slots inside it are replaced with the assigned nodes, as if the following DOM is actually displayed: <ul id="contacts"> <li> <!--shadow-root-start--> <b>Name</b>: <slot name="fullName"> <!--slot-content-start--> <span slot="fullName">Commit Queue</span> <!--slot-content-end--> </slot><br> <b>Email</b>: <slot name="email"> <!--slot-content-start--> <a slot="email" href="mailto:commit-queue@webkit.org">commit-queue@webkit.org</a> <!--slot-content-end--> </slot><br> <b>Address</b>: <slot name="address"> <!--slot-content-start--> <span slot="address">One Infinite Loop, Cupertino, CA 95014</span> <!--slot-content-end--> </slot> <!--shadow-root-end--> </li> </ul>
As you can see, slot-based composition is a powerful tool that allows widgets to insert content into a page without cloning and modifying the DOM. With it, widgets can react to changes in their descendants without resorting to MutationObserver or any explicit notifications from scripts. In essence, composition turns DOM into a communication mechanism between components.:host
: <template id="contact-template"> <style> :host { border: solid 1px #ccc; border-radius: 0.5rem; padding: 0.5rem; margin: 0.5rem; } b { display: inline-block; width: 5rem; } </style> ... </template>
This pseudo-class, as its name implies, applies to the shadow DOM host in which this rule is located. By default, author styles outside the shadow DOM have a higher priority than the styles inside the shadow DOM. This is done so that the “default styles” can be defined inside the component, and the component users can be allowed to override them when necessary. In addition, a component can define styles that are principally important for its display (such as, for example, width or display
) with the !important
keyword. Any !important
rules inside the shadow DOM are considered higher priority than those !important
that are declared outside.Source: https://habr.com/ru/post/304112/
All Articles