從CSS到houdini

0. 前言

平時寫CSS,感受有不少多餘的代碼或者很差實現的方法,因而有了預處理器的解決方案,主旨是write less &do more。其實原生css中,用上css變量也不差,加上bem命名規則只要嵌套不深也能和less、sass的嵌套媲美。在一些動畫或者炫酷的特效中,不用js的話多是用了css動畫、svg的animation、過渡,複雜動畫實現用了js的話可能用了canvas、直接修改style屬性。用js的,而後有沒有想過一個問題:「要是canvas那套放在dom上就爽了」。由於複雜的動畫頻繁操做了dom,違背了滾瓜爛熟的「性能優化之一:儘可能少操做dom」的規矩,嘴上說着不要,手卻是很誠實地ele.style.prop = <newProp>,但是要實現效果這又是迫不得已或者大大減少工做量的方法。css

咱們都知道,瀏覽器渲染的流程:解析html和css(parse),樣式計算(style calculate),佈局(layout),繪製(paint),合併(composite),修改了樣式,改的環節越深代價越大。js改變樣式,首先是操做dom,整個渲染流程立刻從新走,可能走到樣式計算到合併環節之間,代價大,性能差。而後痛點就來了,瀏覽器有沒有能直接操做前面這些環節的方法呢而不是依靠js?有沒有方法不用js操做dom改變style或者切換class來改變樣式呢?html

因而就有CSS Houdini了,它是W3C和那幾個頂級公司的工程師組成的小組,讓開發者能夠經過新api操做CSS引擎,帶來更多的自由度,讓整個渲染流程均可以被開發者控制。上面的問題,不用js就能夠實現曾經須要js的效果,並且只在渲染過程當中,就已經按照開發者的代碼渲染出結果,而不是渲染完成了再從新用js強行走一遍流程。git

關於houdini最近動態可點擊這裏 上次CSS大會知道了有Houdini的存在,那時候只有cssom,layout和paint api。前幾天忽然發現,Animation api也有了,不得不說,之後極可能是Houdini遍地開花的時代,如今得進一步瞭解一下了。一句話:這是css in js到js in css的轉變github

1. CSS變量

若是你用less、sass只爲了人家有變量和嵌套,那用原生css也是差很少的,由於原生css也有變量:chrome

好比定義一個全局變量–color(css變量雙橫線開頭)canvas

