目录
综述
本文介绍Angular中的样式封装机制,以及背后的基本原理,供您参考。
我们在写一个Angular Component的时候经常要指定该Component需要的样式。样式可以通过inline的方式在styles属性中定义,也可以通过指定一个外部样式文件定义需要的CSS。无论哪种方式,定义的样式都只在当前Component可见,其他Component无法访问。这是Angular Component对样式处理的默认行为,但不是唯一行为。事实上,组件的encapsulation属性可以配置上述行为。下面我来一一介绍。
不封装
这种情况下,encapsulation的属性值为None,即不对样式进行封装。此时,Component中定义的样式会暴露给其他组件。代码如下:
@Component({
selector: 'craft-man-styles',
templateUrl: './craft-man-styles.component.html',
styleUrls: ['./craft-man-styles.component.css'],
encapsulation: ViewEncapsulation.None
})
从Chrome的调试器中,我们可以看到生成的HTML以及样式如下:
我们可以看到,在head标签中定义了新的style标签,用来存放我们自定义的样式craft-man-title。这个style标签没有任何修饰,因此是全局可见的。这意味着,如果这个样式的名称被定义过,那么就会产生冲突。
仿真(Emulated)封装
下面我们再来看看Emulated 的样式封装。这是Angular Component默认采用的封装方式。所谓“仿真”,是相对于第三种方式“原生”而言的。意思是它虽然实现了样式的封装,但是一种类似work around的方式。定义Component时只需要设置:
encapsulation: ViewEncapsulation.Emulated
相应的在生成的HTML与None的方式有所不同:
请注意在style标签中生成的CSS样式,在名称后面附带了属性选择器[_ngcontent-c0]。相应地在Component的template中也使用了同样的属性。因此,这个style中的CSS样式只会应用于我们自己的Component。这个属性选择器是Angular在运行时生成的,目的就是将自定义的CSS作用域限定在组件内部。这些样式外部是可见的,但是只会在应用了特定属性的元素上应用,因此是一种仿真封装。
原生(Native)封装
最后我们来看看原生封装方式。在代码中设置如下:
encapsulation: ViewEncapsulation.Native
与仿真封装不同的是,该方式会使用影子DOM(Shadow DOM)的技术来实现样式封装。这里简单介绍下Shadow DOM的概念。
Shadow DOM是Web Component标准中的一个重要概念。它提供了一种DOM的嵌套和封装方式。通常来说,一个页面的document只对应一个DOM Tree,而Shadow DOM可以帮助我们将一个不可见的影子DOM嵌入页面上的某个元素,并且这个影子DOM只在该元素内部可见。Shadow DOM提供了一定的封装性,比如样式、JavaScript代码都可以封装到DOM内部。这就是所谓“原生”封装的内涵了。如果想要在某个元素上定义一个Shadow DOM,可以使用createShadowRoot()函数。想更深入了解Shadow DOM的相关技术,可以参考下面资料中的链接。
我们可以看看这种情况下的页面:
我们看到自定义的样式没有在head标签中生成,而是直接写在Component的template中。注意#shadow-root (open)这个特殊元素,它就是浏览器生成的Shadow DOM。我们自定义的Component中所有属性包括HTML、CSS等都定义在这个嵌套DOM中,该DOM对其他元素并不可见,从而实现了样式封装。
参考资料
- 《Angular 权威教程》 第14章 高级组件
- Shadow DOM 介绍: https://toddmotto.com/web-components-concepts-shadow-dom-imports-templates-custom-elements/#shadowdom
- Shadow DOM 文档:https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM
- 本文示例源代码: