d

daisyhhuang

V1

2022/05/13阅读:30主题:默认主题

自定义你的HTML标签

前言

随着互联网的兴起,网页成为一种新的承载信息的方式。我们每天都在手机或者电脑浏览各种网页信息,这里大家有没有想过,这些网页的基本组成元素是什么。就像生产一台手机,首先需要手机的原材料部件。那么组成网页的原材料又是什么呢?这些原材料是否可以定制化?

所有网页结构都是由html的标签构成,html语言是所有网页的基础。它就像生产一台手机所需所有原材料。

为了方便使用,浏览器封装了很多标准的html的标签给我们使用,就是平时常用的 <p> <section> <h1><video>等标签。他们各自都带了自己独特的功能,比如video标签,这样一行代码,就能让浏览器渲染出一个播放组件。

那除了使用浏览器的默认标签,能不能使用自己自定义的html标签?比如我想用浏览器标准没有的<my-element>标签,也就是我们所说的自定义标签。

答案是可以使用,效果如下:

可以看到<my-element>是可以被渲染出来的。所以,我们可以得出结论:浏览器其实天生就是允许自定义标签的存在,不会报错,只是不会去解析它,就把它放在那里,没有默认的样式和行为。这是写入html5的标准的。

"User agents must treat elements and attributes that they do not understand as semantically neutral; leaving them in the DOM (for DOM processors), and styling them according to CSS (for CSS processors), but not inferring any meaning from them."

能不能在更近一步,像<video>这个标签一样,是自带默认的样式和行为的,就好像React Vue框架中的组件component一样。当我在浏览器中写这个<my-element>标签时,可以自动渲染出Hey,it’s me 的一句话。

效果如下:

下面我们就来看如何实现这样一个功能。

自定义HTML标签

1、创建一个类

首先来创建一个新的类MyElement,作为新标签的构造函数。同时,还需要基于HTMLElement这样一个基类,这样它才能具备HTMLElement已经有的方法和属性,比如可以使用DOM API。

class MyElement extends HTMLElement {
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = "<h1>Hey, it's me</h1>";
  }
}

这里我们只用了constructor这个基本的构造函数该有的方法。其实这个类还有一些自己独特生命周期函数可以被调用,比如connectedCallback 是在元素被插入到dom中被调用。这里不在拓展讲,感兴趣的可以查看custom element reactions[1]

2、定义自定义的标签

接下来,我们就可以用浏览器浏览器提供的这个customElements[2]方法,来定义一个自定义标签。

其中,第一个参数为自定义的标签名字,第二个参数为这个标签的构造函数,这里它们分别是my-elementMyElement

class MyElement extends HTMLElement {
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = "<h1>Hey, it's me</h1>";
  }
}
customElementRegistry.define('my-element', MyElement);

这样就完成对<my-element>的定义,是不是很简单。接下来我们看看效果。

<!DOCTYPE html>
<html>
<head>
<title>Custom Element</title>
</head>
<body>
  <my-element> </my-element>
</body>
</html>
<script>
class MyElement extends HTMLElement {
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = "<h1>Hey, it's me</h1>"
    // 元素的功能代码写在这里
  }
}
customElements.define('my-element', MyElement);
</script>

把上面的代码贴入浏览器,就可以看到浏览器已经渲染出来了我们自定义的html标签。

但是在实际的使用中,这样还不够。哪天我想换掉Hey, it's me这个文案,那我又需要新增一个类。现实使用的时候,当然不会这样做,我们需要通过属性来使得这个标签变得更为通用。

就好比<video>标签,只要我们传入src的属性,它就可以播放src链接里的内容。

添加属性

这里我们让这个Hey, it's me变成一个可以供用于传入的属性content,所以需要新增content属性。在构造函数里,get用来获取属性,set用来设置属性。

getter,setter有不了解的同学可以看这里,这部分不在过多解释。 https://zh.javascript.info/property-accessors

class MyElement extends HTMLElement {
  get content() {
    return this.getAttribute('content');
  }

  set content(val) {
    this.setAttribute('content', val);
  }
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = `<h1>${this.content}</h1>`
  }
}

