Using shadow DOM in react

Time:2019-12-2

1. What is shadow DOM

What is shadow DOM? Let’s first open Chrome’s devtool and tick ‘show user agent shadow DOM’ in ‘settings – > Preferences – > elements’. Then, open a video website that supports HTML5 playback. For example, YouTube:

Using shadow DOM in react

You can seevideoThere is a#shadow-rootUnder shadowrootdivCommon HTML tags like this. We can knowvideoThere will be “play / pause button, progress bar, video time display, volume control” and other controls. In fact, they are composed of these sub elements in shadowroot. And what we use mostinputIn fact, shadow DOM is also attached. For example, we try to add an input in chromeplaceholderYou can see through devtools that, in fact, the text is under shadowroot with an ID ofpalcehoderIn the div of.

Shadow DOM allows you to insert a “sub DOM tree” during document rendering, and the sub tree is not in the main DOM tree. It also provides encapsulation capabilities for DOM elements and CSS in the sub tree. Shadow DOM keeps the sub tree DOM separate from the main document dom. CSS in the sub DOM tree will not affect the content of the main DOM tree, as shown in the following figure:

Here are a few technical concepts related to shadow DOM that need to be understood:

  • Shadow host: a regular DOM node to which shadow DOM will be attached.
  • Shadow tree: the DOM tree inside the shadow dom.
  • Shadow boundary: the end of shadow Dom and the beginning of regular dom.
  • Shadow root: the root node of shadow tree.

2. What is the use of shadwo DOM

2.1. Native components built in browser

The biggest use of shadow DOM should be to isolate the external environment for encapsulating components. It is estimated that browser developers also realize that it is easier to implement native components built in browser through HTML / CSS, such as browser native components mentioned aboveinputvideoAndtextareaselectaudioAnd so on. They are all rendered by HTML / CSS.

2.2. Web Components

Web components allow developers to create reusable custom elements, which can be used together to create custom elements that encapsulate functions, and can be reused anywhere like browser native elements, without worrying about the conflict between styles and DOM. It is mainly composed of three main technologies:

  • Custom Elements(custom elements): a set of JavaScript APIs that allow you to define custom elements and their behavior, and then use them as needed in your user interface.
  • HTML Templates(HTML template):templateSumslotThe element enables you to write markup templates that do not appear in the rendering page. They can then be reused multiple times as the basis for a custom element structure.
  • Shadow DOM(shadow DOM): a set of JavaScript APIs are used to attach a “shadow DOM tree” to an element, isolate it from the main document DOM tree, and control its associated functions. In this way, you can keep the element private and not worry about “style” conflicts with other parts of the document.

An important feature in web components is encapsulation. It can hide HTML tag structure, CSS style and behavior, and separate them from other codes on the page. In this way, different functions will not mix together, and the code will look cleaner. Shadow DOM is the key feature of DOM and CSS encapsulation.

2.3 other scenarios requiring isolation

Many people have probably heard of the “micro front end”. As an “architecture style”, the micro front end can be composed of multiple “independently deliverable front end applications” into a whole. So under the “micro front end architecture”, how to ensure that there is no conflict between each independent sub application and between sub applications? Styles don’t overlap? So, can each “sub application” be isolated through shadow DOM? The answer is yes. I’ve done it in some projects.

Other scenarios that need DOM / CSS isolation may be the use of shadow dom. For example, “Alibaba cloud shopping cart”, a “public component” that needs to be “embedded and integrated” into the sales page of different products, needs to avoid style conflicts with the host page, that is, it does not affect the host page, nor is it affected by the host page.

Using shadow DOM in react

3. Support of mainstream browsers

Among them, chrome, opera and safari support shadow DOM by default, while Firefox has supported it since version 63. It can be seen that the best support is chrome, and ie does not support it until version 11. Microsoft’s other browser edge needs to be replaced with the same kernel as chrome, so the edge after the core change will definitely support shadow dom.

Please refer to HTTPS: / / caniuse. COM / ා feat = shadowdomv1 for details of browser support

4. How to create shadow DOM

Shadow DOM must be attached to an element. It can be an element declared by HTML or created dynamically by script. Can be a native element, such asdiv、pIt can also be a “custom element” such asmy-element, the syntax is as follows:

const shadowroot = element.attachShadow(shadowRootInit); 

Refer to the following example:

<html>
  <head>
    <title>Shadow Demo</title>
  </head>
  <body>
    <h1>Shadow Demo</h1>
    <div id="host"></div>
    <script>
      const host = document.querySelector('#host');
      //Attach shadow DOM to an element through attachshadow
      const shodowRoot = host.attachShadow({ mode: 'open' });
      //Add something to the shodowroot
      shodowRoot.innerHTML = `<style>*{color:red;}</style><h2>haha!</h2>`;
    </script>
  </body>
</html>

Through this simple example, we can see that “the style defined in shadow DOM does not affect the elements in the main document”, as shown in the following figure

