我們先看看我們最終的目標(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">
我是一個(gè)按鈕
</button>
在這個(gè)文件中,我們 "似乎沒(méi)有引入任何庫(kù)",就好像寫內(nèi)聯(lián)樣式一樣,把樣式描述、媒體查詢、偽類都實(shí)現(xiàn)了。
cssin
cssin 是一個(gè)高度可定制的低級(jí) CSS-In-JS 框架,它為您提供構(gòu)建定制設(shè)計(jì)所需的所有構(gòu)建模塊,而無(wú)需任何令人討厭的樣式,你可以使用內(nèi)聯(lián)樣式的所有語(yǔ)法,和其他擴(kuò)展語(yǔ)法。
大多數(shù) CSS 框架都做得太多了。
它們帶有各種預(yù)先設(shè)計(jì)的組件,如按鈕,卡片和警報(bào),這些組件可能會(huì)幫助您最初快速移動(dòng),但是當(dāng)您的網(wǎng)站使用自定義設(shè)計(jì)脫穎而出時(shí),會(huì)導(dǎo)致更多的痛苦。
cssin 與眾不同。
cssin 提供了低級(jí)實(shí)用程序類,而不是固定的預(yù)先設(shè)計(jì)的組件,使您可以構(gòu)建完全自定義的設(shè)計(jì)而無(wú)需離開(kāi) JS。
cssin 生成的每個(gè)相同的樣式值可以被重復(fù)引用,而不是重新創(chuàng)建。
理念
我們?cè)谑褂?cssin 之前做了非常多的嘗試,css\less\scss, tailwindCSS, styled-components 和其他 css-in-js 方案。其中 tailwindCSS 是最符合生產(chǎn)需要的,我們從中學(xué)到許多東西和理念;可是這些樣式方案對(duì)于作者來(lái)說(shuō)并沒(méi)能真正解決問(wèn)題:
簡(jiǎn)短高效的描述我的樣式,并且不離開(kāi) js;當(dāng)然也不放棄 css 的任何一個(gè)特性
css-in 就是為了解決此類問(wèn)題而存在
旨在定制
cssin 所有樣式都是通過(guò)定制而得,cssin 允許您自定義它。這包括顏色,邊框大小,字體粗細(xì),間距實(shí)用程序,斷點(diǎn),陰影和任何 css 樣式。
cssin 采用純 Typescript 編寫,并且無(wú)需對(duì)項(xiàng)目框架進(jìn)行配置,這意味著您可以輕松獲得真正編程語(yǔ)言的全部功能。
cssin 相當(dāng)于在內(nèi)斂樣式上擴(kuò)展了偽類和媒體查詢,并且支持自定義屬性名和設(shè)定組件。
cssin 不僅僅是一個(gè) CSS-IN-JS 框架,它還是一個(gè)創(chuàng)建設(shè)計(jì)系統(tǒng)的引擎。
輕巧
- 僅有 2kb (gzip)
- 每條樣式會(huì)被緩存, 以更高的性能進(jìn)行樣式處理
- 可以在任何框架中使用,如你喜歡的 React、Vue、Stencil
安裝
$ npm i cssin --save
先看看展現(xiàn)形式
example: cssin.workos.top
在沒(méi)有進(jìn)行任何配置之前,cssin 的語(yǔ)法和內(nèi)斂樣式是一致的
import React from 'react';
import cssin from 'cssin';
// 設(shè)置一個(gè)全局的 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>
);
};
看起來(lái)還不錯(cuò),有點(diǎn)像內(nèi)聯(lián)樣式,但是又有些許不同,似乎直接描述了偽類和媒體查詢,而且代碼不夠精簡(jiǎn)。
好的,我們最后會(huì)通過(guò)簡(jiǎ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>;
};
或者極限簡(jiǎn)潔:
import React from 'react';
import cssin from 'cssin';
export default () => {
return <div className={cssin`button`}>Button</div>;
};
更加極限極限簡(jiǎn)潔, 連 cssin 的包裹都省略掉:
import React from 'react';
export default () => {
return <div inlist="button">Button</div>;
};
我們會(huì)一步步來(lái)達(dá)到最后的步驟。
或許一段話就可以描述清楚 cssin
我們先回顧剛開(kāi)始的代碼塊:
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>
);
};
上述代碼有點(diǎn)像內(nèi)聯(lián)樣式,但是又有一些不同,因?yàn)樗梢詫?shí)現(xiàn)偽類及更好的自定義,我們逐步分析:
- 和編寫內(nèi)聯(lián)樣式一樣的編寫 css 樣式, 如:
background-color: #f66; padding: 1.2rem; - 直接使用偽類, 偽類在屬性名之前,使用
:分割如:hover:background-color=#f33 - 可以直接描述媒體查詢等功能, 媒體查詢對(duì)象使用
@開(kāi)頭, 如:@md:border-radius=2rem - 可以使用 css 變量,
color:--button-color等效于color:var(--button-color);
其他規(guī)則:
- 如果只有屬性名,那么它將是一個(gè)組件, 如
button; - 如果只有屬性名,并且以
.開(kāi)頭, 那么就是對(duì)原生 css 樣式的引用, 如.button; - 如果包含
{}, 表示這是一個(gè)純 css, 它會(huì)被插入至全局樣式中, 如body { margin:0px; }
以上就是 cssin 的所有規(guī)則
下面是完整屬性的表達(dá)式:
@[媒體查詢]:[偽類名]:[屬性名]:[屬性值];
下面這句完整的語(yǔ)法描述:
// 當(dāng)媒體查詢大于 760px 時(shí)、鼠標(biāo)移入時(shí)、描邊等于 #f00;
cssin`@md:hover:border:1px solid #f00;`;
還有另外兩條規(guī)則,不過(guò)我們可以先跳過(guò)它,在后文會(huì)有更詳細(xì)的描述
為什么不直接編寫 style 內(nèi)聯(lián)樣式?
- style 樣式無(wú)法完全描述 css 的功能,如媒體查詢、偽類等等 style;
- 樣式無(wú)法自定義更簡(jiǎn)短的樣式集、樣式集的組合、嵌套;
- 內(nèi)聯(lián)樣式無(wú)法直接引用 className,這樣我們通常需要編寫 css 文件,設(shè)置 className 和 style;
- 并且默認(rèn)優(yōu)先級(jí)比 css 高,css 和 內(nèi)聯(lián)樣式混合使用需要注意優(yōu)先級(jí);
cssin 最后生成的還是 css 樣式,所以不會(huì)有以上的問(wèn)題
訂制自定義樣式
和眾多 css 框架一樣,cssin 允許你自定義樣式集,這樣可以用更簡(jiǎn)短的聲明來(lái)描述樣式
cssin 有一個(gè) addSheets 屬性用來(lái)添加樣式映射表
我們現(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 , 我們不會(huì)需要有css代碼,所以可以降低項(xiàng)目首屏的資源請(qǐng)求。
自定義樣式除了可以簡(jiǎn)化開(kāi)發(fā),還可以減少js代碼量,從而最終達(dá)到相對(duì)更少的打包資源。
定制媒體查詢
cssin 默認(rèn)配置了 4 個(gè)級(jí)別的媒體查詢,我們可以覆蓋它或者創(chuàng)建新的規(guī)則
注意,我們約定,只有以 @ 開(kāi)頭的才是媒體查詢對(duì)象
// 默認(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)建一個(gè) @xxl
addSheets({
'@md': (v) => `@media (min-width: 800px) {${v}}`,
'@xxl': (v) => `@media (min-width: 1920px) {${v}}`,
});
訂制組件
我們希望把剛剛的代碼簡(jiǎn)寫成更精巧的組件, 組件其實(shí)是一組樣式集
設(shè)置自定義組件, 因?yàn)?sheets 是一個(gè)簡(jiǎn)單的對(duì)象表,請(qǐng)注意不要和其他自定義樣式重名導(dǎo)致覆蓋
它和自定義樣式或媒體查詢的區(qū)別是它的值是一個(gè)單純的字符串:
import React from 'react';
import cssin, { addSheets } from 'cssin';
addSheets({
// 區(qū)別于自定義樣式,組件的值是一個(gè)字符串,它遵循 cssin 語(yǔ)法,可以調(diào)用其他組件和自定義樣式
button: 'bgc:#f66; hover:bgc:#f22; padding:1.2rem; color:--button-color;',
});
// 最終只需要包裹一個(gè)單詞的聲明
export default () => {
return <div className={cssin`button;`}>Button</div>;
};
注意,組件不可以和偽類或者媒體查詢進(jìn)行組合,因?yàn)榻M件內(nèi)部就已經(jīng)包含了偽類或媒體查詢
覆蓋 setAttribute
這里涉及一些魔法,請(qǐng)辯證的使用。
作者在編寫代碼的時(shí)候不希望每次都引用 cssin,這對(duì)作者來(lái)說(shuō)太過(guò)繁瑣了,如果你也有這種感覺(jué),可以使用 coverAttribute
index.js
import React from 'react';
import { coverAttribute } from 'cssin';
// 這里我們覆蓋inlist對(duì)象,它會(huì)模擬 className={cssin`...`}
coverAttribute('inlist');
// 請(qǐng)確保 coverAttribute 在 ReactDOM.render 之前執(zhí)行
ReactDOM.render(<App />, document.getElementById('root'));
App.js
import React from 'react';
// 最終只需要一個(gè)單詞的聲明,就像原生聲明一樣
export const App = () => {
return (
<div>
<div inlist="button">Button</div>
</div>
);
};
inlint 可以和 className 一起使用,前提是 className 必須在 inlist 之前聲明
import React from 'react';
// 最終只需要一個(gè)單詞的聲明,就像原生聲明一樣
export const App = () => {
return (
<div>
<div className="" inlist="button">Button</div>
</div>
);
};
我們需要注意,覆蓋 setAttribute 并不是非常正確的做法,因?yàn)樗鼛?lái)了其他開(kāi)發(fā)人員的不友好,其他人員并不知道我們做了這些黑魔法的前提下,會(huì)非常困惑。
另外,請(qǐng)不要覆蓋 className 等常用屬性,這樣會(huì)讓其他組件庫(kù)失效
使用 css 原生功能在 javascript 中
使用 {} 編寫單純的 css 片段
有時(shí)候,我們會(huì)需要編寫單純的 css 片段,我們約定若字符串中包含 {}
此時(shí)傳入的字符串只會(huì)被當(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è)計(jì)、開(kāi)箱即用,亦可以作為一個(gè)自定義樣式集合的參照標(biāo)本
默認(rèn)情況下 cssin 并未配置它,如果我們需要可以如下配置:
import 'cssin/commonSheets'; // 引入 sheets集合
import 'cssin/commonValues'; // 引入 css-value 集合
我們可以查看這兩個(gè)文件,就是對(duì) cssin API 簡(jiǎn)單的運(yùn)用,也歡迎有朋友提供更好的自定義樣式及組件:
性能開(kāi)銷
cssin 雖然是運(yùn)行時(shí)創(chuàng)建css樣式,但是它有著極低的性能開(kāi)銷。
我們可以看到,創(chuàng)建重復(fù)執(zhí)行500次,每次大約創(chuàng)建20條樣式,只消耗了 1.6ms, 這是因?yàn)閏ssin會(huì)對(duì)整體屬性做緩存,還會(huì)對(duì)子屬性創(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)在開(kāi)始使用它:
$ npm i cssin --save