Custom labels

A custom tag defines a new HTML tag by extending an HTMLElement or a subclass of HTMLElement, which is componentized by native JS.

Custom tag through the window. The customElements. Define to define,

  • The first parameter is the name of the tag, which must be followed by a dash-And it’s all lowercase
  • The second argument is the tag’s constructor, which is inherited from the tag mentioned aboveHTMLElementThe class of
  • The third parameter accepts an object, which currently has only oneextendsProperties can be configured if the constructor is inherited fromHTMLElementSubclasses of, such asHTMLDivElementYou need to specifyextends:"div"

After you define a custom element, you can use the custom element directly in HTML. If the custom element inherits from another element, you need to specify the name of the custom tag using the original tag plus the IS attribute

<! -- Inherit from HTMLElement -->
<ce-myelement></ce-myelement>

<! -- Inherited from the p tag -->
<p is="ce-my-p-element"></p>
Copy the code

Here is a simple example of clicking on an element that prints itself

class CopyCode extends HTMLElement {
    constructor() {
        super(a);this.onclick = (e) = > {
            if(e.target ! =this) return;
            console.log(e.target); }; }}window.customElements.define("ce-myelement", CopyCode);
Copy the code

The shadow of the DOM

create

The previous custom tags just define their own special generic methods, can also insert child elements, already have componentized methods, but compared to complex components are completely inadequate, it should be used in conjunction with another feature, Shadow DOM

Shadow DOM can seal the interior so that neither JS nor CSS can select the interior elements (they just can’t be selected and will still be displayed on the page), it can define

tags and only affect the interior styles

You can take over a normal element as a shadow DOM by using the following method

const innerNode = document.createElement("p");
innerNode.innerText = "inner";

const div = document.querySelector("div");

const shadow = div.attachShadow({ mode: "closed" });

shadow.appendChild(div.appendChild(innerNode));
Copy the code

When you set an element to shadow DOM, all of its children are hidden from the page, and elements in the Shadow DOM appear on the screen

The shadow DOM is available from the original element’s shadowRoot attribute, but not if the mode attribute is closed, which means that the element is completely closed and cannot be changed externally

const shadow = div.attachShadow({ mode: "closed" });
console.log(div.shadowRoot); // null

const shadow = div.attachShadow({ mode: "open" });
div.shadowRoot == shadow; // true
Copy the code

Add the style

Now that you’ve created a shadowDOM for the div, you can add elements and styles to it just like you would on a normal page

  • By creating a<style>Label useinnerTextHand writing
  • throughcsstheimport url()Method introduces an external style
  • through<link>Tags introduce external styles

slot

The shadow DOM takes over the internal content of the normal element, and the original content of the element is hidden. In this case, you can use the slot element

to introduce the external element into the shadow DOM and make it display in the appropriate place

As a simple example, change the text in div to the red H1 size text

const div = document.querySelector("div");
const shadow = div.attachShadow({ mode: "open" });

const h1 = document.createElement("h1");
h1.style.color = "red";
h1.appendChild(slot);

shadow.appendChild(h1);
Copy the code

Slots also support named slots, specifying the name by defining the name attribute on

, and specifying the slot with the same name on a normal element replaces the normal element with the shadow, and the default element can also be placed in

const div = document.querySelector("div");
const shadow = div.attachShadow({ mode: "open" });

const slot = document.createElement("slot");
slot.setAttribute("name"."h1");

const h1 = document.createElement("h1");
h1.style.color = "red";
h1.appendChild(slot);
shadow.appendChild(h1); // Add a slot element to the shadow

const text = document.createElement("div");
text.innerText = "h1";
text.setAttribute("slot"."h1");
div.appendChild(text); // Insert the element with the specified slot into the original element
Copy the code

The template

In the example above, we’ve been building the DOM tree with code. You can actually use the

tag to build the template. Unlike regular tags, the

tag doesn’t show up on the page, and it has the same CSS scope as the shadow DOM

