Initial experience of ES6 module + custom elements

Time:2021-8-30

It may be the latest “first experience”.
Hey, a friend said to me, author, you write so much that no one can read it. It’s meaningless. It’s better to save this time and sit down and have a cup of milk tea, right. I can’t be angry. When I send it out, I want to see how many people have read this article. When it’s done, I can give me a point to be angry with my rich friend.

Next generation ECMAScript andWeb ComponentsThe standard has been developed very comprehensively, and modern browser support is also very extensive. Pure native code can also write out similar effects of Vue single file components.

<game-character data-name="El Primo" data-image="//example.dev/el-primo.png"></game-character>
import GameCharacter from '//example.dev/xxx.js';
GameCharacter.register('game-character');

Module + Components Example – CodePen (be sure to open it!)

Element is the body of the component

Although html is the basic technology for building web pages, it is undeniable that itsThe number of native tags and scalability are quite limited。 developerabundantLack of oneAutomatically associate JS with HTMLThe way.

All this continues untilCustom elementThe emergence of.Custom elementMDN)It is a milestone in the process of HTML modernization. It binds the specified HTML element to es class, and enhances the connection between the structure and function of web development,It is an important breakthrough in the front-end field of modern bionics (people study the structure and function of organisms, work principles, and create advanced technology),It makes it easy to create and extend reusable HTML elements.abundantIdentify and create components through native custom elementsSimple and direct, intuitive, small amount of code

Naming of specifications

In order to distinguish native elements and help browsers parse, the HTML specification is clearly and in detailThe name of a custom element should follow the rules of

  • The name of a custom element must contain a hyphen (-).
  • Start with a letter.
  • Does not contain uppercase letters.
  • Cannot be one of the following:

    • <annotation-xml>
    • <color-profile>
    • <font-face>
    • <font-face-src>
    • <font-face-uri>
    • <font-face-format>
    • <font-face-name>
    • <missing-glyph>

Standard to<math-α>and<emotion-😍>For example,Names can be used as long as the above requirements are met

In addition, a user-defined element that meets the naming requirements, even if it is not registered, is not validcan’tDeemedHTMLUnknownElementExamples of.

//'game character' meets the naming criteria.
document.createElement('game-character') instanceof HTMLUnknownElement
// false

//'character' does not meet the naming criteria.
document.createElement('character') instanceof HTMLUnknownElement
// true

Simple and clear relationship definition

The selling point of custom elements is basically in custom classes. Take this example, all<game-character>Will beGameCharacterExamples of.

class GameCharacter extends HTMLElement {
  //When the constructor does not inherit and is empty, or inherits but has only one super(), it can be ignored.
  constructor() {
    super();
  }
}

To customize the relationship between elements and classes, you can usecustomElements.defineMDN)The API also completes the application of the element in the domregister

customElements.define('game-character', GameCharacter);

The custom element completes the registration process,newThe operator has the ability to create an instance of the custom element.

//After registration,
const ele = document.createElement('game-character');
//Functionally equivalent to:
const ele = new GameCharacter();

Reusable structure

Shadow DOMMDN)It is a native solution specific to HTML and CSSElement ID and CSS style of local scopeThus, it gets rid of the dependence on the third-party framework. It is a key link of web components; Together with custom elements and ES6 module, it forms the native “hardware” foundation of modularization of modern web development.

It’s easy to create a shadow DOM on selected elementsattachShadowMDN)You can create the “shadow root” of the element. The shadow root is also the return value of this method, which can be passed through the elementshadowRootMDN)Property access. Each element can only have one shadow root and can be called multiple timesattachShadowI’ll report oneNotSupportedErrorWrong.Shadow roots have most APIs for normal elements.

Dynamically built by document API

class GameCharacter extends HTMLElement {
  imgEle;

  nameEle;

  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });

    //Build element structure
    const imgEle = document.createElement('img');
    imgEle.title = 'Avatar Picture Unset.';
    imgEle.alt = 'Unknown Person.';
    shadowRoot.appendChild(imgEle);
    this.imgEle = imgEle;

    const nameEle = document.createElement('span');
    nameEle.textContent = 'Unknown Person.';
    shadowRoot.appendChild(nameEle);
    this.nameEle = nameEle;
  }

  //Respond to HTML attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case 'data-image':
        this.imgEle.src = newValue;
        break;
      case 'data-name':
        this.nameEle.textContent = newValue;
        this.imgEle.title = newValue;
        this.imgEle.alt = newValue;
        break;
      default:
        break;
    }
  }
}