Element.attachShadowParameters ofshadowRootInitOfmodeThe option is used to set the encapsulation mode. It has two optional values:

  • “open”Pass on the host elementhost.shadowRootGet the shadow root reference, so that any code can access the sub DOM tree through the shadow root.
  • “closed”: pass on the host elementhost.shadowRootGet null, we can only useElement.attachShadowThe return value of gets a reference to the shadowroot (usually hidden in the class). For example, input, video, etc. built in the browser are closed, and we have no way to access them.

5. Which elements can have shadow DOM attached

Not all HTML elements can open shadow DOM, only a limited set of elements can attach shadow dom. Sometimes trying to attach a shadow DOM tree to certain elements will result inDOMExceptionError, for example:

document.createElement('img').attachShadow({mode: 'open'});    
// => DOMException

Use<img>Such a non container element as shadow host is unreasonable, so this code will throwDOMExceptionMistake. In addition, some elements cannot attach shadow DOM (such as a element) for security reasons. Another reason for errors is that the browser has already attached shadow DOM (such as input) with this element.

The following table lists all supported elements:

                +----------------+----------------+----------------+
                |    article     |      aside     |   blockquote   |
                +----------------+----------------+----------------+
                |     body       |       div      |     footer     |
                +----------------+----------------+----------------+
                |      h1        |       h2       |       h3       |
                +----------------+----------------+----------------+
                |      h4        |       h5       |       h6       |
                +----------------+----------------+----------------+
                |    header      |      main      |      nav       |
                +----------------+----------------+----------------+
                |      p         |     section    |      span      |
                +----------------+----------------+----------------+

6. How to apply shadow DOM in react

How should shadow DOM be used in a react based project? For example, you are writing a common component for different products or businesses based on react, which can be embedded for integration. For example, you are designing or developing a “micro front end architecture” Application Based on react.

When we write react applications, we generally don’t want DOM operations everywhere, because it’s very unreact (adjective). Can we encapsulate it into a more react (adjective) component style to use shadow DOM?

6.1. Try to write a react component:

import React from "react";
import ReactDOM from "react-dom";

export class ShadowView extends React.Component {
  attachShadow = (host: Element) => {
    host.attachShadow({ mode: "open" });
  }
  render() {
    const { children } = this.props;
    return <div ref={this.attachShadow}>
      {children}
    </div>;
  }
}

export function App() {
  return <ShadowView>
    <span>This is isolated</span>
  </ShadowView>
}

ReactDOM.render(<App />, document.getElementById("root"));

Running to see the effect, you will surely find “eh? Nothing is shown “:

Using shadow DOM in react

It should be noted here that when shadow DOM is attached to an element, the original “child elements” of the element will no longer be displayed, and these child elements are not in shadow domhost.shadowRootThe child element of is part of the child DOM tree. That is to say, the root node of the sub DOM tree ishost.shadowRootNot host.  host.shadowRootIs an instance of shadowroot, while shadowroot inherits from documentfragment, and its child elements can be manipulated through the native DOM API.

We need to passElement.attachShadowAttach to the element, and then you can get the attached shadowroot instance. For a reference to a native DOM node like shadowroot, in addition to usingReactDOM.renderOrReactDOM.createPortalWe can’t easily render react. Element into it unless we directly manipulate dom.

6.2. The first version of DOM is modified based on direct operation:

After getting the real DOM reference through ref in react, can you move the children of host to host.shadowroot through the native DOM API?

import React from "react";
import ReactDOM from "react-dom";

//A new version of DOM based on direct operation
export class ShadowView extends React.Component {
  attachShadow = (host: Element) => {
    const shadowRoot = host.attachShadow({ mode: "open" });
    //Move all children to shadowroot
    [].slice.call(host.children).forEach(child => {
      shadowRoot.appendChild(child);
    });
  }
  render() {
    const { children } = this.props;
    return <div ref={this.attachShadow}>
      {children}
    </div>;
  }
}

