理解Shadow DOM(一)
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(一)的更多相关文章
- shadow dom
初识shadow dom 我们先看个input="range"的表现: what amazing ! 一个dom能表现出这么多样式嘛? 无论是初学者和老鸟都是不肯相信的,于是在好奇 ...
- Shadow DOM及自定义标签
参考链接:点我 一.什么是Shadow DOM Shadow DOM,直接翻译的话就是 影子 DOM,可以理解为潜藏在 DOM 结构中并且我们无法直接控制操纵的 DOM 结构.类似于下面这种结构 Sh ...
- 究竟什么是Shadow DOM?
shadow dom 是什么? 顾名思义,shadow dom直译的话就是影子dom,但我更愿把它理解为DOM中的DOM.因为他能够为Web组件中的 DOM和 CSS提供了封装,实际上是在浏览器渲染文 ...
- 【shadow dom入UI】web components思想如何应用于实际项目
回顾 经过昨天的优化处理([前端优化之拆分CSS]前端三剑客的分分合合),我们在UI一块做了几个关键动作: ① CSS入UI ② CSS作为组件的一个节点而存在,并且会被“格式化”,即选择器带id前缀 ...
- 封印术:shadow dom
置顶文章:<纯CSS打造银色MacBook Air(完整版)> 上一篇:<鼠标滚动插件smoovejs和wowjs> 作者主页:myvin 博主QQ:851399101(点击Q ...
- 使用shadow dom封装web组件
什么是shadow dom? 首先我们先来看看它长什么样子.在HTML5中,我们只用写如下简单的两行代码,就可以通过 <video> 标签来创建一个浏览器自带的视频播放器控件. <v ...
- 深度理解 Virtual DOM
目录: 1 前言 2 技术发展史 3 Virtual DOM 算法 4 Virtual DOM 实现 5 Virtual DOM 树的差异(Diff算法) 6 结语 7 参考链接 1 前言 我会尽量把 ...
- shadow dom 隔离代码 封装
Shadow DOM是指浏览器的一种能力,它允许在文档(document)渲染时插入一棵DOM元素子树,但是这棵子树不在主DOM树中. Shadow DOM 解决了 DOM 树的封装问题. ...
- 纯CSS菜单样式,及其Shadow DOM,Json接口 实现
先声明,要看懂这篇博客要求你具备少量基础CSS知识, 当然如果你只是要用的话就随便了,不用了解任何知识 完整项目github链接:https://github.com/git-Code-Shelf/M ...
随机推荐
- Django的模板系统
一.语法 关于模板渲染只需要记住两种特殊符号(语法): {{ }} 和 {% %} (变量相关用{{ }} 逻辑相关用{% %}) 二.变量 在Django的模板语言中按照{{ 变量名 }}来使用 ...
- nginx中有关 root 和 alias的主要区别
举个例子给伙伴们区别就明显看出来了,例子如下: location /img/ { alias /var/www/image/; }注意:如果按照上述配置的话,则访问/img/目录里面的文件时,ning ...
- Java根据年龄段获取对应年份起始时间戳和最终时间戳、根据生日时间戳获取月份(与数据库的时间戳处理成的月份拼接成SQL条件)
1.传入年龄段,两个值,一个最小值,一个最大值,然后获取该年龄段的两个时间戳: (1)处理时间方法: /** * 根据年龄获取时间戳(开始年龄key取0,返回一年最后一秒时间戳,时间戳大:反之结束年龄 ...
- sh命令
sh或是执行脚本,或是切换到sh这个bash里,默认的shell是bash,你可以试试tcsh啊,csh啊,ksh,zsh什么的,看看别的shell是什么样子的.当然,linux中sh是链接到bash ...
- SAP生产机该不该开放Debuger权限
前段时间公司定制系统在调用SAP RFC接口的时候报错了,看错误消息一时半会儿也不知道是哪里参数数据错误,就想着进到SAP系统里面对这个接口做远程Debuger,跟踪一下参数变量的变化,结果发现根本就 ...
- 使用Visual Studio Team Services持续集成(四)——使用构建运行测试
使用Visual Studio Team Services持续集成(四)--使用构建运行测试 使用构建来运行测试来验证集成是一个很好的实践. MyHealth.API.IntegrationTests ...
- 记录一次spark连接mysql遇到的问题
版权声明:本文为博主原创文章,未经博主允许不得转载 在使用spark连接mysql的过程中报错了,错误如下 08:51:32.495 [main] ERROR - Error loading fact ...
- C#方法重载(overload)方法重写(override)隐藏(new)
一.重载:同一个作用域内发生(比如一个类里面),定义一系列同名方法,但是方法的参数列表不同.这样才能通过传递不同的参数来决定到底调用哪一个. 值得注意的是,方法重载只有通过参数不同来判断调用哪个方法, ...
- Android 闪烁动画
import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animatio ...
- c/c++ 智能指针 shared_ptr 和 new结合使用
智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...