The simple shadow tree structure can be generated one by one directly using the example method.

Static build from template string

Complex shadow tree structures can be created<template>MDN)Element by accepting a newlinebackquote Template string, stored as a template.

Template
//Element structure
const template = document.createElement('template');
template.innerHTML = `
  <img>
  <span>Unknown Person.</span>
`;
Import of template structure

Template element<template>ofcontentMDN)Property returns a template element composed of child elementsDocumentFragmentMDN)。

class GameCharacter extends HTMLElement {
  // ...

  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });

    //Build element structure
    const children = template.content.cloneNode(true);
    shadowRoot.appendChild(children);

    this.imgEle = shadowRoot.querySelector('img');
    this.nameEle = shadowRoot.querySelector('span');
  }

  // ...
}

Stored in<template>The contents of the element will be parsed by the browser, but will not be rendered.

Isolated style design

The CSS scope provided by shadow DOM ensuresThe style rules in the shadow tree will not be leaked, and the external styles will not be infiltrated, this has brought simpler and more efficient CSS selectors, more general and easy to read class names, and the worry of naming conflicts has been ignored. For developers who are new to ModularityWhen you enter a paradise, you suddenly see the lightAs ifThe first meeting between ignorant youth and hello world。 The project is getting bigger and bigger, and the code is smelly and long. Today, the author can return to nature. He can’t help laughing through tears.

:hostMDN)The selector is responsible for selecting the host of the shadow root. When limiting the conditions that the host needs to meet, you need to put the relevant selector in the:host()MDNInside selector:host-content()MDN)Selectors are used to qualify the hostParent elementConditions to be met.

Example

In this example, hovering over a custom element changes the border color and image size, and the parent element contains.blackClass, the custom element will be replaced with a black background:

:host {
  background: #fff;
  color: #333;
  border-width: 2px;
  border-style: solid;
  border-color: #2196f3;
  transition: border-color 240ms ease-in-out;
}

:host-content(.black) {
  background: #333;
  color: #fff;
}

:host(:hover) {
  border-color: #ffc107;
}

/*Invalid rule*/
:host:hover {
  /*Nothing works*/
}

img {
  transition: transform 240ms ease-in-out;
}

:host(:hover) img {
  transform: scale(1.1);
}
How to apply

Style design can be written directly in JS files, likeStatic by template stringstructureElement HTML structureDo the same when you combine CSS with HTML<template>Put it together. You can also choose to become a single file independently, record or obtain the URL of the file in the script, and import it in the style class sub element of the custom element.

Static record style
/*Included in JS file*/
const styleText = `
  :host {
    ...
  }
  ...
`;

class GameCharacter extends HTMLElement {
  //...

  constructor() {
    // ...

    const styleEle = document.createElement('style');
    styleEle.textContent = styleText;
    shadowRoot.appendChild(styleEle);

    // ...
  }

  // ...
}

The following are included in<template>The method in the element is recommended, which can be passed later“import.meta”And“Template string”Two sections for more information.

/*Included in JS file 2, use < template > element*/
const template = document.createElement('template');
template.innerHTML = `
  <style>
  :host {
    /* ... */
  }
  /* ... */
  </style>
  <img>
  <span>Unknown Person.</span>
`;

class GameCharacter extends HTMLElement {
  // ...

  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });

    //Build element structure
    const children = template.content.cloneNode(true);
    shadowRoot.appendChild(children);

    // ...
  }

  // ...
}
Import external style sheets
/*External style loading*/
const styleURL = '//example.dev/index.css';

class GameCharacter extends HTMLElement {
  //...

  constructor() {
    // ...

    const styleEle = document.createElement('style');
    styleEle.textContent = `@import url(${styleURL})`;
    shadowRoot.appendChild(styleEle);

    // ...
  }

  // ...
}
/*External style loading 2, using the < link > element*/
  constructor() {
    // ...

    const styleEle = document.createElement('link');
    styleEle.setAttribute('rel', 'stylesheet');
    styleEle.setAttribute('href', styleURL);
    shadowRoot.appendChild(styleEle);

    // ...
  }

Callback function

Custom element involvedCallback function name. correspondingexplainandSupplementary pointsAs follows:

