CSS `CSS Typed OM` 操作 `Layout Worklet` `Input` / `Output` `Properties`

嘿,大家好!今天咱们来聊聊CSS Typed OM(CSS类型化对象模型)这哥们儿,以及它如何与Layout Worklet、输入/输出Properties这几个小伙伴一起玩耍。这玩意儿听起来有点高大上,但其实理解起来并不难。准备好,咱们开始咯!

第一部分:CSS Typed OM,告别字符串的时代!

还记得咱们以前操作CSS属性的时候吗?全是字符串!

// 老方法,字符串大法好!
const element = document.getElementById('myElement');
element.style.width = '200px';
element.style.backgroundColor = 'red';

// 获取属性也是字符串
const width = element.style.width; // width 是 "200px"

字符串大法好是好,但缺点也很明显:

  • 类型不安全: 你可以随便往 width 属性里塞任何字符串,浏览器只能尽力解析,解析失败就GG。
  • 性能损耗: 浏览器需要反复解析字符串,转换成数值,单位等等,才能真正应用到元素上。

CSS Typed OM就是为了解决这些问题而生的。它把CSS属性表示成类型化的JavaScript对象,而不是简单的字符串。

// Typed OM,类型更安全,性能更高!
const element = document.getElementById('myElement');
element.attributeStyleMap.set('width', CSS.px(200));
element.attributeStyleMap.set('backgroundColor', CSS.rgb(255, 0, 0));

// 获取属性也是类型化的对象
const width = element.attributeStyleMap.get('width'); // width 是 CSSUnitValue 对象
console.log(width.value); // 200
console.log(width.unit);  // "px"

看到了吗? CSS.px(200) 创建了一个表示200像素的 CSSUnitValue 对象。 CSS.rgb(255, 0, 0) 创建了一个表示红色RGB值的 CSSRGB 对象。 这样,浏览器就不用再费劲解析字符串了,直接操作这些类型化的对象,效率自然就高了。

常用的类型化对象:

对象类型 描述 示例
CSSUnitValue 表示一个带单位的数值,例如像素、em、rem等 CSS.px(200), CSS.em(1.5)
CSSKeywordValue 表示一个CSS关键字,例如 auto, inherit CSS.keyword('auto')
CSSStyleValue 所有类型化对象的基类
CSSRGB 表示一个RGB颜色值 CSS.rgb(255, 0, 0)
CSSMatrix 表示一个2D或3D变换矩阵 new DOMMatrix([1, 0, 0, 1, 0, 0])
CSSImageValue 表示一个图像值(例如url()) new CSSImageValue(new URL('image.png'))

如何使用Typed OM?

Typed OM 主要通过 element.attributeStyleMap 接口来操作元素的样式。 attributeStyleMap 是一个 StylePropertyMap 对象,提供了以下方法:

  • set(property, value): 设置CSS属性的值。
  • get(property): 获取CSS属性的值。
  • has(property): 检查CSS属性是否存在。
  • delete(property): 删除CSS属性。
  • clear(): 清空所有CSS属性。
  • keys(): 返回一个包含所有属性名的迭代器。
  • values(): 返回一个包含所有属性值的迭代器。
  • entries(): 返回一个包含所有属性名和属性值键值对的迭代器。

兼容性提示: Typed OM 并不是所有浏览器都完全支持,使用前最好检查一下兼容性。 可以通过 window.CSS 对象是否存在来判断浏览器是否支持Typed OM。

第二部分:Layout Worklet,自定义布局,突破CSS的限制!

CSS很强大,但有时候还是会捉襟见肘。比如,你想实现一个非常规的布局,CSS可能就无能为力了。 这时候,Layout Worklet就派上用场了。

Layout Worklet 允许你使用JavaScript来编写自定义的布局算法,然后让浏览器来执行。 这就像给CSS装上了一个强大的外挂,让你可以实现各种奇葩的布局效果。

Layout Worklet 的工作流程:

  1. 编写布局算法: 用JavaScript编写一个Layout Worklet模块,定义布局算法。
  2. 注册 Worklet: 使用 CSS.layoutWorklet.addModule() 方法注册Layout Worklet模块。
  3. 应用布局: 在CSS中使用 layout() 函数来应用自定义布局。

一个简单的Layout Worklet例子:

假设我们要实现一个简单的瀑布流布局。 首先,创建一个名为 waterfall-layout.js 的文件,编写Layout Worklet模块:

// waterfall-layout.js
registerLayout('waterfall-layout', class {
  static get inputProperties() {
    return ['--column-count', '--column-gap'];
  }

  async intrinsicSizes(children, edges, styleMap) {
    // 返回 intrinsic size 的逻辑 (可选)
  }

  async layout(children, edges, styleMap, layoutNextFragment) {
    const columnCount = parseInt(styleMap.get('--column-count').value) || 3;
    const columnGap = parseInt(styleMap.get('--column-gap').value) || 10;
    const childInlineSize = children[0].inlineSize; // 获取子元素的宽度
    const columnWidth = (childInlineSize);

    const columns = Array(columnCount).fill(0); // 初始化每列的高度
    const result = [];

    for (const child of children) {
      // 找到高度最小的列
      let minHeight = Infinity;
      let columnIndex = 0;
      for (let i = 0; i < columnCount; i++) {
        if (columns[i] < minHeight) {
          minHeight = columns[i];
          columnIndex = i;
        }
      }

      // 计算子元素的位置
      const x = columnIndex * (columnWidth + columnGap);
      const y = columns[columnIndex];

      // 添加子元素的布局信息
      result.push({
        inlineSize: childInlineSize,
        blockSize: child.blockSize,
        inlineOffset: x,
        blockOffset: y,
      });

      // 更新列的高度
      columns[columnIndex] += child.blockSize + columnGap;
    }

    // 返回布局结果
    return {
      size: {
        inlineSize: columnCount * (columnWidth + columnGap) - columnGap,
        blockSize: Math.max(...columns),
      },
      children: result,
    };
  }
});

