1. 什么是Shadow DOM?

Shadow DOM 如果按照英文翻译的话可以理解为 影子DOM, 何为影子DOM呢?可以理解为一般情况下使用肉眼看不到的DOM结构,那如果一般情况下看不到的话,那也就是说我们无法直接控制操纵的DOM结构。
Shadow DOM 它是HTML的一个规范,它允许在文档(document)渲染时插入一颗DOM元素子树,但是这个子树不在主DOM树中。
它允许浏览器开发者封装自己的HTML标签、css样式和特定的javascript代码、同时开发人员也可以创建类似 <input>、<video>、<audio>等、这样的自定义的一级标签。创建这些标签内容相关的API,可以被叫做 Web Component。

如上基本解释,让我们很难理解,因此我们可以先看下如下一个 input标签的demo吧。

html代码如下:

<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM</title>
<style>
#app {margin: 100px;}
input::-webkit-input-placeholder {
color: red;
}
</style>
</head>
<body>
<div id="app">
<input type="number" placeholder="请输入内容" />
</div>
</body>
</html>

然后我们在浏览器查看如下所示:

如上代码可以看到,我们使用 input::-webkit-input-placeholder{color: red;} 伪类后,input中的placeholder字体颜色发生改变了,但是我们的input元素的结构并没有看到伪类相关的html结构。

为了能看到基本结构,我们只需要在chrome浏览器中,打开开发者工具,点击右上角的 "Settings"按钮,勾选 "Show use agent shadow DOM". 后 如下图所示:

然后我们再来看下input元素的基本代码结构如下看到:

如上截图所示,我们可以看到 "请输入内容" 中的div元素上有一个属性为 pseudo="-webkit-input-placeholder", 因此我们使用 input元素的伪类 input::-webkit-input-placeholder{} 这样就可以控制元素的样式了。

2. ShadowDOM 存在的意义?

首先我们来看下Shadow-dom 基本的结构如下:

Document: 是document文档对象。
shadow-host: Shadow DOM的容器元素,即:它是Shadow DOM的一个宿主元素。比如:<input />、<audio>、<video> 标签;就是shadow-dom的宿主元素。
shadow-root: Shadow DOM的根节点。通过createShadowRoot返回的文档片段被称为 shadow-root, 它和它的后代元素,都会对用户隐藏。但是它会在浏览器中被渲染的,也就是说它是存在的,但是一般情况下在浏览器中是不显示出来的。在chrome浏览器中,如上我们可以看到的。
contents: Shadow DOM包含的子节点树结构。它包含 <input />、<audio>、<video>等标签中各子组件的DOM的具体实现。

那么ShadowDOM存在的意义是?

我们都知道像React或Vue这样的都有组件的概念,比如element-ui等这样的vue组件。但是我们常用的input、audio、video、等这些元素,其实它也是以组件的形式存在的,即:HTML Web Component. 即这些都有 Shadow DOM。

因此我们可以认为像 input, audio, video等这些元素也是以组件的形式存在的。那么这些组件内部是由一些HTML标签组成的。
这些元素组成了DOM树的子树。但是当我们使用input,audio,或video等这些元素组件的时候,都会知道该子树的结构,当我们访问网页DOM结构的时候,这些子树都会暴露出来,当我们使用css样式去改变DOM的样式的时候,如果DOM的类名和该子树的类名相同的话,会和子树的样式产生冲突,并且我们使用控件的时候,我们并不关心控件的内部结构,只关心控件本身,因此我们需要将控件的内部信息封装起来。因此 W3C提出了 ShadowDOM的概念,ShadowDOM可以使一些DOM节点在特定范围内可见,在网页中是不可见的。但是在页面渲染的时候也会渲染该ShadowDOM。
也可以看这篇文章介绍

ShadowDOM在各个浏览器的支持程度呢?