name response
constructor Element instances are created or upgraded.
connectedCallback The element is injected into the dom.
disconnectedCallback The element is moved out of the dom.
attributeChangedCallback The HTML attribute specified by the element has been changed.
adoptedCallback The element is moved to a new document.

constructor

HTML standard corresponds to a custom elementClassconstructorFunction requirements, as follows:

  1. The corresponding class of each custom elementconstructorFunction internals must firstNo parametersCall oncesuper
  2. Except directlyreturnandreturn this, cannot be usedreturn
  3. Cannot calldocument.writeanddocument.openmethod.
  4. Cannot get or changewhateverChild elements andwhateverHTML attributes.
  5. Generally speaking, give the work to as many people as possibleconnectedCallback(inject DOM callback).
  6. In general,constructorThe function sets the default value, event listener, and possibly shadow root.

connectedCallback & disconnectedCallback

Although the triggering of element injection and removal callbacks literally means that the element is in or out of the DOM state, it is worth noting that both callbacks areSynchronization functions that do not block the current queue。 This means that if you add a custom element to the DOM and quickly remove it, thenconnectedCallbackThis element when triggeredIt may not be in the DOM anymore

attributeChangedCallback

The callback will accept three string parameters. The first isChanged HTML attribute name, the second isOld value before being changed, the third isNew value after change。 This callbackOnlyOf listening classobservedAttributes static stateThe name of the HTML attribute contained in the array returned by the. For example,

class GameCharacter extends HTMLElement {
  // ...
  static observedAttributes = ['data-image', 'data-name'];

  attributeChangedCallback(name) {
    //Always true.
    this.constructor.observedAttributes.includes(name);
  }
  // ...
}

At present, most online tutorials, including HTML specifications and examples in MDN documents, willobservedAttributesReturn with a static getter,

  // ...
  static get observedAttributes() {
    return ['data-image', 'data-name'];
  }
  // ...

This isEarly ES6Class is not allowed to declare attributes and can only contain functions. There is no reason to do so now.

adoptedCallback

When an element is moved into a newdocumentObject. For example, there is a custom elementele, calldocument.adoptNode(ele)Will triggereleElementaladoptedCallbackCallback.

Register series callback

After the registration process is completed, eachAlready existsof<game-character>meetingTriggers a callback function in a series of instances, this process is also called“Upgrade”, in ordermaincontain:

  1. Run onceconstructor
  2. Take the HTML attribute of each element as the relevant parameter and run it onceattributeChangedCallbackProperty change callback.
  3. If it already exists in DOM, run onceconnectedCallbackInject DOM callback.

Modules are the soul of components

Before the emergence of ES6, the modularization of server and browser was mainly realized by the third-party framework commonjs and AMD. ES6 provides moreSimple and directThe solution closes the modular development of native HTML, CSS and JSLast ring

Combining custom elements with ES6 module can first solve the problem of separation of component scripts. After all, ES6 module is designed to separate module code into files. After script separation,How to use the characteristics of module to solve component related problemsFile and variable dependencies, is the subject of this section.

import.meta

In ES6, it isimportImported projects can access aimport.metaMDN)Object, which contains only one in the current HTML specificationurlProperties tocharacter stringFormal transfer modulePath to the script file itself。 You can simply encapsulate this path and create a module that obtains other file paths or contents under the same module.

Get file path
/* get-sibling-url/main.js */
const getSiblingURL = (fileName, referencePath) => {
  const folderPath = referencePath.substr(0, referencePath.lastIndexOf('/'));
  return `${folderPath}/${fileName}`;
};

export default getSiblingURL;
Get file content (promise)
/* get-sibling-file/main.js */
const getSiblingFile = (fileName, referencePath) => {
  const folderPath = referencePath.substr(0, referencePath.lastIndexOf('/'));
  const filePath = `${folderPath}/${fileName}`;
  return fetch(filePath);
};

export default getSiblingFile;

Should CSS style sheets be separated from JS module?

The author’s suggestion is,unwanted。 Modern code editors can edit different parts of the same file in multiple windows, and the relationship between HTML, CSS and JS of custom element module is very close. The Vue document is excerpted here“Single file component”Part of the chapter:

What about separation of concerns?
An important thing to note,Separation of concerns is not equal to separation of file types。 In modern UI development, we have found that it is more reasonable to divide the code base into loosely coupled components and combine them than to separate the code base into three large levels and interweave them with each other. In a component, its templates, logic and styles are internally coupled, and matching them together actually makes the component more cohesive and maintainable.