完成之后就可以看到效果。这个content是我们通过属性传入,想写啥写啥。

<!DOCTYPE html>
<html>
<head>
<title>Custom Element</title>
</head>
<body>
  <my-element content="Hey,it’s me"> </my-element>
</body>
</html>

<script>
class MyElement extends HTMLElement {
  get content() {
    return this.getAttribute('content');
  }

  set content(val) {
    this.setAttribute('content', val);
  }
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = `<h1>${this.content}</h1>`
  }
}

customElements.define('my-element', MyElement);
</script>

把这段代码贴入,可以看到

添加属性之后,还不够,接下来最后一步,就是给这个自定义标签添加行为。比如我点击之后,可以将内容改成'what's up'。

添加行为

前面我们说过,由于这个我们类是继承于HTMLElement的,所以它可以使用所有的DOM API。这里我们就直接使用addEventListener绑定click事件即可。

class MyElement extends HTMLElement {
  get content() {
    return this.getAttribute('content');
  }

  set content(val) {
    this.setAttribute('content', val);
     
  }
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = `<h1>${this.content}</h1>`;
    this.addEventListener('click', e => {
       this.content = "what's up";
       this.innerHTML = `<h1>${this.content}</h1>`;
    });
  }
}

点击之后,发现就可以实现我们的效果。

注:当然这里可以更好一点,就是把this.innerHTML放到set函数里,这样之后每次去改content的值,它就会自动重新渲染内容啦。完整版代码如下。

<!DOCTYPE html>
<html>
<head>
<title>Custom Element</title>
</head>
<body>
  <my-element content="Hey,it’s me"> </my-element>
</body>
</html>

<script>
class MyElement extends HTMLElement {
  get content() {
    return this.getAttribute('content');
  }

  set content(val) {
    this.setAttribute('content', val);
    this.innerHTML = `<h1>${this.content}</h1>`;
  }
  constructor() {
    // 必须首先调用 super 方法
    super();
    this.innerHTML = `<h1>${this.content}</h1>`;
    this.addEventListener('click', e => {
       this.content = "what's up";
    });
  }
}
customElements.define('my-element', MyElement);
</script>

是不是有点熟悉,没错,这就是Vue2的响应式原理的超简单实现啦。

总结

到这里我们就把自定义标签 完成了。总结一下,首先通过继承HTMLElement标签来定义自定义标签的类,然后通过customElements.define这个方法来正式给这个类一个html标签名字,最后可以通过setter getter 设置获取属性值,以及所有DOM API来给这个自定义标签加入事件。

不过到这里还没完,大家有没有发现video标签,我们是没有办法通过css来改变它的样式,也不能通过常规的办法拿到它的dom元素,也就是说它跟外界是隔离的。而我们如果希望做到像video这样,封装好组件与外界隔离,该怎么做呢?

这里就要用到Shadow DOM了。它一般跟自定义标签搭配使用来封装独立于外界,不会受外界影响更改的组件。

下一篇我们继续 ~

注意事项:

虽然自定义标签也是html5的标准,但是无规矩不成方圆。所以这里还是有一些规矩来限制自定义标签的使用的。

  • 自定义标签的名字必须含有 -

比如上例中我们的<my-element>是OK的, <element>就不行。因为加上-,这样浏览器才能区分的出自定义标签和常规标签。同时可以保证兼容性。当新的常规标签加入的时候,这个标签不至于之前就被自定义标签所占有。这个专业点叫forward compatibility

  • 不能重复定义同一个html标签,否则浏览器会报错。
  • 自定义标签必须要自闭合。

因为html 只允许一小部分的元素自闭合,所以使用的时候要注意闭合标签。
<my-elements> </my-elements>

参考文章:

1 https://web.dev/custom-elements-v1/

2 http://www.ruanyifeng.com/blog/2017/06/custom-elements.html

参考资料

[1]

custom element reactions: https://web.dev/custom-elements-v1/#custom-element-reactions

[2]

customElements: https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define

分类:

前端

标签:

前端

作者介绍

d
daisyhhuang
V1