这个Layout Worklet 接收两个输入属性: --column-count (列数) 和 --column-gap (列间距)。 layout() 方法是核心,它接收所有子元素,计算每个子元素的位置,并返回布局结果。

然后,在你的HTML文件中,注册Layout Worklet模块,并应用自定义布局:

<!DOCTYPE html>
<html>
<head>
  <title>Layout Worklet Example</title>
  <style>
    #container {
      display: grid; /* 这里必须使用 grid 或 inline-grid,以便触发 Layout Worklet */
      grid-template-columns: 1fr;
      width: 800px;
      margin: 0 auto;
      padding: 20px;
      background-color: #f0f0f0;
      position: relative; /* 确保子元素可以被正确定位 */
      layout: waterfall-layout;
      --column-count: 3;
      --column-gap: 10px;
    }

    #container > div {
      background-color: #ddd;
      margin-bottom: 10px;
      padding: 10px;
      box-sizing: border-box; /* 确保 padding 不会影响元素的宽度 */
    }
  </style>
</head>
<body>
  <div id="container">
    <div>Item 1</div>
    <div>Item 2</div>
    <div>Item 3</div>
    <div>Item 4</div>
    <div>Item 5</div>
    <div>Item 6</div>
    <div>Item 7</div>
    <div>Item 8</div>
    <div>Item 9</div>
  </div>

  <script>
    CSS.layoutWorklet.addModule('waterfall-layout.js');
  </script>
</body>
</html>

注意:

  • display: grid (或 inline-grid) 是必须的,因为Layout Worklet是基于Grid布局的。
  • layout: waterfall-layout 告诉浏览器使用我们自定义的 waterfall-layout 布局。
  • --column-count--column-gap 是传递给Layout Worklet的输入属性。

第三部分:Input/Output Properties,沟通的桥梁!

Layout Worklet 需要接收CSS属性作为输入,才能进行布局计算。 同时,它也可以输出一些属性,供其他CSS样式使用。 这就是Input/Output Properties的作用。

Input Properties:

Input Properties 允许Layout Worklet 接收CSS属性的值。 在Layout Worklet 模块中,通过 static get inputProperties() 方法来声明需要接收的输入属性。

// Layout Worklet 模块
registerLayout('my-layout', class {
  static get inputProperties() {
    return ['--my-property', '--another-property'];
  }

  async layout(children, edges, styleMap, layoutNextFragment) {
    const myPropertyValue = styleMap.get('--my-property').value;
    const anotherPropertyValue = styleMap.get('--another-property').value;

    // 使用输入属性进行布局计算
    // ...
  }
});

在CSS中,通过CSS变量来设置Input Properties的值:

#container {
  layout: my-layout;
  --my-property: 10px;
  --another-property: red;
}

Output Properties:

Output Properties 允许Layout Worklet 输出一些属性,供其他CSS样式使用。 目前,Output Properties 还没有正式的标准,但是可以通过一些hack的方式来实现类似的功能。

一种常见的hack方式是使用 CSS.registerProperty() 方法注册一个自定义属性,然后在Layout Worklet 中修改这个属性的值。

// 注册自定义属性
CSS.registerProperty({
  name: '--my-output-property',
  syntax: '<number>',
  inherits: false,
  initialValue: 0,
});

// Layout Worklet 模块
registerLayout('my-layout', class {
  async layout(children, edges, styleMap, layoutNextFragment) {
    // 计算输出属性的值
    const outputValue = 100;

    // 设置输出属性的值 (hack方式)
    document.documentElement.style.setProperty('--my-output-property', outputValue);

    // ...
  }
});

在CSS中,可以使用 var() 函数来获取Output Properties的值:

#element {
  width: var(--my-output-property);
}

更规范的输出方式 (未来展望):

未来,可能会有更规范的Output Properties API,例如:

// Layout Worklet 模块 (未来可能的API)
registerLayout('my-layout', class {
  static get outputProperties() {
    return ['--my-output-property'];
  }

  async layout(children, edges, styleMap, layoutNextFragment) {
    // 计算输出属性的值
    const outputValue = 100;

    // 设置输出属性的值
    return {
      size: { ... },
      children: [ ... ],
      outputProperties: {
        '--my-output-property': CSS.px(outputValue),
      },
    };
  }
});

总结:

技术点 作用 示例
CSS Typed OM 使用类型化的JavaScript对象来表示CSS属性,提高性能和类型安全性。 element.attributeStyleMap.set('width', CSS.px(200));
Layout Worklet 允许你使用JavaScript编写自定义的布局算法,突破CSS的限制。 registerLayout('waterfall-layout', class { ... });
Input Properties 允许Layout Worklet 接收CSS属性的值。 static get inputProperties() { return ['--column-count', '--column-gap']; }
Output Properties 允许Layout Worklet 输出一些属性,供其他CSS样式使用。(目前需要hack,未来可能有更规范的API) CSS.registerProperty({ name: '--my-output-property', ... }); (Hack方式) 未来可能: return { outputProperties: { '--my-output-property': CSS.px(outputValue) } };

总结的总结:

Typed OM 让我们告别了字符串操作CSS属性的时代,让代码更健壮,性能更高。 Layout Worklet 则赋予了我们自定义布局的能力,让我们能够创造出各种炫酷的页面效果。 Input/Output Properties 则是连接Layout Worklet 和 CSS世界的桥梁,让它们能够互相沟通,协同工作。

希望今天的讲解对大家有所帮助! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注