Saving CSS and HTML in JS as a string has a certain optimization space. It is recommended to read the following text“Template string”A section.

Example

Custom element<hello-world>For displaysettings.jsonWithin the documentgreetingProperty, the module configuration parametersettings.jsonModule scriptmain.jsThe directory structure of and can be:

Module configuration parameters
{
  "greeting": "Are you OK?"
}
Module script
/* hello-world/main.js */
import getSiblingFile from '../get-sibling-file';

class HelloWorld extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });

    const selfPath = import.meta.url;
    getSiblingFile('settings.json', selfPath)
    .then((r) => r.json())
    .then((settings) => {
      shadowRoot.textContent = settings.greeting;
    })
    .catch(console.log);
  }
}

export default HelloWorld;
directory structure
├── 📁src
│   ├── 📁modules
│   │   ├── 📁hello-world
│   │   │   ├── 📜settings.json
│   │   │   ├── 📑main.js
│   │   │   ├── 📰README.md
│   │   │   └── 📜package.json
│   │   └── 📁get-sibling-file
│   │       ├── 📑main.js
│   │       ├── 📰README.md
│   │       └── 📜package.json
│   ├── 📁images
│   │   ├── 🌴banner.png
│   │   └── 🌴icon.png
│   ├── 📁scripts
│   │   └── 📑index.js
│   └── 📄robots.txt
├── 📰CHANGELOG.md
├── ✍LICENSE
├── 📰README.md
├── 📜package.json

Main scriptindex.jsWill be able toimportImportHelloWorldClass, throughcustomElements.define('hello-world', HelloWorld)establish<hello-world>Contact with this class and complete the registration of elements in DOM.

Register callback vs register function

If you need to execute the function at registration time,<hello-world>Can passcustomElements.whenDefinedMDN)Listen for your own registration events in the dom. This function takes a parameter as the listenerTarget element name, return aPromise, according towhenDefinedThe registration of the name of the target element when called, immediately or later at the time of registrationundefinedResolve for value. But,

  1. It is meaningless to statically store its own element name in the user-defined element module, which also reduces the flexibility and possibility of the moduleGenerate naming conflicts that cannot be resolved by the main script, which does not conform to the purpose of modularization;
  2. If there are other user-defined elements inside the user-defined element, it depends onwhenDefinedRegistration of custom elementsFrom outer element to inner elementIt is against intuition and logically leads to performance loss;
  3. customElementsSeries functionUnable to pass parameter, which limits the information transfer between the custom element module and the main script.

Oh, that’s hard. What shall I do? After thinking about it, I finally found that it passedestablishIn moduleThe defined “registration function” with certain specifications replaces the registration callback, aboveAllThere are ways to understand every problem:

  1. The main script can register functions withTransmission parameter, you can control the name of the user-defined element and its child user-defined elements,Prevent naming conflicts
  2. A custom element component that contains other custom elements can be called when its own registration function is calledFirst, call the registration function of the child custom element, createFrom inner element to outer elementRegistration chain.

Example

In the following example, the parent custom element module will accept the specified element nameCall the registration function of the child custom element before registering itself

Parent custom element module
/* modules/parent-ele/main.js */
import ChildEle from '../child-ele';

//Default name.
const elementNames = {
  ParentEle: 'parent-ele',
  ChildEle: 'child-ele',
};

class ParentEle {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    const child = new ChildEle();
    shadowRoot.appendChild(child);
  }

  static register(nameSettings = {}) {
    Object.assign(elementNames, nameSettings);
    //First call the registration function of the child custom element,
    ChildEle.register(elementNames.ChildEle);
    //Then register itself in the dom.
    customElements.define(elementNames.ParentEle, ParentEle);
  }
}

export default ParentEle;
Child custom element module

The child custom element module accepts the specified element name and registers itself with the specified name.

/* modules/child-ele/main.js */

//Default name.
const elementName = 'spider-man';

class ChildEle {
  static register(name) {
    if (name) elementName = name;
    customElements.define(elementName, this);
  }
}

export default ChildEle;
Main script

Main scriptActual controlThe name of each element of the imported custom element module.

/* scripts/main.js */
import ParentEle from '../modules/parent-ele';
ParentEle.register({
  ParentEle: 'ben-ele',
  ChildEle: 'peter-ele',
});

