相比<p>、<div>这样的标签,
input标签表现得更加复杂,而它是怎么实现的呢?

在这里我们要引出一个概念

Shadow DOM

Shadow DOM是HTML的一个规范 ,它允许浏览器开发者封装自己的HTML标签、CSS样式和特定的javascript代码,同时也可以让开发人员创建类似<video>这样的自定义一级标签,创建这些新标签内容和相关的的API被称为Web Component
Web Component内容比较多不在这里讲,有空再写一篇



在平时我们是没办法从DevTools中看到Shadow DOM的,因为默认是隐藏的,需要手动设置,

DevTools
DevTools



来一个例子,我们来看看一个input组件

在没有显示Shadow DOM之前 它在HTML中是长这个样子的

展开之后 是这个样子

可以看出在input标签内出现了#shadow-root(user-agent),它包含了input组件内的DOM结构,
这就是所谓的Shadow DOM

Shadow DOM
Shadow DOM

结合代码和这两张图片我们可以来理一理Shadow DOM的结构
从外到内分别是shadow-hostshadow-rootshadow-tree

我们可以来看看MDN对Shadow DOM的描述

Shadow host: The regular DOM node that the shadow DOM is attached to.
Shadow host是一个被Shadow DOM附着的普通DOM节点
Shadow tree: The DOM tree inside the shadow DOM.
Shadow tree是属于Shadow DOM下的节点树
Shadow root: The root node of the shadow tree.
Shadow root是Shadow tree的根节点
Shadow boundary: the place where the shadow DOM ends, and the regular DOM begins.
Shadow boundary是Shadow DOM结束,普通DOM开始的分界线

同时我们可以看见shadow-tree的元素中出现了我们不熟悉的属性pseudo

何为pseudo
我们可能用过这样的方式来隐藏掉浏览器默认的丑陋滚动条

body::-webkit-scrollbar{display:none;}

对的,这个pseudo就是伪元素Pseudo Element

有了它,
我们可以在css中从shadow-host通过pseudo来访问shadow-root下的元素


Shadow DOM的好处

一个东西存在必然有它的价值,Shadow DOM为我们提供了简单有效的样式隔离,除此之外和普通DOM元素没有差别
在某个Shadow DOM内写的css代码并不会对该Shadow DOM以外的元素生效,于是我们可以通过Shadow DOM来隐藏控件的内部实现,就不用担心对样式表进行修改时对控件造成影响了

下面我们来看看Shadow DOM的简单使用

笔者找到两种方式来创建Shadow root

  • 源自MDNElement.attachShadow(shadowRootInit)
    最后修改时间: 2020.01.08
  • 源自简书帖子的Element.createShadowRoot()
    最后修改时间: 2018.10.11

都可以通过来创建Element(Shadow host)下的Shadow root

笔者测试Element.attachShadow(shadowRootInit)在现代浏览器的兼容性更强
并且Element.createShadowRoot()不是W3C标准,MDN并不建议使用
于是以下均用Element.attachShadow(shadowRootInit)来举例子
Element.createShadowRoot()有兴趣的可以跳转简书帖子阅读

详细说一说MDNElement.attachShadow(shadowRootInit)

可以作为Shadow host使用attachShadow()的标签有

一个有合法名字的自定义标签、<article>、<aside>、<blockquote>、<body>、<div>、<footer>、<h1>、<h2>、<h3>、<h4>、<h5>、<h6>、<header>、<main>、<nav>、<p>、<section>、<span>

其中的shadowRootInit包括以下字段

mode

  1. open

    • 可以从根外部的JavaScript访问Shadow root的元素
    • Element.shadowRoot; // Returns a ShadowRoot obj
  2. closed

    • 与open相反
    • Element.shadowRoot; // Returns null

delegatesFocus

  1. true

    • 点击Shadow Root的某个不可focus元素时,会聚焦到另外第一个可以focus的元素上,并为Shadow host提供任何可用的:focus样式
  2. false

    • 与true相反

这是MDN提供的兼容图

下面我们用一些例子来帮助我们理解

比方说现在的HTML代码是这样的

<body>
    <h1>outer h1</h1>
    <div id='test'>
    </div>
</body>

然后我们对#test添加Shadow DOM并且在Shadow DOM里添加css样式

if (document.body.attachShadow) { //做兼容性处理
    let test = document.querySelector('#test');
    let shadow = test.attachShadow({
        mode: 'open'
    });
    shadow.innerHTML = `
        <h1>inner h1</h1>
        <style>
            h1{color:red;text-align:center}
        </style>
    `;
}

效果图

代码图

可以看到Shadow DOM里的样式并没有泄露出来,这给我们创建Web Component奠定了基础