//Check it out
export class App extends React.Component {
  state = { message: '...' };
  onBtnClick = () => {
    this.setState({ message: 'haha' });
  }
  render() {
    const { message } = this.state;
    return <div>
      <ShadowView>
        <div>{message}</div>
        < button onclick = {this. Onbtnclick} > internal click < / button >
      </ShadowView>
      < button onclick = {this. Onbtnclick} > external click < / button >
    </div>
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

If you look at the effect in the browser, you can see that it can be displayed normally. But at the same time, a problem will be found: “events isolated on elements in shadowroot cannot be triggered”. What’s the reason?

It is caused by the “composite event mechanism” of react. We know that “event” in react is not directly bound to specific DOM elements, but managed by the “reacteventlistener” bound to the document. When the element is clicked or other events are triggered, when the event is dispatched to the document, react will process and trigger the corresponding composite event Implementation of the components.

So why can’t composite events be triggered normally in shadow DOM? This is because the browser “redirects” the events when they are captured outside the shadow DOM, that is to say, the host element will be used as the event source when the events occurred in the shadow DOM are captured outside. This will make react not think that events bound by elements in shadowdom based on JSX syntax are triggered when processing composite events.

6.3. Try to use reactdom.render to change:

The second parameter of react dom. Render, which can pass in a DOM element. Is it possible to render react elements into shodaw DOM through react dom.render? Let’s try this:

import React from "react";
import ReactDOM from "react-dom";

//Change to react dom.render
export class ShadowView extends React.Component {
  attachShadow = (host: Element) => {
    const { children } = this.props;
    const shadowRoot = host.attachShadow({ mode: "open" });
    ReactDOM.render(children, shadowRoot);
  }
  render() {
    return <div ref={this.attachShadow}></div>;
  }
}

//How about the effect
export class App extends React.Component {
  state = { message: '...' };
  onBtnClick = () => {
    this.setState({ message: 'haha' });
    alert('haha');
  }
  render() {
    const { message } = this.state;
    return <ShadowView>
      <div>{message}</div>
      < button onclick = {this. Onbtnclick} > Click me < / button >
    </ShadowView>
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

It can be seen that children can be rendered through react dom.render, which can be normally rendered to shadow root, and composite events in shadow DOM can also be normally triggered for execution.

Why can “element events isolated in shadow DOM” be triggered at this time? Because when realc finds that the rendered target is in shadowroot, it will bind the event to the RootNode of documentfragment obtained through element. Getrootnode().

Using shadow DOM in react

It seems that everything goes well, but it will be found that when the state of the parent component is updated, the shadowview component is not updated. As shown in the above example, the message is still old, and the reason is that when we use react dom.render, the elements and parent components of shadow DOM are not in the same react rendering context.

6.4. Use reactdom.createportal to implement the first version:

We know that the appearance of createportal facilitates the development of components such as “pop-up window, prompt box”, which are separated from the document flow, replacing the unstable API “unstable” rendersubtreeintocontainer.

There is a feature of reactdom.createportal that “events can bubble up from the portal’s entry through the DOM rendered by createportal”. This feature is very important. For DOM without parent-child relationship, synthetic events can bubble up. Can events of elements rendered to shadow DOM through createportal also trigger normally? And it can make the rendering of all elements in one context. Then implement the following based on createportal:

import React from "react";
import ReactDOM from "react-dom";

//The implementation of using react dom. Create portal
export function ShadowContent({ root, children }) {
  return ReactDOM.createPortal(children, root);
}

export class ShadowView extends React.Component {
  state = { root: null };
  setRoot = eleemnt => {
    const root = eleemnt.attachShadow({ mode: "open" });
    this.setState({ root });
  };
  render() {
    const { children } = this.props;
    const { root } = this.state;
    return <div ref={this.setRoot}>
      {root && <ShadowContent root={root} >
        {children}
      </ShadowContent>}
    </div>;
  }
}

//How about trying
export class App extends React.Component {
  state = { message: '...' };
  onBtnClick = () => {
    this.setState({ message: 'haha' });
  }
  render() {
    const { message } = this.state;
    return <ShadowView>
      <div>{message}</div>
      < button onclick = {this. Onbtnclick} > Click me < / button >
    </ShadowView>
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

WOW! Everything is OK. There is a small problem that createportal does not support the version below react 16, but it is not a big problem in most cases.

7. Shadowview component for react

Several methods to implement the shadow DOM component in react are mentioned above, and shadowview is a well written out of the box shadow DOM container component for react. Using shadowview, you can create container elements that enable shadow DOM in react applications as easily as ordinary components.

At present, shadowview is fully compatible with react 15 / 16. The “event handling, component rendering update” and other behaviors of components are consistent in both versions.

GitHub: https://github.com/Houfeng/shadow-view

7.1. Installation components

npm i shadow-view --save

7.2. Use components

import * as React from "react";
import * as ReactDOM from "react-dom";
import { ShadowView } from "shadow-view";

function App() {
  return (
    <ShadowView 
        styleContent={`*{color:red;}`} 
            styleSheets={[
            'your_style1_url.css',
          'your_style2_url.css'
        ]}
        >
      < style > {you can also write internal style here} < / style >
      < div > this is a test < / div >
    </ShadowView>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));

7.3. Component properties

Attribute name type Explain
className string Classname of component itself
style any Inline style of component itself
styleContent string Styles acting inside shadowview
styleSheets string[] External stylesheets acting inside shadowview
scoped boolean Start isolation, default is true
tagName string Outer container tagName, default to shadow view

So, how is shadowview compatible with react 15? You can find out at https://github.com/houfeng/shadow-view.


Author: Houfeng

Read the original text

This is the original content of yunqi community, which can not be reproduced without permission.

Recommended Today

Docker learning (5) basic command of dockerfile

To learn dockerfile, you need to understand its basic commands From – base image Try to use the official reference image [x] From Tomcat: 8.5.50-jdk8-openjdk 񖓿 make reference image (based on Tomcat: 8.5.50-jdk8-openjdk) [x] From CentOS ා make a base image based on CentOS: latest [x] From scratch? Does not depend on any reference image […]