ParentEleWill take<ben-ele>Identity,ChildEleWill take<peter-ele>Complete the DOM registration process as.

If,

  • The main script calls the registration function without parameters,ParentEleWill be<parent-ele>IdentityChildElewith<child-ele>Complete the DOM registration process as.
  • The main script calls the registration function without parameters and importsChildEle, thenChildEleWill be<spider-man>Complete the DOM registration process as.

sureSimilarly, nested multilayerCustom element module.

Spell, this is a spell

If you don’t see this, you lose.

Fouc total solution

Whether you include CSS styles in JS files or set<style>@import url()</style>and<link>Custom elements can appear when loading styles from outsideFOUC(flash of Unstyled content, browser style flashing) phenomenon,Greatly affect the user experience。 It seems that developers can only choose to give up the great convenience brought by shadow Dom and native CSS scope, or expect users to accept the visual infringement caused by fouc.Pain and sadness in development practice, it takes away the new features of the front-end coderCuriosity and longingIf the development of web components is reversed, the future of custom elements is shrouded in fouc’s frequent lightningDark and dim, fall apart.

Fouc is divided into two cases for user-defined elements.

Existing element definition flicker

Callback function”As mentioned in the section, the related functions of the classes associated with the existing custom elements in the DOM will be calledcustomElements.define after, trigger. The web components standard was not designed to support static analysis like the ES module. The class associated with the custom element isDynamic selectionTherefore, it is impossible for the browser to execute the related functions of the class corresponding to the user-defined element when parsing the dom.

So incustomElements.defineOf the specified classconstructorWhen shadow DOM is generated during execution, it is bound to cause an element redrawing.

For this element flicker, a common solution is to use:not(:defined)Selectors hide undefined elements, such as adding the following code toMain document styleYes.

game-character:not(:defined) {
  visibility: hidden;
}

But for the main document stream(positionbystaticorrelative)Custom elements for(MDN), you often need to create a DOM treeSet the defined width and height before loadingSo as not to affect other elements in the main document flow after definition, causing certain flicker. However, it is not intuitive to calculate the width and height when the DOM tree is loaded, and it is almost impossible for custom elements with variable width and heightunrealistic。 In September 2019onceThere was a proposal to solve this “unrealistic” problem, but it was abandoned for some reason [note]. At present, no new proposal has been put forward.

One of the feasible ways is to useTransition animationReduce visual discomfort.

/*Not required temporarily. In this example, the default width and height is 0*/
game-character:not(:defined) {
  /* visibility: hidden; */
}
class GameCharacter extends HTMLElement {
  //...

  connectedCallback() {
    //The default width height is 0.
    Object.assign(this.style, {
      width: '0',
      height: '0',
      opacity: '0',
      overflow: 'hidden',
      transition: 'all 240ms ease-in-out',
    });
    //Transition to content width and height, reset transparency.
    Object.assign(this.style, {
      //Calculating the width and height of the content will cause element redrawing, so the previous statement with the width and height set to zero will not be invalidated here.
      width: `${this.scrollWidth}px`,
      height: `${this.scrollHeight}px`,
      opacity: '',
    });
    //Reset changes after transition.
    this.addEventListener('transitionend', () => {
      Object.assign(this.style, {
        width: '',
        height: '',
        overflow: '',
        transition: '',
      });
    }, { once: true });
  }

  // ...
}

[note] for the original proposal, please refer toMitigating flash of unstyled content with custom elements
For the reasons for abandonment of the proposal, please2020 Spring Virtual F2F · Issue #855 · w3c/webcomponentsRetrieve the keyword “fouc” in.

External style sheet load flashing

Unlike the behavior when it exists in the main document stream, style class elements<style>and<link>In shadow DOMRendering is not blocked, a custom element may flicker once even if the outreach style has been cached. When a custom element is dynamically added, the addition itself causes a flicker, and the external style loading causes another flicker.

Therefore, even from this point of view, in the shadow DOMNot recommendedImport an external style sheet.

If you must import from the outside without additional fouc, you can first obtain the external CSS file and dynamically record its text content as JS variables. In style textBefore loading, avoid element display and affecting the positioning of other elements; stayAfter loading, take the variable asInternal styleApply to existing and subsequently created elements.

