[譯] Houdini:也許是你未曾聽過的最振奮人心的 CSS 進化

Jrain發表於2018-12-13

寫於 2016.07.05

原文連結:Houdini: Maybe The Most Exciting Development In CSS You’ve Never Heard Of 更多譯文將陸續推出,歡迎點贊+收藏+關注我的專欄,未完待續……

你是否曾經想要使用一些特別的CSS特性,卻因為未曾得到所有瀏覽器的支援而選擇放棄?又或者是,這些特性得到了所有瀏覽器的支援,但總會伴隨著奇怪的bug,表現不一致甚至相互矛盾?如果這些事情都曾發生在你身上——我敢打賭——你應該關注一下Houdini

Houdini是W3C的一項新的任務,其宗旨在於解決上面所說的問題。它計劃通過提供一系列的API,使開發者得以自定義擴充套件CSS,並把這些樣式直接放入瀏覽器的渲染引擎中渲染出來。

但這事實上到底意味著什麼?這真的是一個好主意嗎?以及它會如何幫助我們開發現代的和麵向未來的頁面呢?

在這篇文章中,我將嘗試回答上述問題。在此之前,明白當今存在什麼問題,以及需要做出什麼改變,是非常重要的。待會我將更加詳細地介紹Houdini是如何解決問題的,並將列出一些在目前開發過程中遇到的酷炫的特性。文章的最後,我會提供一些實實在在的能讓我們這些web開發者為Houdini成為現實的做法。

Houdini想要嘗試解決的是什麼問題?

每當我撰寫文章或者製作DEMO,用於展示一些新的CSS特性的時候,不可避免的總有人評論或者在Twitter留言說:“真是酷炫狂霸叼炸天!但糟糕的是在未來十年內我們都無法使用它們。”

就像上面那些負能量滿滿又毫無建設性的評論那樣,我深以為然。在歷史上,新特性的草案往往經過多年才被普遍接受。正因為如此,並且在縱觀web的發展史後,讓新特性草案真正成為CSS標準的唯一辦法就是讓其走一遍標準流程。

標準流程
標準流程中的每一步

面對這所謂的標準流程我無力反抗,但必須承認這浪費了大量的時間!

比方說,flexbox第一次被提議是在2009年,但開發者們仍然在抱怨直到現在都無法使用它,因為只有少數瀏覽器支援這一特性。好在隨著大部分瀏覽器都支援自動更新,這個問題正在慢慢得以改善;然而,即使擁有著現代瀏覽器,從草案到成為可用標準之間,依然存在著遲延。

有趣的是,這並非是web開發中所有領域都出現的問題。讓我們看看Javascript是怎麼做的:

js方案
Js中寫polyfill的步驟

在這種情況下,一個方案從構思到在生產環境使用,往往只不過幾天時間。我的意思是,在生產環境中我已經在使用async/await方法了,即使這個方法未被任何一個瀏覽器所支援!

你也可以感受到這兩個社群的巨大情緒差異。在Javascript社群中,你會看到一些抱怨JS發展太快的文章。與之相反的,在CSS社群你會聽到一些感嘆,在能夠真正使用之前,學習任何新的特性都是徒勞的。

那麼,為什麼我們不自己去寫一些CSS的polyfill呢?

乍這麼一想,寫CSS的polyfill似乎是現成的答案。伴隨著優秀的polyfill,CSS將會發展得跟Javascript一樣快,對嗎?

很可惜,這並沒有那麼簡單。為CSS進行polyfill非常困難,更多的時候往往會毀掉所有的效能。

Javascript是一門動態語言,這意味著你可以用JS去polyfill它自身。也正因為它動態的特性,它是非常易於擴充套件的。從另一個角度來說,CSS幾乎不能被自己所polyfill。在某些情況下,你可以通過構建的方法實現CSS的polyfill(POSTCSS就是幹這個的);然而當你想要polyfill任何依賴於DOM結構的,或者某一元素樣式或位置的東西的時候,你不得不在客戶端執行你的polyfill邏輯。

不幸的是,瀏覽器很難實現這個需求。

下面的圖片展示的是瀏覽器對於一個HTML文件從接收到渲染的基本過程。其中藍色區域就是Javascript有能力作出控制的步驟:

xx
Javascript在瀏覽器渲染程式中的控制權