Rewrite the above code as a template:

<div>aaa</div>

<template id="text">
    <b slot="h1">text</b>
</template>

<template id="temp">
    <style>
        h1 {
            color: red;
        }
    </style>
    <h1>
        <slot name="h1"></slot>
    </h1>
</template>

<script src="./index.js" type="module"></script>
Copy the code
const div = document.querySelector("div");
const shadow = div.attachShadow({ mode: "open" });
shadow.appendChild(document.querySelector("#temp").content.cloneNode(true));

const text = document.querySelector("#text").content;
div.appendChild(text);
Copy the code

component

This, combined with the custom tags above, creates a component

<body>
    <ce-red-h1 data-text="abc">
        <b slot="h1">b</b>
    </ce-red-h1>

    <template id="temp">
        <style>
            h1 {
                color: red;
            }
        </style>
        <h1>
            <slot name="h1"></slot>
        </h1>
    </template>

    <script src="./index.js" type="module"></script>
</body>
Copy the code
class RedH1 extends HTMLElement {
    text;
    constructor() {
        super(a);const template = document.querySelector("#temp");
        this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true));
        this.text = this.dataset.text;
        const p = document.createElement("p");
        p.innerText = this.text;
        this.shadowRoot.appendChild(p); }}window.customElements.define("ce-red-h1", RedH1);
Copy the code

Custom tags can also pass data through data-, but only as strings

Used in the Vue

The document

Vue provides a defineCustomElement to create a custom tag constructor, it receives defineComponent the same parameters, return window will be used in the same class. The customElements. Define to register, Because it is registered using the native method, such components do not need to be mounted as global components to be used globally. Custom tags created using vUE templates can support passing complex data such as objects

To use custom labels in vUE, you must configure loader first; otherwise, a warning is displayed indicating that labels are not VUE components

// vite
vue({
    template: {
        compilerOptions: {
            isCustomElement: (tag) = > tag.startsWith("ce-"), // Custom tags start with 'ce-' so they don't throw wrong}},})// vuecli
module.exports = {
  chainWebpack: config= > {
    config.module
      .rule('vue')
      .use('vue-loader')
      .tap(options= > ({
        ...options,
        compilerOptions: {
          // Treat all tags beginning with ion- as custom elements
          isCustomElement: tag= > tag.startsWith('ion-')}}))}}Copy the code

To prevent styles from being individually packaged externally during packaging, you need to change the vue file suffix to.ce.vue

Everything defined by the single-file component is put into the shadow DOM of the custom element

<template> <h1 class="redH1"> <slot name="h1"></slot> </h1> <p>{{ text.value }}</p> </template> <script setup lang="ts">  interface Props { text: { value? : string; }; } withDefaults(defineProps<Props>(), { text: () => ({ value: "a", }), }); </script> <style> // scoped h1 {color: red; } </style>Copy the code
// index.ts
import { defineCustomElement } from "vue";
import RedH1 from "./RedH1.ce.vue";

window.customElements.define("ce-red-h1", defineCustomElement(RedH1));
Copy the code

Then you can use index globally by introducing it as a side effect in main

<template>
    <ce-red-h1 .text="text">
        <b slot="h1">b</b>
    </ce-red-h1>
</template>

<script setup lang="ts">
import { reactive } from "@vue/reactivity";

const text = reactive({
    value: "abc",
});
</script>
Copy the code

Note that if you’re passing objects, arrays, etc., instead of using V-bind :text, use V-bind :text.prop. Using a single file will pack more code into it, and native writing is recommended if you are using simple functional components

Usage scenarios

If you need to extend the HTML obtained from outside and add more complex functions, custom tags are a good choice. For example, my blog post is parsed to HTML through Markdown. Just copy the button in the upper right corner of the code snippet of the parsed HTML text is a custom tag. By customizing the click event to copy the innerText from the parent element directly into the clipboard, you don’t have to set the paste content for each snippet as you do for the Paste button