Solution example
import getSiblingFile from '../get-sibling-file';
const selfPath = import.meta.url;
const styleInfo = {
  //External style file name.
  fileName: 'main.css',

  //In the initial style, absolute does not affect the location of main document flow elements, and hidden hides elements.
  //Do not use display: none to avoid that resources such as internal pictures are not loaded.
  defaultText: `
    :host {
      position: absolute;
      visibility: hidden;
    }
  `,

  //Store variables for external styles.
  text: '',

  //Wait for an instance of an external style to load.
  applyQueue: [],

  //Load external styles.
  load() {
    getSiblingFile(this.fileName, selfPath)
    .then((r) => r.text())
    .then((text) => {
      this.text = text;
      //Modify the instance of the style you want to load.
      this.applyQueue.forEach((ele) => {
        const { styleEle } = ele;
        styleEle.textContent = text;
      });
    }).catch((error) => {
      throw error;
    });
  },
};

//Load.
styleInfo.load();

class OutsideLover extends HTMLElement {
  styleEle = null;

  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });

    const styleEle = document.createElement('style');
    this.styleEle = styleEle;
    //Depending on whether the loading is complete or not,
    if (!styleInfo.text) {
      //Use the default style and put it in the waiting queue.
      styleEle.textContent = styleInfo.defaultText;
      styleInfo.applyQueue.push(this);
    } else {
      //Use the loaded style.
      styleEle.textContent = styleInfo.text;
    }
    shadowRoot.appendChild(styleEle);

    // Something else.
  }
}

The author has made it into a simpleplug-in unit。 After import, the inheritedHTMLElementChange toStylingAdvancedHTMLElement, leave outattachShadow, record the external style file name to be imported as a static attributestyleFileUrlJust.

The code in the example can be simplified as follows:

import StylingAdvancedHTMLElement from '//raw.githubusercontent.com/PaperFlu/StylingAdvancedHTMLElement/master/export.js';
//Get promise instead of URL.
import getSiblingURL from '../get-sibling-url';
const selfPath = import.meta.url;

class OutsideLover extends HTMLElement {
  styleFileUrl = getSiblingURL('main.css', selfPath);

  constructor() {
    super();
    //Instead of calling attachshadow, reference shadowroot directly.
    const { shadowRoot } = this;

    // Something else.
  }
}

Template string

Vue designer inClearly stated in the documentThe disadvantage of using string to save the HTML structure of components in JS is also the common disadvantage of string to save text in all other languages: first, it loses the syntax highlight of the editor, and second, it needs to be used for line feed\Unlike the string template in Vue, ES6Template stringMDN)Accept newline, in most editors and markdown code blocks at GitHub, NPM, etc,Syntax highlighting can be achieved by adding labels

//ES6 template strings can get the correct syntax highlighting in most editors in this way
//It is handled correctly by formatting tools such as prettier.
const html = String.raw;
const someHTML = html`
  <img>
  <span>Unknown Person.</span>
`;

Adding a tag before the template string is a legal operation in the ES6 standard. The tag will be regarded as a function toCertain rulesParse the template string(MDN)。 A formatting tool like prettier can correctly format template strings with the help of tags.

There is a package on NPM,common-tags, the common tag functions of ES6 + are designed. After installation, you can:

import { html } from 'common-tags';
const template = document.createElement('template');
template.innerHTML = html`
  <style>
    :host {
      background: #fff;
      color: #333;
      border-width: 2px;
      border-style: solid;
      border-color: #2196f3;
      transition: border-color 240ms ease-in-out;
    }
  </style>
  <img>
  <span>Unknown Person.</span>
`;

class GameCharacter extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });

    //Build element structure
    const children = template.content.cloneNode(true);
    shadowRoot.appendChild(children);

  }
}

