CSS in JS
是一种解决css问题想法的集合,它是将 CSS 样式写在 JavaScript 文件中,而不需要独立出.css
、.less
之类的文件。将 CSS 放在 JS 中使我们更方便的使用 JS 的变量、模块化、tree-shaking。还解决了 CSS 中的一些问题,比如更方便解决基于状态的样式,更容易追溯依赖关系,生成唯一的选择器来锁定作用域。实现CSS in JS
的第三方库有像 JSS、styled-components 等。
因此,JS in CSS
就是可以在 CSS 中使用 JavaScript 脚本,实现是基于 CSS Houdini。
Houdini
Houdini 是一组底层API,它们公开了 CSS 引擎的各个部分,从而使开发人员能够通过加入浏览器渲染引擎的样式和布局过程来扩展 CSS。 Houdini是一组API,它们使开发人员可以直接访问CSS 对象模型 (CSSOM),使开发人员可以编写浏览器可以解析为 CSS 的代码,从而创建新的CSS功能,而无需等待它们在浏览器中本地实现。使得可参与到以下的每个环节中(常规 JS 只能操作 DOM / 部分CSSOM):
CSS Houdini 新的API可以扩展 css 的变量,可以定义 CSS 变量的 类型
,初始值
,继承
,比 CSS 变量更强大灵活。
Houdini 提供了两种自定义属性的注册方式,分别是在 JS 和 CSS 中。
CSS.registerProperty({
name: '--my-prop', // String 自定义属性名
syntax: '<color>', // String 如何去解析当前的属性,即属性类型,默认 *
inherits: false, // Boolean 如果是true,子节点将会继承
initialValue: '#c0ffee', // String 属性点初始值
});
或
@property --my-prop {
syntax: '<color>';
inherits: false;
initial-value: #c0ffee;
}
还可以使用+
使syntax
属性支持一个或多个类型,也可以使用|
来分割。更多syntax
属性值:
Worklets
Worklets
是渲染引擎的扩展,从概念上来讲它类似于 Web Workers,但有几个重要的区别:
- 设计为并行,每个
Worklets
必须始终有两个或更多的实例,它们中的任何一个都可以在被调用时运行 - 作用域较小,限制不能访问全局作用域的API(Worklet的函数除外)
- 渲染引擎会在需要的时候调用他们,而不是我们手动调用
Worklet 是一个 JavaScript 模块,通过调用 worklet 的addModule方法(它是个 Promise )来添加。比如 registerLayout
, registerPaint
, registerAnimator
都是需要放在 Worklet 中。
Worklets 的生命周期流程如下:
- Worklet的生命周期从渲染引擎内开始;
- 对于 JavaScript,渲染引擎启动 JavaScript 主线程;
- 然后启动多个 workle t进程,并且可以运行。这些进程理想情况下是独立于主线程的线程,这样就不会阻塞主线程(但它们也不需要阻塞);
- 然后在主线程中加载浏览器的 JavaScript;
- 该 JavaScript 调用
worklet.addModule
并异步加载一个 worklet; - 加载后,将 worklet 加载到两个或多个可用的 worklet 流程中;
- 当需要时,渲染引擎将通过从加载的 Worklet 中调用适当的处理函数来执行 Worklet。该调用可以针对任何并行的 Worklet 实例。
Typed OM
Typed OM
是对现有的CSSOM
的扩展,并实现 Parsing API
和 Properties & Values API
相关的特性。它将 CSS 值转化为有意义类型的 JavaScript 对象,而不是像现在的字符串。。
现在读取 CSS 值增加了新的基类CSSStyleValue
,有许多的子类可以更加精准的描述 CSS 值的类型:
使用 Typed OM
主要有两种方法:
- 通过
attributeStyleMap
设置和获取有类型的行间样式; - 通过
computedStyleMap
获取元素完整的Typed OM
样式。
Layout
CSS Layout API 暴露了一个 registerLayout
方法给开发者实现自己的布局算法,类似 display: flex 等,接收一个布局名(layout name)作为后面在 CSS 中使用的属性值,还有一个包含有这个布局逻辑的 JavaScript 类。
<style>
my-div {
display: layout(my-layout);
}
</style>
<script>
await CSS.layoutWorklet.addModule('layout-worklet.js');
</script>
/** layout-worklet.js */
registerLayout('my-layout', class {
static get inputProperties() { return ['--foo']; }
static get childrenInputProperties() { return ['--bar']; }
async intrinsicSizes(children, edges, styleMap) {}
async layout(children, edges, constraints, styleMap) {}
});
Painting
可以在 CSS background-image
中使用 Painting API ,可以使用 Canvas 2d上下文,根据元素的大小控制图像,还可以使用自定义属性。
<script>
await CSS.paintWorklet.addModule('paint-worklet.js');
</script>
/** layout-worklet.js */
registerPaint('sample-paint', class {
static get inputProperties() { return ['--foo']; }
static get inputArguments() { return ['<color>']; }
static get contextOptions() { return {alpha: true}; }
paint(ctx, size, props, args) { }
});
Animation
Animation API 可以控制基于用户输入的关键帧动画,并且以非阻塞的方式。还能更改一个 DOM 元素的属性,不过是不会引起渲染引擎重新计算布局或者样式的属性,类似 transform、opacity 或者滚动条位置(scroll offset)。Animation API
的使用需要通过new
一个WorkletAnimation
来注册worklet:
<script>
await CSS.animationWorklet.addModule('animation-worklet.js');
// 需要添加动画的元素
const elem = document.querySelector('#my-elem');
const scrollSource = document.scrollingElement;
const timeRange = 1000;
const scrollTimeline = new ScrollTimeline({
scrollSource,
timeRange,
});
const effectKeyframes = new KeyframeEffect(
elem,
// 动画需要绑定的关键帧
[
{transform: 'scale(1)'},
{transform: 'scale(.25)'},
{transform: 'scale(1)'}
],
{
duration: timeRange,
},
);
new WorkletAnimation(
'sample-animator',
effectKeyframes,
scrollTimeline,
{},
).play();
</script>
/** animation-worklet.js */
registerAnimator('sample-animator', class {
constructor(options) {
}
animate(currentTime, effect) {
effect.localTime = currentTime;
}
});
Parser
允许开发者自由扩展 CSS 词法分析器。
解析规则:
const background = window.cssParse.rule("background: green");
console.log(background.styleMap.get("background").value) // "green"
const styles = window.cssParse.ruleSet(".foo { background: green; margin: 5px; }");
console.log(styles.length) // 5
console.log(styles[0].styleMap.get("margin-top").value) // 5
console.log(styles[0].styleMap.get("margin-top").type) // "px"
解析CSS:
const style = fetch("style.css")
.then(response => CSS.parseStylesheet(response.body));
style.then(console.log);
Font Metrics
Font Metrics API 提供一些方法来测量在屏幕上呈现的文本元素的尺寸,将允许开发者控制文本元素在屏幕上呈现的方式。使用当前功能很难或无法测量这些值,例如:
- flex布局:
align-items
baseline 特性。需要知道每一个 flex 盒子中第一个元素的基线位置。 - 首字母: 需要知道每个字母的基线高度和字母最大的高度,以及换行内容的基线长度。
- 单个字形的前进和后退。
- 换行: 需要访问字体数据,文本的所有样式输入以及布局信息(可用的段落长度等)。
- 元素中的每一个
line boxes
都需要一个基线。(line boxes
代表包含众多inline boxes
的这行)
因此该API将使开发者可以更轻松地创建与文本和字体相关的 CSS 特性。