溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

什么是CSS in JS與JS in CSS

發(fā)布時間:2021-10-13 15:01:17 來源:億速云 閱讀:200 作者:iii 欄目:web開發(fā)

本篇內(nèi)容主要講解“什么是CSS in JS與JS in CSS”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“什么是CSS in JS與JS in CSS”吧!

CSS in JS

CSS in JS是一種解決css問題想法的集合,而不是一個指定的庫。從CSS in  JS的字面意思可以看出,它是將css樣式寫在JavaScript文件中,而不需要獨立出.css、.less之類的文件。將css放在js中使我們更方便的使用js的變量、模塊化、tree-shaking。還解決了css中的一些問題,譬如:更方便解決基于狀態(tài)的樣式,更容易追溯依賴關(guān)系,生成唯一的選擇器來鎖定作用域。盡管CSS  in  JS不是一個很新的技術(shù),但國內(nèi)的普及程度并不高。由于Vue和Angular都有屬于他們自己的一套定義樣式的方案,React本身也沒有管用戶怎樣定義組件的樣式,所以CSS  in JS在React社區(qū)的熱度比較高。

目前為止實現(xiàn)CSS in  JS的第三方庫有很多:點擊這里。像JSS、styled-components等。在這里我們就不展開贅述了(相關(guān)鏈接已放在下方),這篇文章的重點是JS in  CSS??。

JS in CSS又是什么

在上面我們提到CSS in JS就是把CSS寫在JavaScript中,那么JS in  CSS我們可以推斷出就是可以在CSS中使用JavaScript腳本,如下所示。可以在CSS中編寫Paint  API的功能。還可以訪問:ctx,geom。甚至我們還可以編寫自己的css自定義屬性等。這些功能的實現(xiàn)都基于CSS Houdini。

.el {   --color: cyan;   --multiplier: 0.24;   --pad: 30;   --slant: 20;   --background-canvas: (ctx, geom) => {     let multiplier = var(--multiplier);     let c = `var(--color)`;     let pad = var(--pad);     let slant = var(--slant);      ctx.moveTo(0, 0);     ctx.lineTo(pad + (geom.width - slant - pad) * multiplier, 0);     ctx.lineTo(pad + (geom.width - slant - pad) * multiplier + slant, geom.height);     ctx.lineTo(0, geom.height);     ctx.fillStyle = c;     ctx.fill();   };   background: paint(background-canvas);   transition: --multiplier .4s; } .el:hover {   --multiplier: 1; }

上述在線demo點擊這里

Houdini 解決了什么問題

CSS 與 JS的標(biāo)準(zhǔn)制定流程對比

在如今的Web開發(fā)中,JavaScript幾乎占據(jù)了項目代碼的大部分。我們可以在項目開發(fā)中使用ES  2020、ES2021、甚至提案中的新特性(如:Decorator),即使瀏覽器尚未支持,也可以編寫Polyfill或使用Babel之類的工具進(jìn)行轉(zhuǎn)譯,讓我們可以將最新的特性應(yīng)用到生產(chǎn)環(huán)境中(如下圖所示)。

什么是CSS in JS與JS in CSS

而CSS就不同了,除了制定CSS標(biāo)準(zhǔn)規(guī)范所需的時間外,各家瀏覽器的版本、實戰(zhàn)進(jìn)度差異更是曠日持久(如下圖所示),最多利用PostCSS、Sass等工具來幫我們轉(zhuǎn)譯出瀏覽器能接受的CSS。開發(fā)者們能操作的就是通過JS去控制DOM與CSSOM來影響頁面的變化,但是對于接下來的Layout、Paint與Composite就幾乎沒有控制權(quán)了。為了解決上述問題,為了讓CSS的魔力不在受到瀏覽器的限制,Houdini就此誕生。

什么是CSS in JS與JS in CSS

CSS Polyfill

