打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
CSS魔术师Houdini API介绍

前言

今天想继续 CSS 的议题,常常会觉得学 CSS 的新技术不太划算,因为每次看到新的 Feature 出现,都只能当下兴奋几分钟,然后就会认命接受可能还要再等个五年才能真正使用的可能性…如果你有跟我一样的感受,那今天这篇文章或许可以带给你一丝丝希望。

在现今的 Web 开发中,JavaScript 几乎占据所有版面,除了控制页面逻辑与操作 DOM 对象以外,连 CSS 都直接写在 JavaScript 里面了,就算浏览器都还沒有实现的特性,总会有人做出对应的 Polyfills,让你快速的将新 Feature 应用到 Production 环境中,更別提我们还有 Babel 等工具帮忙转译。

而 CSS 就不同了,除了制定 CSS 标准规范所需的时间外,各家浏览器的版本、实战进度差异更是旷日持久,再加上 CSS 并非 Javascript 这样的动态语言,我们无法简单的提供 Polyfills,顶多利用 PostCSS、Sass 等工具來帮我們转译出浏览器能接受的 CSS,而剩下的就是浏览器的事了。

这边让我们回想一下,浏览器在网頁的渲染过程中,做了哪些事情?

浏览器的 Render Pipeline 中,JavaScript 与 Style 两个阶段会解析 HTML 并为加载的 JS 与 CSS 建立 Render Tree,也就是所谓的 DOM 与 CSSOM:(对于 Render Pipeline 与 Render Tree 若不了解,可以先看看我先前的文章 Front-end kata 60fps的快感)

而就现阶段的 Web 技术來看,开发者们能操作的就是通过 JS 去控制 DOM 与 CSSOM,來来影响页面的变化,但是对于接下來的 Layout、Paint 与 Composite 就几乎沒有控制权了。

既无法让各家浏览器快速并统一实战规格,亦不能轻易产生 Polyfills,所以到现在我们依然无法大胆使用 Flexbox,即便它早在 2009 年就被提出了…

但 CSS 并非就此驻足不前。

为了解決上述问题,为了让 CSS 的魔力不再浏览器把持,Houdini 就诞生了!( Houdini 是美国的伟大魔术师,擅长逃脱术,很适合想将 CSS 从浏览器中解放的概念)

CSS Houdini

CSS Houdini 是由一群來自 Mozilla, Apple, Opera, Microsoft, HP, Intel, IBM, Adobe 与 Google 的工程师所组成的工作小组,志在建立一系列的 API,让开发者能够介入浏览器的 CSS engine 操作,帶给开发者更多的解決方案,用来解決 CSS 长久以来的问题:

Cross-Browser isse

CSS Polyfill 的製作困難

Houdini task force 目前起草了一些 API 规范,并逐步努力让其通过 W3C,成为真正的 Web standards。由于都是草稿阶段,有些甚至只有规画,还未被真正写入规范,所以变动很大,有些我也不是很了解,所以就大致介绍一下,若有错误拜托务必告知!

另外,有兴趣的读者可以直接从这里 CSS Houdini Drafts 看详细內容( Drafts 的更新时间都非常近期,活跃中的草稿!)。

下面这张图我将 Google 提供的 Render pipeline 与 Houdini: Maybe The Most Exciting Development In CSS You’ve Never Heard Of 中提到的 pipeline 做个结合对比,显示出 Houdini 试图在浏览器的 Render pipeline 中提供哪些 API 给开发者使用:

其中灰色部分就是只在规划阶段,而黃色部份就是已经写入规范正在推行中。

Houdini API 介绍

CSS Properties and Values API

先介绍一个最能够使用的 API,除了 IE family 以外,Chrome、Firefox 与 Safari 都已经能夠直接使用了! caniuse

相信很多人都使用过 CSS Preprocessors,他给予开发者在 CSS 中使用变量的能力:

$font-size: 10px;$brightBlue: blue;.mark{ font-size: 1.5 * $font-size; color: $brightBlue}

但其实使用 Preprocessors 还是有其缺点,像是不同的 Preprocessors 就有不同的 Syntax,需要额外 setup 与 compile,而现在 CSS 已经有原生的变量可以使用了!就是 CSS Properties and Values API!

SCSS 与 Native CSS Custom Properties 的一个主要差別可以看下图:

原生的 CSS variable syntax:

/* declaration */--VAR_NAME: declaration-value>;/* usage */var(--VAR_NAME)

变量可以定义在 root element selector 內,也能在一般 selector 內,甚至是给別的变量 reuse:

/* root element selector (全域) */:root { --main-color: #ff00ff; --main-bg: rgb(200, 255, 255); --block-font-size: 1rem;}.btn__active::after{ --btn-text: 'This is btn'; /* 相等於 --box-highlight-text:'This is btn been actived'; */ --btn-highlight-text: var(--btn-text)' been actived'; content: var(--btn-highlight-text); /* 也能使用 calc 來做運算 */ font-size: calc(var(--block-font-size)*1.5);}body { /* variable usage */ color: var(--main-color);}

而有了变量以后,会为 CSS 带來什么好处应该很明显,他的 Use case 可以多写一篇文章來介绍了,或是可以直接看這篇的详细介绍,我这边介绍几个我觉得比较有趣的:

模拟一个特殊的 CSS rule:

单纯透过更改变量來达到改变 box-shadow 颜色

.textBox { --box-shadow-color: yellow; box-shadow: 0 0 30px var(--box-shadow-color);}.textBox:hover { /* box-shadow: 0 0 30px green; */ --box-shadow-color: green;}

动态调整某个 CSS rule 內的各別属性:

此外,我们也可以用 JavaScript 来控制:

const textBox = document.querySelector('.textBox');// GETconst Bxshc = getComputedStyle(textBox).getPropertyValue('--box-shadow-color');// SETtextBox.style.setProperty('--box-shadow-color', 'new color');

非常好用的特性,几乎所有主流浏览器都已经支持了,大家快来使用吧!

Box Tree API

Box tree API 并沒有出现在上图中,但在 Paintin API 中会用到其概念。

大家都知道在 DOM tree 中的每个元素都有一个 Box Modal,而在浏览器解析过程中,还会将其拆分成 fragments,至于什么是 fragments?以 drafts 中的例子來解释:

上面的 HTML 总共就会拆出七个 fragments:

最外层的 div

第一行的 box (包含 foo bar)

第二行的 box (包含 baz)

吃到 ::first-line 与 ::first-letter 的 f 也会被拆出來成独立的 fragments

只吃到 ::first-line 的 oo 只好也独立出來

吃到 ::first-line 与 包在 內的 bar 当然也是

在第二行底下且为 italic 的 baz

而 Box tree API 目的就是希望让开发者能够取得这些 fragments 的信息,至于取得后要如何使用,基本上应该会跟后面介绍的 Parser API、Layout API 与 Paint API 有关联,当我们能取得详细的 Box Modal 信息时,要客制化 Layout Module 才更为方便。

CSS Layout API

Layout API 顾名思义就是提供开发者撰写自己的 Layout module,Layout module 也就是用来 assign 给 display 属性的值,像是 display: grid 或 display: flex。你只要透过 registerLayout 的 function,传入 Layout 名称与 JS class 来定义 Layout 的逻辑即可,例如我们实战一个 block-like 的 Layout:

registerLayout('block-like', class extends Layout { static blockifyChildren = true; static inputProperties = super.inputProperties; *layout(space, children, styleMap) { const inlineSize = resolveInlineSize(space, styleMap); const bordersAndPadding = resolveBordersAndPadding(constraintSpace, styleMap); const scrollbarSize = resolveScrollbarSize(constraintSpace, styleMap); const availableInlineSize = inlineSize - bordersAndPadding.inlineStart - bordersAndPadding.inlineEnd - scrollbarSize.inline; const availableBlockSize = resolveBlockSize(constraintSpace, styleMap) - bordersAndPadding.blockStart - bordersAndPadding.blockEnd - scrollbarSize.block; const childFragments = []; const childConstraintSpace = new ConstraintSpace({ inlineSize: availableInlineSize, blockSize: availableBlockSize, }); let maxChildInlineSize = 0; let blockOffset = bordersAndPadding.blockStart; for (let child of children) { const fragment = yield child.layoutNextFragment(childConstraintSpace); // 這段控制 Layout 下的 children 要 inline 排列 // fragment 應該就是前述的 Box Tree API 內提到的 fragment fragment.blockOffset = blockOffset; fragment.inlineOffset = Math.max( bordersAndPadding.inlineStart, (availableInlineSize - fragment.inlineSize) / 2); maxChildInlineSize = Math.max(maxChildInlineSize, childFragments.inlineSize); blockOffset += fragment.blockSize; } const inlineOverflowSize = maxChildInlineSize + bordersAndPadding.inlineEnd; const blockOverflowSize = blockOffset + bordersAndPadding.blockEnd; const blockSize = resolveBlockSize( constraintSpace, styleMap, blockOverflowSize); return { inlineSize: inlineSize, blockSize: blockSize, inlineOverflowSize: inlineOverflowSize, blockOverflowSize: blockOverflowSize, childFragments: childFragments, }; }});

上面这段代码是来自 Houdini Draft 的示例,完整放上来是想给大家看一下实战一个 Layout 需要注意的细节有多少,其实并不是如想像中的轻松,相信未来会出现更多方便的 API 辅助开发。(放心接下来不会再有这么多 code 了 XD)

有了 Layout API,不管是自己实战或是拿別人写好的 Layout,你都可以直接如下方式使用:

.wrapper { display: layout('block-like');}

CSS Painting API

Painting API 与 Layout 类似,提供一个叫做 registerPaint 的方法:

定义 Paint Method,这边偷偷用到了待会要介紹的 CSS Properties:

registerPaint('simpleRect', class { static get inputProperties() { return ['--rect-color']; } paint(ctx, size, properties) { // 依據 properties 改變顏色 const color = properties.get('--rect-color'); ctx.fillStyle = color.cssText; ctx.fillRect(0, 0, size.width, size.height); }});

宣告使用:

.div-1 { --rect-color: red; width: 50px; height: 50px; background-image: paint(simpleRect);}.div-2 { --rect-color: yellow; width: 100px; height: 100px; background-size: 50% 50%; background-image: paint(simpleRect);}

.div-1 与 .div-2 就可以拥有各自定义宽高颜色的方形 background-image。

Worklets

在上述的 Layout API 与 Paint API 中,我们都有撰写一个 JS文件,用来定义新的属性,然后在 CSS 文件中呼叫取用,你可能会觉得那个 JS 文件就直接像一般 Web 嵌入 JS 的方式一样即可,但实际上并非如此,我们需要通过 Worklets 來帮我們载入。以上面的 Paint API 为例:

// add a WorkletpaintWorklet.addModule('simpleRect.js');// WORKLET 'simpleRect.js'registerPaint('simpleRect', class { static get inputProperties() { return ['--rect-color']; } paint(ctx, size, properties) { // 依據 properties 改變顏色 const color = properties.get('--rect-color'); ctx.fillStyle = color.cssText; ctx.fillRect(0, 0, size.width, size.height); }});

同理,Layout API 则是 layoutWorklet.addModule('blockLike.js')。

Worklets 光名字就有点像 Web Worker 了,都是独立于主要执行者之外,并且不直接与 DOM 互动。你可能会想那为何还需要有一個 Worklets?

因为 Houdini 是希望将开发者的程式码 hook 到 CSS engine 中运作,而根据规范內的叙述,web worker 相对笨重,不适合用来处理 CSS engine 這种可能会牵扯到数百万像素图片的工作。

所以可以推断,Worklets 的特点就是轻量以及生命周期较短。

共实除了 Layout Worklets 与 Paint Worklets 外,还有所谓的 Animation Worklet,虽然还沒有放入规范,但已经有在着手进行中,也有 Polyfills 了,Chrome 的 Sticky Header 就是采用 Houdini 的 Animation Worklet。Twitter 的 Header Effect 也是采用 Animation WorkletAnimation Worklet 是想介入 Render Pipeline 中的 Composite 步骤,也就是原本利用 JS 与 CSS 控制动画时,浏览器会重新执行的部分。

关于 Animation Worklet 的详细操作介绍可以看这份PPT: houdini-codemotion

CSS Parser API

Parser API 目前还是处在 Unofficial draft,但我相信如果这个 API 确认的话,对前端开发有绝对的帮助,她的概念是想让开发者能扩充浏览器解析 HTML、CSS 的功能,也就是说,你可以想办法让他看得懂最新定义的 pseudo-classes 或甚至是 element-queries 等等,这样就能正确解析出 CSSOM,从此不用再等浏览器更新。

CSS Typed OM

CSS Typed OM 就是 CSSOM 的強化版,最主要的功能在于将 CSSOM 所使用的字串值转换成具有型別意义的 JavaScript 表示形态,像是所有的 CSS Values 都有一个 base class interface:

interface CSSStyleValue { stringifier; static CSSStyleValue? parse(DOMString property, DOMString cssText); static sequence? parseAll(DOMString property, DOMString cssText);};

你可以如下操作 CSS style: (source from CSS Houdini- the bridge between CSS, JavaScript and the browser)

// CSS -> JSconst map = document.querySelector('.example').styleMap;console.log( map.get('font-size') );// CSSSimpleLength {value: 12, type: 'px', cssText: '12px'}// JS -> JSconsole.log( new CSSUnitValue(5, 'px') );// CSSUnitValue{value:5,unit:'px',type:'length',cssText:'5px'}// JS -> CSS// set style 'transform: translate3d(0px, -72.0588%, 0px);'elem.outputStyleMap.set('transform', new CSSTransformValue([ new CSSTranslation( 0, new CSSSimpleLength(100 - currentPercent, '%'), 0 )]));

根据 Drafts 的內容,有了型別定义,在 JavaScript 的操作上据说会有性能上的显著提升。此外,CSS Typed OM 也应用在 Parser API 与 CSS Properties API 上。

Font Metrics API

Font Metrics 也沒有出現在上方的 Houdini API on render pipeline 中,但它其实已经被写入 Draft 规范 中了。

老实说看不是很懂他的 spec 写的內容,但我猜测这东西的用途应该跟这篇文章 Deep dive CSS: font metrics, line-height and vertical-align 其中提到一个问题有关,(里面非常详细的介绍了 font metrics、line-height 与 vertical-align 在网页上如何互相影响,推荐大家有空的话耐心阅读一番。):

不同 font-family 在相同 font-size 下,所产生的 span 高度会不同。

要想控制 Font metrics,也就是控制字所占的宽高的话,目前可以先用 CSS Properties 來处理,根据已知字体的 font-metrics 动态算出我们要 apply 多少的 font-size:

p { /* 定義好我們已知字型的 Font metrics */ /* font metrics */ --font: Catamaran; --fm-capitalHeight: 0.68; --fm-descender: 0.54; --fm-ascender: 1.1; --fm-linegap: 0; /* 定義想要的高度 */ --capital-height: 100; /* 設定 font-family */ font-family: var(--font); /* 利用 Font metrics 的資訊與想定義的高度來計算出 font-size */ --computedFontSize: (var(--capital-height) / var(--fm-capitalHeight)); font-size: calc(var(--computedFontSize) * 1px);}

而想必 Font Metrics API 就是希望能 expose 出更方便的 API 來达成上述的事情。

总结

Web 开发基本上就是由 HTML、JS、CSS 三大要素构成,然而 JS 与 CSS 的发展差异却极其庞大,一个速度快到让人跟不上,一个则是等半天还是无法放心使用新规则,实在非常有趣…

但透过这次了解 Houdini API 的过程,理解到了 CSS 算是朝向好的方向前进,虽然很多离实际采用还有段距离,但至少我们已经能夠在最新的浏览器上使用 Custom Properties 了!CSS 的未來还是充滿希望的!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
CSS Houdini 通览
input file 美化 CSS样式美化
静态布局、自适应布局、流式布局、响应式布局、弹性布局等的概念和区别
让多个元素在一行显示的方法和技巧(面试题)
How Web Apps Work: Browsers, HTML, and CSS · Mark's Dev Blog
<font style="vertical-align: inherit;"><font style="vertical-align: inherit;">从零开始制作HEXO主题</font></font>
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服