這幅圖挺讓人沮喪的。作為一個開發者,你無法控制瀏覽器是如何將HTML和CSS解析為DOM 和 CSS 物件模型(CSSOM)的;無法控制整個渲染過程;無法控制瀏覽器是如何選擇把元素渲染到DOM上面的,或者如何把內容填充到螢幕上展現給使用者;你也無法控制瀏覽器是如何排版的。

你唯一能夠完全控制的只有DOM這一塊。在這種時候CSSOM是可用的;即便如此,引用Houdini網站的一句話,這是“蹩腳的,瀏覽器之間不一致的,缺乏論證的特性。”

舉個例子,在如今瀏覽器中的CSSOM,不會告訴你跨域樣式表的規則,並且很容易就會拋棄掉它看不懂的CSS語法或者宣告——這意味著當你想要polyfill一些瀏覽器不支援的特性的時候,你無法使用CSSOM。取而代之的,你只能手動遍歷DOM,找到<style>或者(和)<link>標籤,解析它,重寫它,再把它重新新增回DOM。

當然,更新DOM意味著瀏覽器將會完整地重新進行一遍佈局、繪製、排版的重繪過程。

x
使用Javascript在瀏覽器渲染階段進行polyfill

當完整地渲染頁面的時候也許並不會造成如此大的效能衝擊(尤其對於一些網站來說),不怎麼需考慮發生的可能性。如果你的polyfill邏輯需要在事件響應中進行,比如滾動事件,視窗大小改變事件,滑鼠移動事件,鍵盤事件——每時每刻都在發生這些事情的時候——效能方面的影響就會非常大,有時候甚至會卡頓,崩潰。

更糟糕的是,你會發現如今絕大多數的CSS polyfill都包含了它們自己的CSS解析器以及執行邏輯。與此同時,由於解析和執行都是非常複雜的事情,所以這些polyfill要麼太大,要麼太容易有bug。

綜上所述,如果你希望瀏覽器去做一些它並不懂如何去做的工作(比如使用你的自定義CSS),那麼你必須偽裝一些指令給瀏覽器,可以通過手動更新和修改DOM來實現。除此之外你沒有任何辦法影響渲染過程中的其他階段。

既然如此,為什麼我沒有想到修改瀏覽器內部的渲染引擎呢?

對我來說,這個問題是這篇文章的關鍵。如果之前的內容你只是粗略瀏覽的話,那麼請認真仔細地閱讀接下來的這一段文字。

經過上一小節,我敢肯定有部分讀者已經在想:“我不需要這個!我只是在製作普通的頁面而已。我並沒有準備把瀏覽器給黑了或者弄一些什麼創意啊,實驗啊,尖端產品之類的玩意兒。”

如果你這麼想,我強烈建議你回顧一下這些年來你在開發時所用技術的發展史。希望能夠進入並修改瀏覽器樣式渲染過程的想法並非為了酷炫的demo——而是讓開發者或者框架更大的權利去做兩件主要的事情:

  • 消除定義的樣式在瀏覽器之間的差異
  • 發明或者polyfill新的屬性,從而讓人們能夠在今天就使用它們

如果你曾經使用過諸如jQuery的Javascript庫,你已經從中獲益了!事實上,這正是如今幾乎所有前端庫和框架的賣點。Github上的五個最受歡迎的Javascript和DOM操作框架——AngularJS,D3,JQuery,React和Ember——它們全都為瀏覽器相容做了許多的工作,從而讓你根部不需要考慮瀏覽器相容性。它們都通過向外提供的API就能直接被使用。

現在,讓我們回到CSS以及它的跨瀏覽器相容問題。即使是流行如Bootstrap和Foundation這兩個聲稱主打相容性的CSS框架也無法避免跨瀏覽器帶來的bug——它們只是避開了bug而不是解決它。同時,CSS跨瀏覽器帶來的bug不僅僅是過去的事情。即時在今天,面對如flexbox之類的新特性,我們仍然面臨著跨瀏覽器帶來的不一致性問題。

最後,想像一下如果你確信你能使用任何的CSS屬性,並且在不同瀏覽器中它們都能正常執行,你的開發體驗將會多麼舒適。再設想一下,任何你在部落格、大會上聽到的新特性——比如CSS grids,CSS snap points和sticky positioning,都能夠通過某種方式像原生CSS一樣得到完美執行,所有的一切只需要你從Github上覆制一段程式碼,那將會多麼美好。

這正是Houdini的目標,這正是其組織所為之奮鬥的未來。