我們上文中提到JavaScript中進(jìn)入提案中的特性我們可以編寫Polyfill,只需要很短的時間就可以講新特性投入到生產(chǎn)環(huán)境中。這時,腦海中閃現(xiàn)出的第一個想法就是CSS  Polyfill,只要CSS的Polyfill 足夠強大,CSS或許也能有JavaScript一樣的發(fā)展速度,令人可悲的是編寫CSS  Polyfill異常的困難,并且大多數(shù)情況下無法在不破壞性能的情況下進(jìn)行。這是因為JavaScript是一門動態(tài)腳本語言。它帶來了極強的擴展性,正是因為這樣,我們可以很輕松使用JavaScript做出JavaScript的Polyfill。但是CSS不是動態(tài)的,在某些場景下,我們可以在編譯時將一種形式的CSS的轉(zhuǎn)換成另一種(如PostCSS)。如果你的Polyfill依賴于DOM結(jié)構(gòu)或者某一個元素的布局、定位等,那么我們的Polyfill就無法編譯時執(zhí)行,而需要在瀏覽器中運行了。不幸的是,在瀏覽器中實現(xiàn)這種方案非常不容易。

什么是CSS in JS與JS in CSS

如上圖所示,是從瀏覽器獲取到HTML到渲染在屏幕上的全過程,我們可以看到只有帶顏色(粉色、藍(lán)色)的部分是JavaScript可以控制的環(huán)節(jié)。首先我們根本無法控制瀏覽器解析HTML與CSS并將其轉(zhuǎn)化為DOM與CSSOM的過程,以及Cascade,Layout,Paint,Composite我們也無能為力。整個過程中我們唯一完全可控制的就是DOM,另外CSSOM部分可控。

CSS Houdini草案中提到,這種程度的暴露是不確定的、兼容性不穩(wěn)定的以及缺乏對關(guān)鍵特性的支持的。比如,在瀏覽器中的 CSSOM  是不會告訴我們它是如何處理跨域的樣式表,而且對于瀏覽器無法解析的 CSS 語句它的處理方式就是不解析了,也就是說&mdash;&mdash;如果我們要用 CSS polyfill  讓瀏覽器去支持它尚且不支持的屬性,那就不能在 CSSOM 這個環(huán)節(jié)做,我們只能遍歷一遍DOM,找到<style> 或 <link rel="stylesheet"> 標(biāo)簽,獲取其中的 CSS 樣式、解析、重寫,最后再加回 DOM 樹中。令人尷尬的是,這樣DOM樹全部刷新了,會導(dǎo)致頁面的重新渲染(如下如所示)。

什么是CSS in JS與JS in CSS

即便如此,有的人可能會說:“除了這種方法,我們也別無選擇,更何況對網(wǎng)站的性能也不會造成很大的影響”。那么對于部分網(wǎng)站是這樣的。但如果我們的Polyfill是需要對可交互的頁面呢?例如scroll,resize,mousemove,keyup等等,這些事件隨時會被觸發(fā),那么意味著隨時都會導(dǎo)致頁面的重新渲染,交互不會像原本那樣絲滑,甚至導(dǎo)致頁面崩潰,對用戶的體驗也極其不好。

綜上所述,如果我們想讓瀏覽器解析它不認(rèn)識的樣式(低版本瀏覽器使用grid布局),然而渲染流程我們無法介入,我們也只能通過手動更新DOM的方式,這樣會帶來很多問題,Houdini的出現(xiàn)正是致力于解決他們。

Houdini API

Houdini是一組底層API,它公開了CSS引擎的各個部分,如下圖所示展示了每個環(huán)節(jié)對應(yīng)的新API(灰色部分各大瀏覽器還未實現(xiàn)),從而使開發(fā)人員能夠通過加入瀏覽器渲染引擎的樣式和布局過程來擴展CSS。Houdini是一群來自Mozilla,Apple,Opera,Microsoft,HP,Intel和Google的工程師組成的工作小組設(shè)計而成的。它們使開發(fā)者可以直接訪問CSS對象模型(CSSOM),使開發(fā)人員可以編寫瀏覽器可以解析為CSS的代碼,從而創(chuàng)建新的CSS功能,而無需等待它們在瀏覽器中本地實現(xiàn)。

什么是CSS in JS與JS in CSS

Properties & Values API

盡管當(dāng)前已經(jīng)有了CSS變量,可以讓開發(fā)者控制屬性值,但是無法約束類型或者更嚴(yán)格的定義,CSS  Houdini新的API,我們可以擴展css的變量,我們可以定義CSS變量的類型,初始值,繼承。它是css變量更強大靈活。

CSS變量現(xiàn)狀:

.dom {   --my-color: green;   --my-color: url('not-a-color'); // 它并不知道當(dāng)前的變量類型   color: var(--my-color); }

Houdini提供了兩種自定義屬性的注冊方式,分別是在js和css中。

CSS.registerProperty({   name: '--my-prop', // String 自定義屬性名   syntax: '<color>', // String 如何去解析當(dāng)前的屬性,即屬性類型,默認(rèn) *   inherits: false, // Boolean 如果是true,子節(jié)點將會繼承   initialValue: '#c0ffee', // String 屬性點初始值 });

我們還可以在css中注冊,也可以達(dá)到上面的效果

@property --my-prop {   syntax: '<color>';   inherits: false;   initial-value: #c0ffee; }

這個API中最令人振奮人心的功能是自定義屬性上添加動畫,像這樣:transition: --multiplier  0.4s;,這個功能我們在前面介紹什么是js in  css那個demo用使用過。我們還可以使用+使syntax屬性支持一個或多個類型,也可以使用|來分割。更多syntax屬性值:

屬性值描述
<length>長度值
<number>數(shù)字
<percentage>百分比
<length-percentage>長度或百分比,calc將長度和百分比組成的表達(dá)式
<color>顏色
<image>圖像
<url>網(wǎng)址
<integer>整數(shù)
<angle>角度
<time>時間
<resolution>分辨率
<transform-list>轉(zhuǎn)換函數(shù)
<custom-ident>ident

Worklets

Worklets是渲染引擎的擴展,從概念上來講它類似于Web Workers,但有幾個重要的區(qū)別:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 設(shè)計為并行,每個Worklets必須始終有兩個或更多的實例,它們中的任何一個都可以在被調(diào)用時運行

  3. 作用域較小,限制不能訪問全局作用域的API(Worklet的函數(shù)除外)

  4. 渲染引擎會在需要的時候調(diào)用他們,而不是我們手動調(diào)用

Worklet是一個JavaScript模塊,通過調(diào)用worklet的addModule方法(它是個Promise)來添加。比如registerLayout,  registerPaint, registerAnimator 我們都需要放在Worklet中

//加載單個 await demoWorklet.addModule('path/to/script.js');  // 一次性加載多個worklet Promise.all([   demoWorklet1.addModule('script1.js'),   demoWorklet2.addModule('script2.js'), ]).then(results => {});  registerDemoWorklet('name', class {    // 每個Worklet可以定義要使用的不同函數(shù)   // 他們將由渲染引擎在需要時調(diào)用   process(arg) {     return !arg;   } });

Worklets的生命周期

什么是CSS in JS與JS in CSS

Worklet的生命周期從渲染引擎內(nèi)開始

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 對于JavaScript,渲染引擎啟動JavaScript主線程

  3. 然后他將啟動多個worklet進(jìn)程,并且可以運行。這些進(jìn)程理想情況下是獨立于主線程的線程,這樣就不會阻塞主線程(但它們也不需要阻塞)

  4. 然后在主線程中加載我們?yōu)g覽器的JavaScript

  5. 該JavaScript調(diào)用 worklet.addModule 并異步加載一個worklet

  6. 加載后,將worklet加載到兩個或多個可用的worklet流程中

  7. 當(dāng)需要時,渲染引擎將通過從加載的Worklet中調(diào)用適當(dāng)?shù)奶幚砗瘮?shù)來執(zhí)行Worklet。該調(diào)用可以針對任何并行的Worklet實例。

Typed OM

Typed OM是對現(xiàn)有的CSSOM的擴展,并實現(xiàn) Parsing API 和 Properties & Values  API相關(guān)的特性。它將css值轉(zhuǎn)化為有意義類型的JavaScript的對象,而不是像現(xiàn)在的字符串。如果我們嘗試將字符串類型的值轉(zhuǎn)化為有意義的類型并返回可能會有很大的性能開銷,因此這個API可以讓我們更高效的使用CSS的值。

現(xiàn)在讀取CSS值增加了新的基類CSSStyleValue,他有許多的子類可以更加精準(zhǔn)的描述css值的類型:

子類描述
CSSKeywordValueCSS關(guān)鍵字和其他標(biāo)識符(如inherit或grid)
CSSPositionValue位置信息 (x,y)
CSSImageValue表示圖像的值屬性的對象
CSSUnitValue表示為具有單個單位的單個值(例如50px),也可以表示為沒有單位的單個值或百分比
CSSMathValue比較復(fù)雜的數(shù)值,比如有calc,min和max。這包括子類 CSSMathSum, CSSMathProduct, CSSMathMin, CSSMathMax, CSSMathNegateCSSMathInvert
CSSTransformValueCSS transforms組成的CSSTransformComponent列表,其中包括CSSTranslate, CSSRotate, CSSScale, CSSSkew, CSSSkewX, CSSSkewY, CSSPerspectiveCSSMatrixComponent

使用Typed OM主要有兩種方法:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 通過attributeStyleMap設(shè)置和獲取有類型的行間樣式

  3. 通過computedStyleMap獲取元素完整的Typed OM樣式

使用attributeStyleMap設(shè)置并獲取

myElement.attributeStyleMap.set('font-size', CSS.em(2)); myElement.attributeStyleMap.get('font-size'); // CSSUnitValue { value: 2, unit: 'em' }  myElement.attributeStyleMap.set('opacity', CSS.number(.5)); myElement.attributeStyleMap.get('opacity'); // CSSUnitValue { value: 0.5, unit: 'number' };

使用computedStyleMap

.foo {   transform: translateX(1em) rotate(50deg) skewX(10deg);   vertical-align: baseline;   width: calc(100% - 3em); } 復(fù)制代碼 const cs = document.querySelector('.foo').computedStyleMap();  cs.get('vertical-align'); // CSSKeywordValue { //  value: 'baseline', // }  cs.get('width'); // CSSMathSum { //   operator: 'sum', //   length: 2, //   values: CSSNumericArray { //     0: CSSUnitValue { value: -90, unit: 'px' }, //     1: CSSUnitValue { value: 100, unit: 'percent' }, //   }, // }  cs.get('transform'); // CSSTransformValue { //   is2d: true, //   length: 3, //   0: CSSTranslate { //     is2d: true, //     x: CSSUnitValue { value: 20, unit: 'px' }, //     y: CSSUnitValue { value: 0, unit: 'px' }, //     z: CSSUnitValue { value: 0, unit: 'px' }, //   }, //   1: CSSRotate {...}, //   2: CSSSkewX {...}, // }

Layout API

開發(fā)者可以通過這個API實現(xiàn)自己的布局算法,我們可以像原生css一樣使用我們自定義的布局(像display:flex, display:table)。在  Masonry layout library 上我們可以看到開發(fā)者們是有多想實現(xiàn)各種各樣的復(fù)雜布局,其中一些布局光靠 CSS  是不行的。雖然這些布局會讓人耳目一新印象深刻,但是它們的頁面性能往往都很差,在一些低端設(shè)備上性能問題猶為明顯。

CSS Layout API 暴露了一個registerLayout方法給開發(fā)者,接收一個布局名(layout name)作為后面在  CSS中使用的屬性值,還有一個包含有這個布局邏輯的JavaScript類。

my-div {   display: layout(my-layout); } 復(fù)制代碼 // 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) {} }); 復(fù)制代碼 await CSS.layoutWorklet.addModule('layout-worklet.js');

Painting API

我們可以在CSS background-image中使用它,我們可以使用Canvas 2d上下文,根據(jù)元素的大小控制圖像,還可以使用自定義屬性。

await CSS.paintWorklet.addModule('paint-worklet.js'); 復(fù)制代碼 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 API

這個API讓我們可以控制基于用戶輸入的關(guān)鍵幀動畫,并且以非阻塞的方式。還能更改一個 DOM  元素的屬性,不過是不會引起渲染引擎重新計算布局或者樣式的屬性,比如 transform、opacity 或者滾動條位置(scroll  offset)。Animation API的使用方式與 Paint API 和 Layout  API略有不同我們還需要通過new一個WorkletAnimation來注冊worklet。

// animation-worklet.js registerAnimator('sample-animator', class {   constructor(options) {   }   animate(currentTime, effect) {     effect.localTime = currentTime;   } }); 復(fù)制代碼 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,   // 動畫需要綁定的關(guān)鍵幀   [     {transform: 'scale(1)'},     {transform: 'scale(.25)'},     {transform: 'scale(1)'}   ],   {     duration: timeRange,   }, ); new WorkletAnimation(   'sample-animator',   effectKeyframes,   scrollTimeline,   {}, ).play();

Parser API

允許開發(fā)者自由擴展 CSS 詞法分析器。

解析規(guī)則:

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 API

它將提供一些方法來測量在屏幕上呈現(xiàn)的文本元素的尺寸,將允許開發(fā)者控制文本元素在屏幕上呈現(xiàn)的方式。使用當(dāng)前功能很難或無法測量這些值,因此該API將使開發(fā)者可以更輕松地創(chuàng)建與文本和字體相關(guān)的CSS特性。例如:

  • flex布局: align-items baseline特性。需要知道每一個flex盒子中第一個元素的基線位置。

  • 首字母: 需要知道每個字母的基線高度和字母最大的高度,以及換行內(nèi)容的基線長度。

  • 單個字形的前進(jìn)和后退。

  • 換行: 需要訪問字體數(shù)據(jù),文本的所有樣式輸入以及布局信息(可用的段落長度等)。

  • 元素中的每一個line boxes都需要一個基線。(line boxes代表包含眾多inline boxes的這行)

Houdini 目前進(jìn)展

什么是CSS in JS與JS in CSS

Houdini 的藍(lán)圖

了解到這里,部分開發(fā)者可能會說:“我不需要這些花里胡哨的技術(shù),并不能帶收益。我只想簡簡單單的寫幾個頁面,做做普通的Web  App,并不想試圖干預(yù)瀏覽器的渲染過程從而實現(xiàn)一些實驗性或炫酷的功能。”如果這樣想的話,我們不妨退一步再去思考?;貞浵伦罱鲞^的項目,用于實現(xiàn)頁面效果所使用到的技術(shù),grid布局方式在考慮兼容老版本瀏覽器時也不得不放棄。我們想控制瀏覽器渲染頁面的過程并不是僅僅為了炫技,更多的是為了幫助開發(fā)者們解決以下兩個問題:

  1. 統(tǒng)一各大瀏覽器的行為

  2. 像JavaScript一樣,在推出新的特性時,我們可以通過Polyfill的形式快速的投入生產(chǎn)環(huán)境中。

幾年過后再回眸,當(dāng)主流瀏覽器完全支持Houdini的時候。我們可以在瀏覽器上隨心所欲的使用任何CSS屬性,并且他們都能完美支持。像今天的grid布局在舊版本瀏覽器支持的并不友好的這類問題,那時我們只需要安裝對應(yīng)的Polyfill就能解決類似的問題。

到此,相信大家對“什么是CSS in JS與JS in CSS”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI