一個更高效的 CSS-IN-JS 方案

我們先看看我們最終的目標(biāo), 我們可以如何描述代碼:

import React from 'react';

export default () => <button inlist="bg:#f00; @md:display:none; padding:1.2rem; hover:radius:0.5rem radius:0.3rem">
  我是一個按鈕
</button>

在這個文件中,我們 "似乎沒有引入任何庫",就好像寫內(nèi)聯(lián)樣式一樣,把樣式描述、媒體查詢、偽類都實現(xiàn)了。

cssin

cssin 是一個高度可定制的低級 CSS-In-JS 框架,它為您提供構(gòu)建定制設(shè)計所需的所有構(gòu)建模塊,而無需任何令人討厭的樣式,你可以使用內(nèi)聯(lián)樣式的所有語法,和其他擴展語法。

大多數(shù) CSS 框架都做得太多了。
它們帶有各種預(yù)先設(shè)計的組件,如按鈕,卡片和警報,這些組件可能會幫助您最初快速移動,但是當(dāng)您的網(wǎng)站使用自定義設(shè)計脫穎而出時,會導(dǎo)致更多的痛苦。

cssin 與眾不同。

cssin 提供了低級實用程序類,而不是固定的預(yù)先設(shè)計的組件,使您可以構(gòu)建完全自定義的設(shè)計而無需離開 JS。

cssin 生成的每個相同的樣式值可以被重復(fù)引用,而不是重新創(chuàng)建。

理念

我們在使用 cssin 之前做了非常多的嘗試,css\less\scss, tailwindCSS, styled-components 和其他 css-in-js 方案。其中 tailwindCSS 是最符合生產(chǎn)需要的,我們從中學(xué)到許多東西和理念;可是這些樣式方案對于作者來說并沒能真正解決問題:

簡短高效的描述我的樣式,并且不離開 js;當(dāng)然也不放棄 css 的任何一個特性

css-in 就是為了解決此類問題而存在

旨在定制

cssin 所有樣式都是通過定制而得,cssin 允許您自定義它。這包括顏色,邊框大小,字體粗細(xì),間距實用程序,斷點,陰影和任何 css 樣式。

cssin 采用純 Typescript 編寫,并且無需對項目框架進(jìn)行配置,這意味著您可以輕松獲得真正編程語言的全部功能。

cssin 相當(dāng)于在內(nèi)斂樣式上擴展了偽類和媒體查詢,并且支持自定義屬性名和設(shè)定組件。

cssin 不僅僅是一個 CSS-IN-JS 框架,它還是一個創(chuàng)建設(shè)計系統(tǒng)的引擎。

輕巧

  • 僅有 2kb (gzip)
  • 每條樣式會被緩存, 以更高的性能進(jìn)行樣式處理
  • 可以在任何框架中使用,如你喜歡的 React、Vue、Stencil

安裝

$ npm i cssin --save

先看看展現(xiàn)形式

example: cssin.workos.top

在沒有進(jìn)行任何配置之前,cssin 的語法和內(nèi)斂樣式是一致的

import React from 'react';
import cssin from 'cssin';

// 設(shè)置一個全局的 css-value
document.body.style.setProperty('--button-color', '#fff');

export default () => {
  return (
    <div
      className={cssin`background-color:#f66; hover:background-color:#f33; padding:1.2rem; color:#000; border:2px solid #f33; @md:border-radius:2rem;`}>
      Button
    </div>
  );
};

看起來還不錯,有點像內(nèi)聯(lián)樣式,但是又有些許不同,似乎直接描述了偽類和媒體查詢,而且代碼不夠精簡。

好的,我們最后會通過簡單的配置的讓樣式描述變成這樣:

import React from 'react';
import cssin from 'cssin';

export default () => {
  return <div className={cssin`btn:#f33, 1.2rem; hover:bg:#f33; @md:radius:2rem;`}>Button</div>;
};

或者極限簡潔:

import React from 'react';
import cssin from 'cssin';

export default () => {
  return <div className={cssin`button`}>Button</div>;
};

更加極限極限簡潔, 連 cssin 的包裹都省略掉:

import React from 'react';

export default () => {
  return <div inlist="button">Button</div>;
};

我們會一步步來達(dá)到最后的步驟。

或許一段話就可以描述清楚 cssin

我們先回顧剛開始的代碼塊:

export default () => {
  return (
    <div
      className={cssin`background-color:#f66; hover:background-color:#f33; padding:1.2rem; color:--button-color; border:2px solid #f33; @md:border-radius:2rem;`}>
      Button
    </div>
  );
};

上述代碼有點像內(nèi)聯(lián)樣式,但是又有一些不同,因為它可以實現(xiàn)偽類及更好的自定義,我們逐步分析:

  • 和編寫內(nèi)聯(lián)樣式一樣的編寫 css 樣式, 如: background-color: #f66; padding: 1.2rem;
  • 直接使用偽類, 偽類在屬性名之前,使用:分割如: hover:background-color=#f33
  • 可以直接描述媒體查詢等功能, 媒體查詢對象使用@開頭, 如: @md:border-radius=2rem
  • 可以使用 css 變量,color:--button-color 等效于 color:var(--button-color);

其他規(guī)則:

  • 如果只有屬性名,那么它將是一個組件, 如 button;
  • 如果只有屬性名,并且以 . 開頭, 那么就是對原生 css 樣式的引用, 如 .button;
  • 如果包含 {}, 表示這是一個純 css, 它會被插入至全局樣式中, 如 body { margin:0px; }

以上就是 cssin 的所有規(guī)則

下面是完整屬性的表達(dá)式:
@[媒體查詢]:[偽類名]:[屬性名]:[屬性值];

下面這句完整的語法描述:

// 當(dāng)媒體查詢大于 760px 時、鼠標(biāo)移入時、描邊等于 #f00;
cssin`@md:hover:border:1px solid #f00;`;

還有另外兩條規(guī)則,不過我們可以先跳過它,在后文會有更詳細(xì)的描述

為什么不直接編寫 style 內(nèi)聯(lián)樣式?

  1. style 樣式無法完全描述 css 的功能,如媒體查詢、偽類等等 style;
  2. 樣式無法自定義更簡短的樣式集、樣式集的組合、嵌套;
  3. 內(nèi)聯(lián)樣式無法直接引用 className,這樣我們通常需要編寫 css 文件,設(shè)置 className 和 style;
  4. 并且默認(rèn)優(yōu)先級比 css 高,css 和 內(nèi)聯(lián)樣式混合使用需要注意優(yōu)先級;

cssin 最后生成的還是 css 樣式,所以不會有以上的問題

訂制自定義樣式

和眾多 css 框架一樣,cssin 允許你自定義樣式集,這樣可以用更簡短的聲明來描述樣式

cssin 有一個 addSheets 屬性用來添加樣式映射表

我們現(xiàn)在達(dá)成剛剛的約定,將:

background-color:#f66; hover:background-color:#f33; padding:1.2rem; color:--button-color; border:2px solid #f33; @md:border-radius:2rem;

變成:

btn:#f33, 1.2rem; hover:bg:#f33; @md:radius:2rem;

import React from 'react';
import cssin, { addSheets } from 'cssin';

// 添加自定義樣式集
addSheets({
  bg: (v) => `{ background-color: ${v}; }`,
  radius: (v) => `{ border-radius: ${v}; }`,
  btn: (v) => {
    const values = v.split(';');
    return {
      `{ background-color: ${values[0]}; padding:${values[1]}; color:var(--button-color); }`
    }
  },
});

// 使用自定義的樣式
export default () => {
  return <div className={cssin`btn:#f33, 1.2rem; hover:bg:#f33; @md:radius:2rem;`}>Button</div>;
};

由于使用 cssin , 我們不會需要有css代碼,所以可以降低項目首屏的資源請求。

自定義樣式除了可以簡化開發(fā),還可以減少js代碼量,從而最終達(dá)到相對更少的打包資源。

定制媒體查詢

cssin 默認(rèn)配置了 4 個級別的媒體查詢,我們可以覆蓋它或者創(chuàng)建新的規(guī)則

注意,我們約定,只有以 @ 開頭的才是媒體查詢對象

// 默認(rèn)的媒體查詢
addSheets({
  '@sm': (v) => `@media (min-width: 640px) {${v}}`,
  '@md': (v) => `@media (min-width: 768px) {${v}}`,
  '@lg': (v) => `@media (min-width: 1024px) {${v}}`,
  '@xl': (v) => `@media (min-width: 1280px) {${v}}`,
});

// 我們覆蓋 @sm 以及創(chuàng)建一個 @xxl
addSheets({
  '@md': (v) => `@media (min-width: 800px) {${v}}`,
  '@xxl': (v) => `@media (min-width: 1920px) {${v}}`,
});

訂制組件

我們希望把剛剛的代碼簡寫成更精巧的組件, 組件其實是一組樣式集

設(shè)置自定義組件, 因為 sheets 是一個簡單的對象表,請注意不要和其他自定義樣式重名導(dǎo)致覆蓋

它和自定義樣式或媒體查詢的區(qū)別是它的值是一個單純的字符串:

import React from 'react';
import cssin, { addSheets } from 'cssin';

addSheets({
  // 區(qū)別于自定義樣式,組件的值是一個字符串,它遵循 cssin 語法,可以調(diào)用其他組件和自定義樣式
  button: 'bgc:#f66; hover:bgc:#f22; padding:1.2rem; color:--button-color;',
});

// 最終只需要包裹一個單詞的聲明
export default () => {
  return <div className={cssin`button;`}>Button</div>;
};

注意,組件不可以和偽類或者媒體查詢進(jìn)行組合,因為組件內(nèi)部就已經(jīng)包含了偽類或媒體查詢

覆蓋 setAttribute

這里涉及一些魔法,請辯證的使用。

