样式隔离演进史
样式隔离的演进历程: 从命名约定到 Web 标准
计算机科学中有两大难题, 缓存失效和命名冲突, 早期 Web 开发中, 随着组件的增多, 为不同样式取一个不重复的合适命名变得愈发复杂,样式隔离的需求也由此产生
2000 年代, 一些开发人员使用命名约定进行人工隔离, 例如 BEM (Block__Element--Modifier), OOCSS, SMACSS 等.
此类方案都专注于将样式抽象为可复用的名称, 但远没有原子化那样精细, 也是最容易上手的隔离方案.
这种隔离方案历史悠久, 但并未被完全抛弃, Bootstrap, Ant Design 等主流库都有此隔离方案参与或部分参与.
通过操作 DOM Api, 进行动态样式插入. 此种方案生成将生成较为复杂的选择器以避免命名冲突. 由于经常需要生成涵盖各种状态的大量样式, 或者根据状态变化大量修改样式, 其性能也会损失严重.
一些前端框架内置了样式隔离的方案, 可以在不改变原有样式的情况下, 实现样式的范围化.
为应用样式的组件添加唯一标记例如 data-v-*
和 _ngcontent
, 并在构建或渲染时为样式选择器附加该属性.
从文档根开始, 生成直到应用样式的组件的全路径样式选择器, 确保样式仅匹配该路径下的元素.
这种方案的优势是简单, 无需额外的构建步骤, 但是它的劣势是性能问题, 因为它需要在运行时遍历 DOM 树, 并且需要在运行时生成样式选择器, 全路径选择也容易比属性隔离生成更繁琐的样式.
CSS 模块是一种将 CSS 类名局部化的技术, 它要求 CSS 文件以 .module.css
结尾, 通过将类名进行哈希, 生成不易重复的新类名.
得益于构建工具的支持, 我们也可以在不改变原有类名的情况下, 实现样式的范围化, 并且能够在构建阶段将类别转换到确定的对象.
因此每一个 CSS 模块都包含: 一个样式表, 需要插入到文档. 一个 JS 对象, 用于映射类名.
即使使用了 CSS 模块, 仍有很多应用在使用 BEM 命名约定来规范模块里的类名. 因为 BEM 能够清晰地表达组件的结构和状态. 使用 BEM 命名可以轻松区分不同的元素和修饰符, 不使用类名又容易将样式传递到子元素.
@scope 是一种新的 CSS 规则, 它允许你定义一个精确的作用域.
@scope (scope root) {
}
@scope (scope root) to (scope limit) {
}
@scope 也可以内联到 style 元素内, 使用方式非常自由.
<div>
<style>
@scope {
}
</style>
</div>
可惜此规则的浏览器兼容性过低, 尚未得到广泛支持. 对于不支持的设备, 你可以尝试 这个 Polyfill.
Shadow DOM 允许为元素创建独立的子树,其内部样式与主文档完全隔离.
仅有 CSS 变量, 部分 CSS 规则和 ::part
选择器能够穿透 Shadow DOM.
Shadow DOM 虽然是一个很好的解决方案, 它总是被主流工具所忽略, 有很多项目都会添加会影响 Shadow DOM 的样式, 例如使用全局通配符样式:
* {
border: 0;
}
这将会破坏声明了边框样式的 Web 组件的样式. 却成为了很多库的默认行为.
理想的做法是仅针对特定元素重置样式, 例如:
input,
textarea,
select,
button,
fieldset,
iframe {
border: 0;
}