common-tagsThe design of is not only to open the editor syntax highlighting, but also to realize the elimination of redundant spaces, line feed adjustment, HTML security escape and so on(Official documents

Syntax highlighting example

Initial experience of ES6 module + custom elements

Extend native elements

If you don’t want to design a new custom element, just for the existing elementdissatisfied, what should I do? Used to contact the relationship between a custom element name and a classcustomElements.defineAlso acceptThird parameterUsed to mark the native element name to be extended when needed. For such a custom element, we call it:Custom native element

At the same time, the corresponding class of custom native elements may not inheritHTMLElementYes, it needsDOM interface that inherits the extended native element。 For example, we need to expand<button>, we need to inheritHTMLButtonElement, if we need to expand<img>, our class needs to inheritHTMLImageElement

Here, my right hand is not happy. It thinks that since they all inherit the interface, it is beautiful and concise to directly ignore the name of the extended native element when registering? So I don’t want to help me continue coding. Oh, I said it or not. It agreed to help me search the problem. Finally, it found that,DOM interfaces corresponding to different native elements may be the same。 Like<blockquote>and<q>, interfaces areHTMLQuoteElement, how does the browser know which one you want to extend. There are specific in the HTML specificationElement interface correspondence table

Now my right hand is satisfied. It agrees to help me continue to complete the following articles and make an easy outputDataURLof<img>。 In this way, it is much more convenient for us to transmit pictures in plain text.

Example

CORS related errors may occur when testing this example on the local file system(file://

//Define a custom element that extends < img >
class TextImg extends HTMLImageElement {
  get dataURL() {
    const canvas = document.createElement('canvas');
    canvas.width = this.width;
    canvas.height = this.height;

    const ctx = canvas.getContext('2d');
    ctx.drawImage(this, 0, 0);

    return canvas.toDataURL('image/x-icon');
  }
}
customElements.define('text-img', TextImg, { extends: 'img' });

Static and dynamic creation

The tag name of the custom native element does not change, but there is oneisProperty indicating its correspondingCustom element nameTo bind the corresponding class.

Create in HTML
<img is="text-img">
Create in JS
//You can
const ele = document.createElement('img', { is: 'text-img' });
//You can do the same
const ele = new TextImg();

ele.src = 'example.png';
ele.alt = 'example';

Contradiction? Wrong?

However, seeing this, my readers are not satisfied. Through extensive study, they have found that my extension method is similar to that on Google Web fundamentalsCustom Elements v1: Reusable Web ComponentsThe tutorial is different!Example 2 in the tutorial clearly reads:

Example – extending <img>:

customElements.define('bigger-img', class extends Image {
  // Give img default size if users don't specify.
  constructor(width=50, height=50) {
    super(width * 10, height * 10);
  }
}, {extends: 'img'});

Users declare this component as:

<!-- This <img> is a bigger img. -->
<img is="bigger-img" width="15" height="20">

or create an instance in JavaScript:

const BiggerImage = customElements.get('bigger-img');
const image = new BiggerImage(15, 20); // pass constructor values like so.
console.assert(image.width === 150);
console.assert(image.height === 200);

Inherited isImageWhat’s up!

So there is no way, and carefully read this article. It is found that there seems to be a contradiction before and after this article, and the example has clearly pointed out before<img>Extensions to inheritHTMLImageElement

To extend an element, you’ll need to create a class definition that inherits from the correct DOM interface. For example, a custom element that extends <button> needs to inherit from HTMLButtonElement instead of HTMLElement. Similarly, an element that extends <img> needs to extend HTMLImageElement.

My right hand decided to compensate me for my previous misunderstanding. It made great efforts. I found that this example had not been updated since at least December 9, 2016. At that time, the custom native element had not been supported by the browser [note]. Even now, the demand for custom native elements is still very small, so the right hand suspects that the article is getting older and has opened a forum on GitHubissueThis question has just not been answered yet.

[note] seejavascript – How to extend default HTML elements as a “customized built-in element” using Web Components? – Stack Overflow。 The discussion points are different, but the same paragraph is quoted.

Now that you are satisfied, let’s continue.

Attention

  • Using JSOf elements created by the programmerisattributemeetingShow on serialization, butcan’tDisplay in DOM.
  • All native<img>Properties of elementsAll canFor customization<img>Element. such assrcShow pictures,onloadEvents, etc.
  • Custom native elementOnly HTML elements contained in the specification can be extended, element interface isHTMLUnknownElementOld elements, such as<bgsound><blink><isindex><keygen><multicol><nextid><spacer>, cannot be extended.

see

Examples of custom native elements in HTML specifications:Creating a customized built-in element – HTML Standard

epilogue

I really appreciate someone’s patience to see the end. I’m so moved!! Order and hide before you go!

Source

Recommended Today

Java Engineer Interview Questions

The content covers: Java, mybatis, zookeeper, Dubbo, elasticsearch, memcached, redis, mysql, spring, spring boot, springcloud, rabbitmq, Kafka, Linux, etcMybatis interview questions1. What is mybatis?1. Mybatis is a semi ORM (object relational mapping) framework. It encapsulates JDBC internally. During development, you only need to pay attention to the SQL statement itself, and you don’t need to […]