作者在編寫代碼的時候不希望每次都引用 cssin,這對作者來說太過繁瑣了,如果你也有這種感覺,可以使用 coverAttribute

index.js

import React from 'react';
import { coverAttribute } from 'cssin';

// 這里我們覆蓋inlist對象,它會模擬 className={cssin`...`}
coverAttribute('inlist');

// 請確保 coverAttribute 在 ReactDOM.render 之前執(zhí)行
ReactDOM.render(<App />, document.getElementById('root'));

App.js

import React from 'react';

// 最終只需要一個單詞的聲明,就像原生聲明一樣
export const App = () => {
  return (
    <div>
      <div inlist="button">Button</div>
    </div>
  );
};

inlint 可以和 className 一起使用,前提是 className 必須在 inlist 之前聲明

import React from 'react';

// 最終只需要一個單詞的聲明,就像原生聲明一樣
export const App = () => {
  return (
    <div>
      <div className="" inlist="button">Button</div>
    </div>
  );
};

我們需要注意,覆蓋 setAttribute 并不是非常正確的做法,因為它帶來了其他開發(fā)人員的不友好,其他人員并不知道我們做了這些黑魔法的前提下,會非常困惑。

另外,請不要覆蓋 className 等常用屬性,這樣會讓其他組件庫失效

使用 css 原生功能在 javascript 中

使用 {} 編寫單純的 css 片段

有時候,我們會需要編寫單純的 css 片段,我們約定若字符串中包含 {}

此時傳入的字符串只會被當(dāng)成單純的 css 樣式進(jìn)行注入至 html 中

import cssin from 'cssin';

cssin`
  body {
    margin: 0px;
    background-color: #f5f5f5;
  }

  @media (min-width: 640px) {
    .box {
      background: #f00;
    }
  }
`;

使用 . 引用原生的 css

其他地方定義的原生的 css 可以和 cssin 混合使用,只需要在屬性名前面增加 .:

import React from 'react';
import cssin from 'cssin';

// 使用 .box 引用 css 樣式
export default () => {
  return <div className={cssin`margin:2rem .box`}>Button</div>;
};

使用默認(rèn)自定義樣式、組件、 css-values

cssin 提供了一整套自定義樣式集合及 css-value 集合,它精心設(shè)計、開箱即用,亦可以作為一個自定義樣式集合的參照標(biāo)本

默認(rèn)情況下 cssin 并未配置它,如果我們需要可以如下配置:

import 'cssin/commonSheets'; // 引入 sheets集合
import 'cssin/commonValues'; // 引入 css-value 集合

我們可以查看這兩個文件,就是對 cssin API 簡單的運用,也歡迎有朋友提供更好的自定義樣式及組件:

commonSheets.ts

commonValues.ts

性能開銷

cssin 雖然是運行時創(chuàng)建css樣式,但是它有著極低的性能開銷。

我們可以看到,創(chuàng)建重復(fù)執(zhí)行500次,每次大約創(chuàng)建20條樣式,只消耗了 1.6ms, 這是因為cssin會對整體屬性做緩存,還會對子屬性創(chuàng)建css樣式做緩存:

console.time(t);
for(let i = 0; i<500;i++) {
  cssin(`transition:all 0.1s ease-in; box-shadow:--shadow-1lg; hover:box-shadow:--shadow-1md; active:box-shadow:--shadow-sm1;`)
  cssin(`transition:all 0.2s ease-in; box-shadow:--shadow-2lg; hover:box-shadow:--shadow-2md; active:box-shadow:--shadow-sm2;`)
  cssin(`transition:all 0.3s ease-in; box-shadow:--shadow-3lg; hover:box-shadow:--shadow-3md; active:box-shadow:--shadow-sm3;`)
  cssin(`transition:all 0.4s ease-in; box-shadow:--shadow-4lg; hover:box-shadow:--shadow-4md; active:box-shadow:--shadow-sm4;`)
}
console.timeEnd(t); // 1.60009765625ms

現(xiàn)在開始使用它:

$ npm i cssin --save
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概要 64學(xué)時 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,789評論 0 3
  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J(rèn)的外補...
    _Yfling閱讀 14,090評論 1 92
  • 1.行內(nèi)元素和塊級元素?img算什么?行內(nèi)元素怎么轉(zhuǎn)化為塊級元素? 行內(nèi)元素:和有他元素都在一行上,高度、行高及外...
    極樂君閱讀 2,594評論 0 20
  • 前端開發(fā)面試題 面試題目: 根據(jù)你的等級和職位的變化,入門級到專家級,廣度和深度都會有所增加。 題目類型: 理論知...
    怡寶丶閱讀 2,669評論 0 7
  • HTML 5 HTML5概述 因特網(wǎng)上的信息是以網(wǎng)頁的形式展示給用戶的,因此網(wǎng)頁是網(wǎng)絡(luò)信息傳遞的載體。網(wǎng)頁文件是用...
    阿啊阿吖丁閱讀 4,906評論 0 0

友情鏈接更多精彩內(nèi)容