查看Can I Use(https://caniuse.com/#search=shadowDOM) 可以看到它在浏览器下的支持程度,如下所示:

3. 如何控制 shadow-dom?

既然W3C提出了ShadowDOM的概念,并且在网页中一般是不可见的,那么我们是否可以控制该 shadow-dom呢?
下面我们以 <video>标签来讲解吧,比如如下HTML代码:

<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM</title>
<style>
#app {margin: 100px;} </style>
</head>
<body>
<div id="app">
<video src="http://www.w3school.com.cn/i/movie.ogg" controls="controls">
your browser does not support the video tag
</video>
</div> </body>
</html>

3.1 使用伪类来控制 shadow-dom的样式。

首先看如上代码显示的效果图如下:

在chrome浏览器下,我们查看 shadow-dom 结构,可以看到每个元素都加上了一个pesudo 这样的属性。我们可以通过这些属性使用伪类来控制他们的样式。如下图所示:

基本样式如下:

video::-webkit-media-controls-play-button {
background-color: red;
}

我们给播放按钮添加了一个背景颜色为红色。

注意:很遗憾的是,只有chrome浏览器下支持,其他浏览器下并不支持,虽然大部分浏览器下支持shadow-dom。

4. 使用javascript如何来创建Shadow DOM

4.1 使用 createShadowRoot()来创建Shadow DOM。

如下基本代码:

<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM</title>
<style>
#app {margin: 100px;}
.shadow-child {
color: red;
}
</style>
</head>
<body>
<div id="app">
<div class="shadow-cls">hello, kongzhi</div>
</div>
<script type="text/javascript">
// 1. 获取影子宿主 shadow host
var shadowHost = document.querySelector('.shadow-cls');
// 2. 创建影子 shadow root
var shadowRoot = shadowHost.createShadowRoot();
// 3. shadow root 作为影子树的第一个节点,其他的节点,比如如下的p节点都是它的子节点。
shadowRoot.innerHTML = '<p class="shadow-child">我是子节点</p>';
</script>
</body>
</html>

然后我们查看效果如下:

如上我们可以看到,给影子树子节点设置css样式,但是并没有生效,那是因为影子宿主(shadow host)和影子根(shadow root)之间存在影子边界。影子边界保证DOM编写的css和javascript代码都不会影响到ShadowDOM.当然反之也是一样,互不影响。

4.2 理解<content> 和 <template> 的用法。

<content>标签可以把来自DOM文档的内容添加到shadow DOM的内容被叫做分布节点。

<content>有一个select属性来告诉<content>标签要插入的内容,select属性值是一个使用CSS选择器来获取想要的内容。
选择器包括类选择器、元素选择器等。

比如如下代码:

<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM</title>
<style>
#app {margin: 100px;}
</style>
</head>
<body>
<div id="app">
<div class="shadow-cls">
<em class="shadowhost-content1">我是空智</em>
<em class="shadowhost-content2">我是龙恩</em>
</div>
<!-- 下面是一个模板 template -->
<template class="template">
<div>
<h1>哈哈,</h1>
<content select=".shadowhost-content1"></content>
我来了
<content select=".shadowhost-content2"></content>!
</div>
</template>
</div>
<script type="text/javascript">
// 1. 获取影子宿主 shadow host
var shadowHost = document.querySelector('.shadow-cls');
// 2. 创建影子 shadow root
var shadowRoot = shadowHost.createShadowRoot();
// 3. 获取模板元素
var template = document.querySelector('.template');
/*
4. template.content 会返回一个文档片段。
5. 使用 document.importNode获取节点,true参数表示深度克隆
*/
shadowRoot.appendChild(document.importNode(template.content, true));
</script>
</body>
</html>

执行结果如下:

我们也可以通过如下打印:

console.log(template.innerHTML);   // 获取完整的HTML片段
console.log(template.content); // 返回一个文档片段#document-fragment
console.log(template.childNodes); // 返回[],说明childNodes无效

这些的信息的区别,如下所示:

4.3 shadowDOM样式

1. 宿主样式:
在shadow DOM中利用 :host定义宿主的样式。:host 是伪类选择器,给所有的宿主添加样式可以如下代码::host 或 :host(*); 如果想给单独的宿主添加样式可以 :host(xx); 其中xx是宿主的标签或类选择器等。并且:host还可以配合 :hover、:active等状态来设置样式,如下代码:

/* 定义宿主样式 :host */
:host {
color: red;
}
/* 定义宿主hover状态下的样式 */
:host(:hover) {
color: black;
}

2. ::shadow

影子边界为了保证DOM编写的css或javascript不影响到shadowDOM。如果我们想让 shadowDOM添加一些样式,可以使用 ::shadow这样的。

3. /deep/

::shadow选择器有一个缺陷是它只能穿透一层影子边界,如果我们在一个影子树中嵌套了多个影子树的话,那么我们需要使用/deep/ 这样的来编写css样式。

4. ::content

我们通过 <content> 标签把主文档中的元素添加到shadowDOM的内容被叫做分布节点。分布节点的样式渲染需要用到::content。比如我们想给节点为em标签的话,我们直接写 em {} 这样的是不会生效的,我们要写成 ::content > em {}. 比如如下代码:

/* 分布节点的样式渲染需要用到 ::content */
::content > em {
color: blue;
background: red;
}

下面我们再来看一下如下demo。

<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM</title>
<style>
#app {margin: 100px;}
</style>
</head>
<body>
<div id="app">
<div class="shadow-cls">
<em class="shadowhost-content1">我是空智</em>
<em class="shadowhost-content2">我是龙恩</em>
</div>
<!-- 下面是一个模板 template -->
<template class="template">
<style>
/* 定义宿主样式 :host */
:host {
color: red;
}
/* 定义宿主hover状态下的样式 */
:host(:hover) {
color: black;
}
/* 分布节点的样式渲染需要用到 ::content */
::content > em {
color: blue;
background: red;
}
</style>
<div>
<h1>哈哈,</h1>
<content select=".shadowhost-content1"></content>
我来了
<content select=".shadowhost-content2"></content>!
</div>
</template>
</div>
<script type="text/javascript"> // 1. 获取影子宿主 shadow host
var shadowHost = document.querySelector('.shadow-cls');
// 2. 创建影子 shadow root
var shadowRoot = shadowHost.createShadowRoot();
// var shadowRoot = shadowHost.attachShadow({mode: 'open'});
// 3. 获取模板元素
var template = document.querySelector('.template');
/*
4. template.content 会返回一个文档片段。
5. 使用 document.importNode获取节点,true参数表示深度克隆
*/
shadowRoot.appendChild(document.importNode(template.content, true));
</script>
</body>
</html>

然后我们再在chrome浏览器下看下结果如下所示:

注意:上面的demo在chrome浏览器下会生效,在firefox或safari是不支持的。因此如果在平时的开发中我们需要引入对应的库进来才会支持。

理解Shadow DOM(一)的更多相关文章

  1. shadow dom

    初识shadow dom 我们先看个input="range"的表现: what amazing ! 一个dom能表现出这么多样式嘛? 无论是初学者和老鸟都是不肯相信的,于是在好奇 ...

  2. Shadow DOM及自定义标签

    参考链接:点我 一.什么是Shadow DOM Shadow DOM,直接翻译的话就是 影子 DOM,可以理解为潜藏在 DOM 结构中并且我们无法直接控制操纵的 DOM 结构.类似于下面这种结构 Sh ...

  3. 究竟什么是Shadow DOM?

    shadow dom 是什么? 顾名思义,shadow dom直译的话就是影子dom,但我更愿把它理解为DOM中的DOM.因为他能够为Web组件中的 DOM和 CSS提供了封装,实际上是在浏览器渲染文 ...

  4. 【shadow dom入UI】web components思想如何应用于实际项目

    回顾 经过昨天的优化处理([前端优化之拆分CSS]前端三剑客的分分合合),我们在UI一块做了几个关键动作: ① CSS入UI ② CSS作为组件的一个节点而存在,并且会被“格式化”,即选择器带id前缀 ...

  5. 封印术:shadow dom

    置顶文章:<纯CSS打造银色MacBook Air(完整版)> 上一篇:<鼠标滚动插件smoovejs和wowjs> 作者主页:myvin 博主QQ:851399101(点击Q ...

  6. 使用shadow dom封装web组件

    什么是shadow dom? 首先我们先来看看它长什么样子.在HTML5中,我们只用写如下简单的两行代码,就可以通过 <video> 标签来创建一个浏览器自带的视频播放器控件. <v ...

  7. 深度理解 Virtual DOM

    目录: 1 前言 2 技术发展史 3 Virtual DOM 算法 4 Virtual DOM 实现 5 Virtual DOM 树的差异(Diff算法) 6 结语 7 参考链接 1 前言 我会尽量把 ...

  8. shadow dom 隔离代码 封装

    Shadow DOM是指浏览器的一种能力,它允许在文档(document)渲染时插入一棵DOM元素子树,但是这棵子树不在主DOM树中.   Shadow DOM 解决了 DOM 树的封装问题.     ...

  9. 纯CSS菜单样式,及其Shadow DOM,Json接口 实现

    先声明,要看懂这篇博客要求你具备少量基础CSS知识, 当然如果你只是要用的话就随便了,不用了解任何知识 完整项目github链接:https://github.com/git-Code-Shelf/M ...

随机推荐

  1. 安装docker17.06.0版本报错和解决方法

    本人在自己电脑的虚拟机里安装docker ce 17.06.0版本的时候报如下错误: [root@manager2 yum.repos.d]# yum install docker-ce-17.06. ...

  2. JMeter 报告监听器导入.jtl结果文件报错解决方案

    JMeter 报告监听器导入.jtl结果文件报错解决方案   by:授客 QQ:1033553122   1. 问题描述 把jmeter压测时生成的 .jtl结果文件导入监听器报告中,弹出如下错误提示 ...

  3. 图说Oracle基础知识(一)

    本文主要对Oralce数据库操作的基础知识进行一下梳理,以便进行归纳总结.适用于未使用过Oracle数据库的读者,或需要学习Oracle数据库方面的基础知识.如有不足之处,还请指正. 关于SQL介绍的 ...

  4. spark大批量读取Hbase时出现java.lang.OutOfMemoryError: unable to create new native thread

    这个问题我去网上搜索了一下,发现了很多的解决方案都是增加的nproc数量,即用户最大线程数的数量,但我修改了并没有解决问题,最终是通过修改hadoop集群的最大线程数解决问题的. 并且网络上的回答多数 ...

  5. 以太坊之——golang以太坊接口调用

    Go语言具有简单易学.功能强大,可跨平台编译等众多优势,所以这里选择以Go语言切入以太坊. 开始之前需要以下环境: Ubuntu(这里以ubuntu16.04为例) geth Ubuntu16.04安 ...

  6. Java线程相关的热门面试题

    ---恢复内容开始--- 下面是Java线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序 ...

  7. Python比较(关系)运算符

    比较(关系)运算符 运 算 符 作 用   举 例  结 果  >  大于 'a'>'b'   False  <  小于  156<456  True  ==  等于  'c' ...

  8. c/c++ allocator 使用

    allocator 使用 作用:只开辟空间,不调用构造函数 操作一览表 allocator<T> a 定义一个名为a的allocator对象,它可以为类型为T的对象分配内存 a.alloc ...

  9. March 07th, 2018 Week 10th Wednesday

    Better later than never. 亡羊补牢,时犹未晚. Time and again all of us are told to complete the tasks assigned ...

  10. java操作elasticsearch实现前缀查询、wildcard、fuzzy模糊查询、ids查询

    1.前缀查询(prefix) //prefix前缀查询 @Test public void test15() throws UnknownHostException { //1.指定es集群 clus ...