因此,即使你並不打算寫CSS的polyfill或者開發一個實驗性質的新特性,你也很可能會希望別人去做這些——因為一旦這些polyfills被實現了,所有人都能從中獲益。

在現在的開發中Houdini到底是什麼?

我在上文提到過,開發者僅有一點點權利去操作瀏覽器的渲染過程。確實,只有DOM和CSSOM能夠被開發者操作。

為了解決這個問題,Houdini小組提供了一些新的方法,第一次讓開發者得以進入到渲染過程中的其他步驟。下面的圖片展示的是渲染過程以及新的方法是如何被使用而修改當中的步驟的。(注意灰色部分的方法是計劃中的但仍在修改的。)

xx
Houdini的新方法影響瀏覽器渲染過程的位置

接下來的幾個小節將會簡要介紹每一個新方法及其所擁有的功能。我要說明的是,仍然有一些新方法並未收錄到這篇文章當中,完整的方法列表請查閱GitHub repository of Houdini’s drafts

CSS屬性-值API

CSS已經可以自定義屬性了,正如我之前說的那樣,對於這個可能性的實現我表示非常興奮。CSS屬性-值API更是讓自定義屬性向前邁了一步,使其在新增屬性操作的時候更加實用。

有許多很棒的事情能夠在自定義屬性中實現,但最大的賣點也許是能夠讓開發者們在transition和animate當中,使用現在並不支援的自定義屬性。

想像一下下面的例子:

body {
  --primary-theme-color: tomato;
  transition: --primary-theme-color 1s ease-in-out;
}
body.night-theme {
  --primary-theme-color: darkred;
}
複製程式碼

在上面的程式碼中,如果night-theme被新增到<body>當中,那麼頁面中的所有元素都將能引用--primary-theme-color屬性的值,並且會慢慢地從tomato過渡到darkred。如果你想現在就這麼做,你不得不手動地為每一個元素新增這些過渡程式碼,因為你無法讓他們自動完成這些工作。

這個API另外一個已經確定的特性是將允許註冊一個“呼叫鉤子”,一個允許開發者在渲染程式結束以後修改自定義屬性最終值的方法,這個方法在polyfill的時候會非常有用。

CSS TYPED OM

CSS TYPED OM能被視為當前CSSOM的2.0版本。其目標是為了解決大量的關於現階段的規範,以及包括通過新的CSS解析API和CSS屬性-值API所帶來的問題。

另外一個目標是為了提升效能。把當前的CSSOM轉化成有意義的JS表示式,能夠帶來大幅度的效能提升。

CSS LAYOUT API

CSS LAYOUT API允許開發者自定義佈局模組。“佈局模組”指的是任何帶有CSSdisplay屬性的元素。這將會是第一次為開發者帶來效能媲美原生布局的方式,比如display: flexdisplay: table

作為實際使用的例子,Masonry layout library展示了開發者是多麼想要實現複雜的令人驚歎的佈局,卻無法僅僅通過CSS來實現的情景。

不幸的是,他們被效能問題所困擾,尤其是在一些能力稍差的裝置上。

CSS LAYOUT API提供一個registerLayout方法給開發人員,該方法接收一個佈局的名字作為引數(一個接下來將會用在CSS當中的名字),以及一個包含著所有佈局邏輯的Javascript類。這裡展示的是一個通過registerLayoutAPI註冊masonry佈局的基本例子。