:root { --color: #f00; } 

使用的時候只要var一下api

.f{ color: var(--color); } 

咱們的html:瀏覽器

<div class="f">123</div> 

因而,紅色的123就出來了。sass

css變量還和js變量同樣,有做用域的:性能優化

:root { --color: #f00; } .f { --color: #aaa } .g{ color: var(--color); } .ft { color: var(--color); } 

html:

<div className="f"> <div className="ft">123</div> </div> <div className=""> <div className="g">123</div> </div> 

因而,是什麼效果你應該也很容易就猜出來了:

css能搞變量的話,咱們就能夠作到修改一處牽動多處的變更。好比咱們作一個像準星同樣的四個方向用準線鎖定鼠標位置的效果: 用css變量的話,比傳統一個個元素設置style優雅多了:

<div id="shadow"> <div class="x"></div> <div class="y"></div> <div class="x_"></div> <div class="y_"></div> </div> 
:root{ --x: 0px; --y: 0px; } body{ margin: 0 } #shadow{ width: 50%; height: 600px; border: #000 1px solid; position: relative; margin: 0; } .x, .y, .x_, .y_ { position: absolute; border: #f00 2px solid; } .x { top: 0; left: var(--x); height: 20px; width: 0; } .y { top: var(--y); left: 0; height: 0; width: 20px; } .x_ { top: 600px; left: var(--x); height: 20px; width: 0; } .y_ { top: var(--y); left: 100%; height: 0; width: 20px; } 
const style = document.documentElement.style shadow.addEventListener('mousemove', e => { style.setProperty(`--x`, e.clientX + 'px') style.setProperty(`--y`, e.clientY + 'px') }) 

那麼,對於github的404頁面這種內容和鼠標位置有關的頁面,思路是否是一會兒就出來了

2. CSS type OM

都有DOM了,那CSSOM也理所固然存在。咱們平時改變css的時候,一般是直接修改style或者切換類,實際上就是操做DOM來間接操做CSSOM,而type om是一種把css的屬性和值存在attributeStyleMap對象中,咱們只要直接操做這個對象就能夠作到以前的js改變css的操做。另一個很重要的點,attributeStyleMap存的是css的數值而不是字符串,並且支持各類算數以及單位換算,比起操做字符串,性能明顯更優。

接下來,基本脫離不了window下的CSS這個屬性。在使用的時候,首先,咱們能夠採起漸進式的作法: if('CSS' in window){...}

2.1 單位

CSS.px(1); // 1px 返回的結果是:CSSUnitValue {value: 1, unit: "px"} CSS.number(0); // 0 好比top:0,也常常用到 CSS.rem(2); //2rem new CSSUnitValue(2, 'percent'); // 還能夠用構造函數,這裏的結果就是2% // 其餘單位同理 

2.2 數學運算

本身在控制檯輸入CSSMath,能夠看見的提示,就是數學運算

new CSSMathSum(CSS.rem(10), CSS.px(-1)) // calc(10rem - 1px),要用new否則報錯 new CSSMathMax(CSS.px(1),CSS.px(2)) // 顧名思義,就是較大值,單位不一樣也能夠進行比較 

2.3 怎麼用

既然是新的東西,那就有它的使用規則。

  • 獲取值element.attributeStyleMap.get(attributeName),返回一個CSSUnitValue對象
  • 設置值element.attributeStyleMap.set(attributeName, newValue),設置值,傳入的值能夠是css值字符串或者是CSSUnitValue對象

固然,第一次get是返回null的,由於你都沒有set過。「那我仍是要用一下getComputedStyle再set咯,這還不是和以前的差很少嗎?」

實際上,有一個相似的方法:element.computedStyleMap,返回的是CSSUnitValue對象,這就ok了。咱們拿前面的第一部分CSS變量的代碼測試一波

document.querySelector('.x').computedStyleMap().get('height') // CSSUnitValue {value: 20, unit: "px"} document.querySelector('.x').computedStyleMap().set('height', CSS.px(0)) // 不見了 

3. paint API

paint、animation、layout API都是以worker的形式工做,具體有幾個步驟:

  • 創建一個worker.js,好比咱們想用paint API,先在這個js文件註冊這個模塊registerPaint(‘mypaint’, class),這個class是一個類下面具體講到
  • 在html引入CSS.paintWorklet.addModule(‘worker.js’)
  • 在css中使用,background: paint(mypaint) 主要的邏輯,全都寫在傳入registerPaint的class裏面。paint API很像canvas那套,實際上能夠看成本身畫一個img。既然是img,那麼在全部的能用到圖片url的地方都適合用paint API,好比咱們來本身畫一個有點炫酷的背景(滿屏隨機顏色方塊)。有空的話能夠想一下js怎麼作,再對比一下paint API的方案。
// worker.js class RandomColorPainter { // 能夠獲取的css屬性,先寫在這裏 // 我這裏定義寬高和間隔,從css獲取 static get inputProperties() { return ['--w', '--h', '--spacing']; } /** * 繪製函數paint,最主要部分 * @param {PaintRenderingContext2D} ctx 相似canvas的ctx * @param {PaintSize} PaintSize 繪製範圍大小(px) { width, height } * @param {StylePropertyMapReadOnly} props 前面inputProperties列舉的屬性,用get獲取 */ paint(ctx, PaintSize, props) { const w = (props.get('--w') && +props.get('--w')[0].trim()) || 30; const h = (props.get('--h') && +props.get('--h')[0].trim()) || 30; const spacing = +props.get('--spacing')[0].trim() || 10; for (let x = 0; x < PaintSize.width / w; x++) { for (let y = 0; y < PaintSize.height / h; y++) { ctx.fillStyle = `#${Math.random().toString(16).slice(2, 8)}` ctx.beginPath(); ctx.rect(x * (w + spacing), y * (h + spacing), w, h); ctx.fill(); } } } } registerPaint('randomcolor', RandomColorPainter); 

接着咱們須要引入該worker:

CSS && CSS.paintWorklet.addModule('worker.js');

最後咱們在一個class爲paint的div應用樣式:

.paint{ background-image: paint(randomcolor); width: 100%; height: 600px; color: #000; --w: 50; --h: 50; --spacing: 10; } 

再想一想用js+div,是否是要先動態生成n個,而後各類計算各類操做dom,想一想就可怕。若是是canvas,這但是canvas背景,你又要在上面放一個div,並且還要定位一波。

注意: worker是沒有window的,因此想搞動畫的就不能內部消化了。不過能夠靠外面的css變量,咱們用js操做css變量能夠解決,也比傳統的方法優雅

4.  自定義屬性

支持狀況 點擊這裏查看 首先,看一下支持度,目前瀏覽器並無徹底穩定使用,因此須要跟着它的提示:Experimental Web Platform features」 on chrome://flags,在chrome地址欄輸入chrome://flags再找到Experimental Web Platform features並開啓。

CSS.registerProperty({
    name: '--myprop', //屬性名字 syntax: '<length>', // 什麼類型的單位,這裏是長度 initialValue: '1px', // 默認值 inherits: true // 會不會繼承,true爲繼承父元素 }); 

說到繼承,咱們回到前面的css變量,已經說了變量是區分做用域的,其實父做用域定義變量,子元素使用該變量其實是繼承的做用。若是inherits: true那就是咱們看見的做用域的效果,若是不是true則不會被父做用域影響,並且取默認值。

這個自定義屬性,精闢在於,能夠用永久循環的animation驅動一次性的transform。換句話說,咱們若是用了css變量+transform,能夠靠js改變這個變量達到花俏的效果。可是,如今不須要js,只要css內部消化,transform成爲永動機。

// 咱們先註冊幾種屬性 ['x1','y1','z1','x2','y2','z2'].forEach(p => { CSS.registerProperty({ name: `--${p}`, syntax: '<angle>', inherits: false, initialValue: '0deg' }); }); 

而後寫個樣式

#myprop, #myprop1 { width: 200px; border: 2px dashed #000; border-bottom: 10px solid #000; animation:myprop 3000ms alternate infinite ease-in-out; transform: rotateX(var(--x2)) rotateY(var(--y2)) rotateZ(var(--z2)) } 

再來看看咱們的動畫,爲了眼花繚亂,加了第二個改了一點數據的動畫

@keyframes myprop { 25% { --x1: 20deg; --y1: 30deg; --z1: 40deg; } 50% { --x1: -20deg; --z1: -40deg; --y1: -30deg; } 75% { --x2: -200deg; --y2: 130deg; --z2: -350deg; } 100% { --x1: -200deg; --y1: 130deg; --z1: -350deg; } } @keyframes myprop1 { 25% { --x1: 20deg; --y1: 30deg; --z1: 40deg; } 50% { --x2: -200deg; --y2: 130deg; --z2: -350deg; } 75% { --x1: -20deg; --z1: -40deg; --y1: -30deg; } 100% { --x1: -200deg; --y1: 130deg; --z1: -350deg; } } 

html就兩個div:

<div id="myprop"></div> <div id="myprop1"></div> 
相關文章
相關標籤/搜索