Skip to main content

样式隔离

  1. 无作用域样式污染 CSS 没有本地作用域,所有声明的样式都是全局的(global styles) 换句话来说页面上任意元素只要匹配上某个选择器的规则,这个规则就会被应用上,

    而且规则和规则之间可以叠加作用(cascading)

    SPA 应用流行了之后这个问题变得更加突出了, 因为对于 SPA 应用来说所有页面的样式代码都会加载到同一个环境中,样式冲突的概率会大大加大。

    由于这个问题的存在,我们在日常开发中会遇到以下这些问题:

  • 很难为选择器起名字:为了避免和页面上其他元素的样式发生冲突,我们在起选择器名的时候一定要深思熟虑,起的名字一定不能太普通。举个例子,假如你为页面上某个作为标题的 DOM 节点定义一个叫做.title 的样式名,这个类名很大概率已经或者将会和页面上的其他选择器发生冲突,所以你不得不手动为这个类名添加一些前缀,例如.home-page-title 来避免这个问题

  • 团队多人合作困难:当多个人一起开发同一个项目的时候,特别是多个分支同时开发的时候,大家各自取的选择器名字可能有会冲突,可是在本地独立开发的时候这个问题几乎发现不了。当大家的代码合并到同一个分支的时候,一些样式的问题就会随之出现

  1. 无用的 CSS 样式堆积 进行过大型 Web 项目开发的同学应该都有经历过这个情景:在开发新的功能或者进行代码重构的时候,由于 HTML 代码和 CSS 样式之间没有显式的一一对应关系,我们很难辨认出项目中哪些 CSS 样式代码是有用的哪些是无用的,这就导致了我们不敢轻易删除代码中可能是无用的样式。这样随着时间的推移,项目中的 CSS 样式只会增加而不会减少 append-only stylesheets)。无用的样式代码堆积会导致以下这些问题:
  • 项目变得越来越重量级:加载到浏览器的 CSS 样式会越来越多,会造成一定的性能影响

  • 开发成本越来越高:开发者发现他们很难理解项目中的样式代码,甚至可能被大量的样式代码吓到,这就导致了开发效率的降低以及一些奇奇怪怪的样式问题的出现

  1. 基于状态的样式定义 对于 SPA 应用来说,特别是一些交互复杂的页面,页面的样式通常要根据组件的状态变化而发生变化 最常用的方式是通过不同的状态定义不同的 className 名,这种方案代码看起来十分冗余和繁琐,通常需要同时改动 js 代码和 css 代码

Radium 示例

Radium 和 styled-components 的最大区别是它生成的是标签内联样式(inline styles) 由于标签内联样式在处理诸如 media query 以及:hover:focus:active 等和浏览器状态相关的样式的时候非常不方便,所以 radium 为这些样式封装了一些标准的接口以及抽象 再来看一下 radium 在 CSS-in-JS Playground 的例子: 从上面的例子可以看出 radium 定义样式的语法和 styled-components 有很大的区别,它要求你使用 style 属性为 DOM 添加相应的样式 直接在标签内生成内联样式,内联样式相比于 CSS 选择器的方法有以下的优点: 自带局部样式作用域的效果,无需额外的操作

shadowDom

熟悉 web Components 的一定知道 Shadow DOM 可以实现样式隔离,由浏览器原生支持 我们经常在微前端领域看到 Shadow DOM,如下创建一个子应用

const shadow = document
.querySelector("#hostElement")
.attachShadow({ mode: "open" });
shadow.innerHTML =
'<sub-app>Here is some new text</sub-app><link rel="stylesheet" href="//unpkg.com/antd/antd.min.css">';

由于子应用的样式作用域仅在 shadow 元素下,那么一旦子应用中出现运行时越界跑到外面构建 DOM 的场景,必定会导致构建出来的 DOM 无法应用子应用的样式的情况。 比如 sub-app 里调用了 antd modal 组件,由于 modal 是动态挂载到 document.body 的,而由于 Shadow DOM 的特性 antd 的样式只会在 shadow 这个作用域下生效,结果就是弹出框无法应用到 antd 的样式。解决的办法是把 antd 样式上浮一层,丢到主文档里,但这么做意味着子应用的样式直接泄露到主文档了

优缺点分析

优点

  • 浏览器原生支持
  • 严格意义上的样式隔离,如 iframe 一样 缺点
  • 浏览器兼容问题
  • 只对一定范围内的 dom 结构起作用,上面微前端场景已经说明 普通业务开发我们还是用框架、如 Vue、React;Shadow DOM 适用于特殊场景,如微前端

预处理器

CSS 预处理器是一个能让你通过预处理器自己独有的语法的程序 市面上有很多 CSS 预处理器可供选择,且绝大多数 CSS 预处理器会增加一些原生 CSS 不具备的特性,例如

  • 代码混合

  • 嵌套选择器

  • 继承选择器 这些特性让 CSS 的结构更加具有可读性且易于维护 要使用 CSS 预处理器,你必须在 web 服务中安装 CSS 编译工具 我们常见的预处理器:

  • Sass

  • LESS

  • Stylus

  • PostCSS

优点:

  1. 利用嵌套,人为严格遵守嵌套首类名不一致,可以解决无作用域样式污染问题
  2. 可读性好,一目了然是那个 dom 节点,对于无用 css 删除,删除了相应 dom 节点后,对应的 css 也能比较放心的删除,不会影响到其他元素样式

缺点

  1. 需要借助相关的编译工具处理 预处理器是现代 web 开发中必备,结合 BEM 规范,利用预处理器,可以极大的提高开发效率,可读性,复用性