registerLayout('masonry', class {
  static get inputProperties() {
    return ['width', 'height']
  }
  static get childrenInputProperties() {
    return ['x', 'y', 'position']
  }
  layout(children, constraintSpace, styleMap, breakToken) {
    // Layout logic goes here.
  }
}
複製程式碼

如果上面的程式碼你看不懂,沒關係。最重要的東西是接下來的例子,每當你把masonry.js檔案引用到你的頁面中,你可以這麼寫CSS並且一切都將正常執行:

body {
  display: layout('masonry');
}
複製程式碼

CSS PAINT API

這個API非常像上文所說的LAYOUT API。它提供一個registerLayout方法。開發者能夠在 CSS的任何地方使用paint()方法,接下來一張圖片會被生成並放置在所註冊的名稱上面。 這裡是一個簡單的描繪了帶有顏色的圓的例子:

registerPaint('circle', class {
  static get inputProperties() { return ['--circle-color']; }
  paint(ctx, geom, properties) {
    // Change the fill color.
    const color = properties.get('--circle-color');
    ctx.fillStyle = color;
    // Determine the center point and radius.
    const x = geom.width / 2;
    const y = geom.height / 2;
    const radius = Math.min(x, y);
    // Draw the circle \o/
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
    ctx.fill();
  }
});
複製程式碼

在CSS中可以這麼使用:

.bubble {
  --circle-color: blue;
  background-image: paint('circle');
}
複製程式碼

現在.bubble元素將會以一個藍色的圓為背景被展示出來。不管發生什麼,這個圓都會等大居中地放置在元素當中。

WORKLETS

前文的大部分的API都通過程式碼塊來展示(比如registerLayoutregisterPaint)。如果你想知道這些程式碼應該放在哪裡執行,答案是放在WORKLET指令碼中。

Worklets就像web workers,它們允許你引用指令碼檔案,並且在渲染過程的任意時刻執行當中的Javascript程式碼,同時它也是獨立於主執行緒的。

Worklet指令碼嚴格制約著你能夠進行的操作,這正是保證高效能的關鍵。

複合的Scrolling和Animation

即使到目前為止仍然沒有關於composited scrolling and animation的官方說明,但這確實是Houdini眾多特性中最廣為人知並非常有希望實現的特性之一。最後的這個API允許開發者們把邏輯執行在脫離主執行緒的負責排版的worklet當中,同時也支援修改一個DOM元素子集的屬性值。這個子集僅僅包含了能夠被讀寫的屬性,卻不會強迫渲染引擎去重新計算佈局或樣式(舉個例子,transform,opacity,scroll offset等。)

這將會允許開發者們去建立高效能的基於scroll-和input-的動畫,比如sticky scroll headers and parallax effects。你可以在Github找到更多的關於這個API試圖去解決的問題的例子

即使仍舊缺乏官方文件,但是有經驗的開發者們已經在Chrome瀏覽器中開始嘗試。事實上,Chrome團隊在最近已經開始基於這些最終將會發布的API來實現CSS snap pointssticky positioning。實在是振奮人心,因為這意味著Houdini API有著足夠的效能,以至於讓新的Chrome特效能夠基於它們來實現。如果你還在擔心Houdini不如原生那麼有效率,以上事實也許可以在某種程度上打消你的疑慮。

看看真實的例子吧。Surma釋出了一個視訊,展示了一個執行在基於Chorme的瀏覽器內部的demo。Demo模仿了原生Twitter APP的使用者頭像變化行為動畫。想知道它是怎麼實現的嗎?請看原始碼

現在,你可以做些什麼?

根據上面提到的,我覺得任何一個web開發者都應該關心Houdini;它將會讓我們的開發生涯更加方便。即使你從未直接使用過Houdini,但你很有可能已經使用過基於它而開發出來的東西。

儘管這個未來並不會馬上到來,但很可能比我們想象的都更加接近了。所有主流瀏覽器供應商的代表們在今年年初聚集在最後一次的Houdini面對面交流會上,會上對於如何去建立並發展Houdini的問題,已經基本達成了共識。

我能說的,不是Houdini是否能夠最終實現的問題,而是大家能夠在何時,在何地能夠參與進來的問題。

瀏覽器供應工商,就像任何一個軟體開發者一樣,必須重點發展新的特性。並且優先發展的往往是經常使用者強調需要的新特性。

所以,如果你關心web開發中樣式和佈局的擴充套件,如果你想要直接使用新的CSS特性而無需經過漫長的等待,請告訴你所使用的瀏覽器的開發成員。

另一個你能夠出力的地方,就是開發一些真實可用的案例——比如去實現一些在如今難以被實現的樣式或佈局。在Github上有幾個實際案例,你可以提交併釋出pull request去貢獻你的想法。如果文件不存在,你可以新建一個。

Houdini小組的成員(一般來說叫W3C)非常期待來自開發者們的想法和建議。任何參與標準制定的都是瀏覽器開發工程師。他們通常不是專業的web開發者,這意味著他們並非時刻清楚痛點在哪兒。

他們需要我們去告訴他們。

參考資料

特別鳴謝Houdini的成員Ian Kilpatrick和Shane Stephens稽核了這篇文章。


文章內容較多,未進行高質量的稽核校對,僅以原文為準,如有錯漏歡迎指出。 不定期釋出開發體驗,學習心得,牆外乾貨,歡迎關注我的專欄。 感謝你的閱讀,我是Jrain,下次見!

相關文章