我們先看看我們最終的目標(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)樣式?
- style 樣式無法完全描述 css 的功能,如媒體查詢、偽類等等 style;
- 樣式無法自定義更簡短的樣式集、樣式集的組合、嵌套;
- 內(nèi)聯(lián)樣式無法直接引用 className,這樣我們通常需要編寫 css 文件,設(shè)置 className 和 style;
- 并且默認(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 簡單的運用,也歡迎有朋友提供更好的自定義樣式及組件:
性能開銷
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