admin管理员组

文章数量:1357159

I am attempting to modify the styles of two custom HTML elements, "output-screen" and "custom-calculator" using shadow DOM.

When I try to do so by attaching a shadow DOM as shown below, the styles are not applied. Any ideas what I'm doing wrong?

JS Fiddle

<custom-calculator id="calculator">
  <output-screen></output-screen>
</custom-calculator>

<script>
var o = Object.create(HTMLElement.prototype);
    var oElement = document.registerElement('output-screen', {
        prototype: o
    });

var c = Object.create(HTMLElement.prototype);
var cElement = document.registerElement('custom-calculator', {
  prototype: c
});

var calc = document.querySelector('#calculator')
calc.attachShadow({ mode: 'open' });
calc.shadowRoot;
calc.shadowRoot.innerHTML = `
<style>
output-screen{
display:inline-block;
background-color:orange;
width:50%;
height:100vh;
}
custom-calculator {
display:inline-block;
background-color:grey;
width:100%;
height:100vh;
vertical-align:top;
}
</style>
`;
</script>

I am attempting to modify the styles of two custom HTML elements, "output-screen" and "custom-calculator" using shadow DOM.

When I try to do so by attaching a shadow DOM as shown below, the styles are not applied. Any ideas what I'm doing wrong?

JS Fiddle

<custom-calculator id="calculator">
  <output-screen></output-screen>
</custom-calculator>

<script>
var o = Object.create(HTMLElement.prototype);
    var oElement = document.registerElement('output-screen', {
        prototype: o
    });

var c = Object.create(HTMLElement.prototype);
var cElement = document.registerElement('custom-calculator', {
  prototype: c
});

var calc = document.querySelector('#calculator')
calc.attachShadow({ mode: 'open' });
calc.shadowRoot;
calc.shadowRoot.innerHTML = `
<style>
output-screen{
display:inline-block;
background-color:orange;
width:50%;
height:100vh;
}
custom-calculator {
display:inline-block;
background-color:grey;
width:100%;
height:100vh;
vertical-align:top;
}
</style>
`;
</script>
Share Improve this question edited Apr 19, 2019 at 20:23 Supersharp 31.3k11 gold badges102 silver badges147 bronze badges asked Jul 31, 2018 at 17:28 Emily ChuEmily Chu 1873 silver badges15 bronze badges 4
  • @Supersharp Would v1 be: customElements.define('custom-calculator', class extends HTMLElement { }) ? – Emily Chu Commented Jul 31, 2018 at 20:26
  • yes it is: look at developers.google./web/fundamentals/web-ponents/… also you should create the Shadow within the custom element constructor() – Supersharp Commented Jul 31, 2018 at 20:37
  • I tried something like this: customElements.define('custom-calculator', class extends HTMLElement { constructor() { super(); this.innerHTML = [style from before here];}})from the documentation to no avail. What am I missing here? – Emily Chu Commented Jul 31, 2018 at 22:13
  • 1 don't forget to create the Shadow root: this.attachShadow({mode:'open'}).innerHTML=... – Supersharp Commented Jul 31, 2018 at 22:36
Add a ment  | 

2 Answers 2

Reset to default 7

In order to style the element that hosts the Shadow DOM, here <custom-calculator>, you must use de :host pseudo-class (instead of custom-calculator which is unknown inside the Shadow DOM).

:host {
  display:inline-block;
  background-color:grey;
  width:100%;
  height:100vh;
  vertical-align:top;
}

Because the Shadow DOM will replace/recover the normal DOM tree (here <output-screen>), you'll have to use <slot> to insert/reveal it in the Shadow DOM.

calc.shadowRoot.innerHTML = `
  <style>
    ...
  </style>
  <slot></slot>`

Then, in order to style what is revealed by/in the <slot> element, you mus use the ::slotted() pseudo-element:

::slotted( output-screen ){
  display:inline-block;
  background-color:orange;
  width:50%;
  height:100vh;
}

Live example:

var calc = document.querySelector('#calculator')
calc.attachShadow({mode: 'open'});
calc.shadowRoot.innerHTML = `
  <style>
    :host {
      display:inline-block;
      background-color:grey;
      width:100%;
      height:100vh;
      vertical-align:top;
    }

    ::slotted( output-screen ){
      display:inline-block;
      background-color:orange;
      width:50%;
      height:100vh;
    }
  </style>
  <slot></slot>`;
<custom-calculator id="calculator">
  <output-screen></output-screen>
</custom-calculator>

You should definitely use the class syntax when working with custom elements as it tends to make things clearer.

Here is a step by step working adaptation from the code you provide :

1. String templates :
The first thing you need to do is to define the templates that you will use.

const string_templates = {
    "custom-calculator": `<style>
      :host {
        display: inline-block;
        background-color: grey;
        width: 100%;
        height: 100vh;
        vertical-align: top;
      }
    </style>
    <slot></slot>`, // !! slot !!
    "output-screen": `<style>
      :host {
        display: inline-block;
        background-color: orange;
        width: 50%;
        height: 100vh;
      }
    </style>`
};

Here we declare an associative array which stores entries under the "key":'value' format. We have two entries whose keys are "custom-calculator" and "output-screen" each one store as a string value the template that is associated with it.

As the <custom-calculator> element will receive other HTMLElement inside its html tags we need to add a <slot> element in its template. The position of this <slot> element in the template defines where the html code provided inside the <custom-selector> tags will be rendered in that element.

2. Create definitive templates from string templates :
Those templates will be used when creating new custom elements instances.

const calc_template = document.createElement('template'); // creates a new <template> element
calc_template.innerHTML = string_templates["custom-calculator"]; // parse the string template only one time

const out_template = document.createElement('template');
out_template.innerHTML = string_templates["output-screen"];

Here we define two constants calc_template and out_template each one is initialized with a new empty <template> element before adding the associated string template as their innerHTML value.

Doing this outside of the class allow to parse each template only one time at file runtime.

3. Classes "CustomCalculator" & "OutputScreen" :
The classes used here are pretty simple as they simply consist of a constructor that perform the following actions :

  • Calls super() this is mandatory prior to the use of this because the class is derived (extends another class) otherwise there will be a reference error.
  • Attach a shadowRoot whith a shadow DOM to the element using Element.attachShadow()
  • Clone the content of the associated template and append it as a child of the element's shadow DOM with Element.appendChild()

- CustomCalculator class :

// Class for the <custom-calculator> customElement
class CustomCalculator extends HTMLElement {
    constructor() {
        super(); // always call super() first
        this.shadow = this.attachShadow( { mode:'open'} ); // attach a shadowRoot to the element
        // clone a template content and add the clone as a shadow DOM child (no <template> tags)
        this.shadow.appendChild( calc_template.content.cloneNode(true) ); 
    } 
} //

Note: If you want to retrieve the <output-screen> element from inside the CustomCalculator class you will have to use this.querySelector() instead of this.shadow.querySelector(), as the html content provided to a custom element through a <slot> is not part of the shadow DOM of this element but is instead located inside its light DOM. (use page inspector to check that)

- OutputScreen class :

// Class for the <output-screen> customElement
class OutputScreen extends HTMLElement {
    constructor() {
        super();
        this.shadow = this.attachShadow( {mode:'open'} );
        this.shadow.appendChild( out_template.content.cloneNode(true) );
    } 
} //

4. Register the custom elements :
When the templates and the classes used for the custom elements are defined we can register them using customElements.define().

Note: once a custom element tag has been defined in a page it could not be used again with another class, so if you want to use two custom elements that have the same tag you will have to change one of them.

customElements.define( 'custom-calculator', CustomCalculator ); // register the <custom-calculator> customElement
customElements.define( 'output-screen', OutputScreen ); // register the <output-screen> customElement

Don't forget the "s" at the end of the first part of the instruction it is a source of error.

Utilization :

The easiest way is to replace the content of your <script> tag with the code highlighted above using the same order.

If you choose to save this code in a js file i.e. custom_calculator.js, you could import it in your page using this line of html :

<script type="module" src="./PATH/custom_calculator.js"></script>

At this point every time you put the following html code in your page you should have the correct custom elements instantiated with their default style.

<custom-calculator>
    <output-screen></output-screen>
</custom-calculator>

Modify the default style of a custom element :

I can now answer the title question, but before going into more details I will add some context for when this use case will be needed.

Let's say you worked on a project in which you developped some custom elements. Now working on a new project you want to use some of the custom elements you have already created but their default style do not match the style used in the new project.

What can be done in order to make a custom element more re usable in regard of its styling ?

You could add a static method to the body of the custom element class code below :

class CustomCalculator extends HTMLElement {
    constructor( ) { }
    
    static setNewStyle( css_style ) { 
        let new_style = document.createElement('style'); // create a new <style> element
        new_style.textContent = css_style; // set the textContent of the new <style> element as the provided style
        calc_template.content.appendChild( new_style ); // insert the new <style> element at the end of the template content    
    } 

} //

export { CustomCalculator }; // needed to call static methods from other files

The provided argument css_style is a string in which you can define the css rules you want to apply on the custom element. The function will then create a new <style> element that receive the provided css_style as textContent before adding it as the last child of the definitive template content.

We also need to export the class of the custom element to be able to use the static methods from other files.

Note: definitive template refers to the template that is used to instantiate a custom element from its class constructor.

How to use :
As a static method the setNewStyle() method cannot be called from the body of the class or any of its instances. It is used like this: CustomCalculator.setNewStyle( css_style );

Example:
For this example the only JavaScript file directly imported in the page is an index.js file. It is imported using the following html line : <script type="module" src="./index.js"></script>

We import the custom element in index.js as follow :

import { CustomCalculator } from './PATH/custom_calculator.js';

CustomCalculator.setNewStyle(`
    :host {
        background-color: black;
    }
`); // when instantiated <custom-calculator> elements will now have a black background.

On the 1st line we import the custom element class, with the 2nd instruction we call the static method setNewStyle( css_style ) of the imported class providing to it a string argument which redefines the background-color on the custom element to black using a :host selector.

本文标签: javascriptModifying custom elements39 style in shadow rootStack Overflow