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

咱们先看看咱们最终的目标, 咱们能够如何描述代码:javascript

import React from "react";

export default () => (
  <button inlist="bg:#f00; @md:display:none; hover:radius:8px; radius:4px"> 我是一个按钮 </button>
);
复制代码

在这个文件中,咱们 "彷佛没有引入任何库",就好像写内联样式同样,把样式描述、媒体查询、伪类都实现了, 而且可自定义样式名,如 bg、radius。css

cssin

cssin 是一个高度可定制的低级 CSS-In-JS 框架,它为您提供构建定制设计所需的全部构建模块,而无需任何使人讨厌的样式,你可使用内联样式的全部语法,和其余扩展语法。html

大多数 CSS 框架都作得太多了。 它们带有各类预先设计的组件,如按钮,卡片和警报,这些组件可能会帮助您最初快速移动,可是当您的网站使用自定义设计脱颖而出时,会致使更多的痛苦。java

cssin 不同凡响。react

cssin 提供了低级实用程序类,而不是固定的预先设计的组件,使您能够构建彻底自定义的设计而无需离开 JS。android

cssin 生成的每一个相同的样式值能够被重复引用,而不是从新建立。ios

理念

咱们在使用 cssin 以前作了很是多的尝试,css\less\scss, tailwindCSS, styled-components 和其余 css-in-js 方案。其中 tailwindCSS 是最符合生产须要的,咱们从中学到许多东西和理念;但是这些样式方案对于做者来讲并没能真正解决问题:git

简短高效的描述个人样式,而且不离开 js;固然也不放弃 css 的任何一个特性github

css-in 就是为了解决此类问题而存在npm

旨在定制

cssin 全部样式都是经过定制而得,cssin 容许您自定义它。这包括颜色,边框大小,字体粗细,间距实用程序,断点,阴影和任何 css 样式。

cssin 采用纯 Typescript 编写,而且无需对项目框架进行配置,这意味着您能够轻松得到真正编程语言的所有功能。

cssin 至关于在内敛样式上扩展了伪类和媒体查询,而且支持自定义属性名和设定组件。

cssin 不只仅是一个 CSS-IN-JS 框架,它仍是一个建立设计系统的引擎。

轻巧

  • 仅有 2kb (gzip)
  • 每条样式会被缓存, 以更高的性能进行样式处理
  • 能够在任何框架中使用,如你喜欢的 React、Vue、Stencil

安装

$ npm i cssin --save
复制代码

先看看展示形式

example: navar.workos.top

在没有进行任何配置以前,cssin 的语法和内敛样式是一致的

import React from "react";
import cssin from "cssin";

// 设置一个全局的 css-value
document.body.style.setProperty("--button-color", "#fff");

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

看起来还不错,有点像内联样式,可是又有些许不一样,彷佛直接描述了伪类和媒体查询,并且代码不够精简。

好的,咱们最后会经过简单的配置的让样式描述变成这样:

import React from "react";
import cssin from "cssin";

export default () => {
  return (
    <div className={cssin`btn:#f33, 8px; hover:bg:#f33; @md:radius:4px;`}> 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>;
};
复制代码

咱们会一步步来达到最后的步骤。

或许一段话就能够描述清楚 cssin

咱们先回顾刚开始的代码块:

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

上述代码有点像内联样式,可是又有一些不一样,由于它能够实现伪类及更好的自定义,咱们逐步分析:

  • 和编写内联样式同样的编写 css 样式, 如: background-color: #f66; padding: 4px;
  • 直接使用伪类, 伪类在属性名以前,使用:分割如: hover:background-color=#f33
  • 能够直接描述媒体查询等功能, 媒体查询对象使用@开头, 如: @md:border-radius=4px

其余规则:

  • 若是只有属性名,那么它将是一个组件, 如 button;
  • 若是值是一个单一的 css 变量, 如 color:--button-color; 等效于 color:var(--button-color);
  • 使用!表示!important, 如 color: #f00!; 等效于 color: #f00 !important
  • 若是只有属性名,而且以 . 开头, 那么就是对原生 css 样式的引用, 如 .button;
  • 若是包含 {}, 表示这是一个纯 css, 它会被插入至全局样式中, 如 body { margin:0px; }

以上就是 cssin 的全部规则

下面是完整属性的表达式: @[媒体查询]:[伪类名]:[属性名]:[属性值];

下面这句完整的语法描述:

// 当媒体查询大于 760px 时、鼠标移入时、描边等于 #f00;
cssin`@md:hover:border:1px solid #f00;`;
复制代码

为何不直接编写 style 内联样式?

  1. style 样式没法彻底描述 css 的功能,如媒体查询、伪类等等 style;
  2. 样式没法自定义更简短的样式集、样式集的组合、嵌套;
  3. 内联样式没法直接引用 className,这样咱们一般须要编写 css 文件,设置 className 和 style;
  4. 而且默认优先级比 css 高,css 和 内联样式混合使用须要注意优先级;

cssin 最后生成的仍是 css 样式,因此不会有以上的问题

若是更喜欢编写 style 属性

有的朋友更喜欢编写 style 属性,可是 style 中的一个痛点是没法实现伪类或媒体查询。

cssin 足够轻量,咱们也能够仅仅使用它的伪类或媒体查询特性,来配合 style 属性进行项目样式的编写.

不过咱们要注意,style 中编写的属性权重默认高于 className 中的样式,因此须要添加 !important:

import React from "react";
import cssin from "cssin";

export default () => {
  return (
    <div className={cssin`hover:background:#f00 !important;`} style={{ background: "#00f", fontSize: "20px" }} > Button </div>
  );
};
复制代码

因为这个模式很常见,因此在 cssin 中,它可使用 ! 直接表示 !important:

...
export default () => {
  return (
    <div className={cssin`hover:background:#f00!;`} style={{ background: "#00f", fontSize: "20px" }} > Button </div>
  );
};Z
复制代码

订制自定义样式

和众多 css 框架同样,cssin 容许你自定义样式集,这样能够用更简短的声明来描述样式

cssin 有一个 addSheets 属性用来添加样式映射表

咱们如今达成刚刚的约定,将:

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

变成:

btn:#f33, 4px; hover:bg:#f33; @md:radius:8px;

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, 4px; hover:bg:#f33; @md:radius:8px;`}>Button</div>;
};
复制代码

因为使用 cssin , 咱们不会须要有 css 代码,因此能够下降项目首屏的资源请求。

自定义样式除了能够简化开发,还能够减小 js 代码量,从而最终达到相对更少的打包资源。

订制媒体查询

cssin 默认配置了 4 个尺寸级别的媒体查询,和基于设备媒体查询,咱们能够覆盖它或者建立新的规则

注意,咱们约定,只有以 @ 开头的才是媒体查询对象

// 默认的媒体查询
addSheets({
  "@sm": (v: string) => `@media (min-width: 640px) {${v}}`,
  "@md": (v: string) => `@media (min-width: 768px) {${v}}`,
  "@lg": (v: string) => `@media (min-width: 1024px) {${v}}`,
  "@xl": (v: string) => `@media (min-width: 1280px) {${v}}`,
  "@ios": (v: string) =>
    `@media (min-width: ${device.isIos ? "0px" : "9999px"}) {${v}}`,
  "@android": (v: string) =>
    `@media (min-width: ${device.isAndroid ? "0px" : "9999px"}) {${v}}`,
  "@native": (v: string) =>
    `@media (min-width: ${device.isNative ? "0px" : "9999px"}) {${v}}`,
  "@pc": (v: string) =>
    `@media (min-width: ${device.isPc ? "0px" : "9999px"}) {${v}}`
});
// 咱们覆盖 @md 以及建立一个 @xxl
addSheets({
  "@md": v => `@media (min-width: 800px) {${v}}`,
  "@xxl": v => `@media (min-width: 1920px) {${v}}`
});
复制代码

使用媒体查询,如下例子是屏幕宽度大于 800px,button 宽度为 200px,而且在 native 端隐藏

import React from "react";
// 最终只须要包裹一个单词的声明
export default () => {
  return (
    <div inlist="width:100px; height:50px; @md:width:200px; @native:display:none;"> Button </div>
  );
};
复制代码

订制组件

咱们但愿把刚刚的代码简写成更精巧的组件, 组件实际上是一组样式集

设置自定义组件, 由于 sheets 是一个简单的对象表,请注意不要和其余自定义样式重名致使覆盖

它和自定义样式或媒体查询的区别是它的值是一个单纯的字符串:

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

addSheets({
  // 区别于自定义样式,组件的值是一个字符串,它遵循 cssin 语法,能够调用其余组件和自定义样式
  button: "bgc:#f66; hover:bgc:#f22; padding:8px; color:--button-color;"
});

// 最终只须要包裹一个单词的声明
export default () => {
  return <div className={cssin`button;`}>Button</div>;
};
复制代码

注意,组件不能够和伪类或者媒体查询进行组合,由于组件内部就已经包含了伪类或媒体查询

覆盖 setAttribute

这里涉及一些魔法,请辩证的使用。

做者在编写代码的时候不但愿每次都引用 cssin,这对做者来讲太过繁琐了,若是你也有这种感受,可使用 cssin 的 coverAttribute

index.js

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

// 这里咱们覆盖inlist对象,它会模拟 className={cssin`...`}
coverAttribute("inlist");

// 请确保 coverAttribute 在 ReactDOM.render 以前执行
ReactDOM.render(<App />, document.getElementById("root")); 复制代码

App.js

import React from "react";

// 最终只须要一个单词的声明,就像原生声明同样
export const App = () => {
  return (
    <div inlist="full; m:20px;"> <div inlist="button">Button</div> </div>
  );
};
复制代码

inlint 能够和 className 一块儿使用,前提是 className 必须在 inlist 以前声明

import React from "react";

// 最终只须要一个单词的声明,就像原生声明同样
export const App = () => {
  return (
    <div> <div className="app-box" inlist="button"> Button </div> </div>
  );
};
复制代码

咱们须要注意,覆盖 setAttribute 并非很是正确的作法,由于它带来了其余开发人员的不友好,其余人员并不知道咱们作了这些黑魔法的前提下,会很是困惑。

另外,请不要覆盖 className 等经常使用属性,这样会让其余组件库失效

使用 css 原生功能在 javascript 中

使用 {} 编写单纯的 css 片断

有时候,咱们会须要编写单纯的 css 片断,咱们约定若字符串中包含 {}

此时传入的字符串只会被当成单纯的 css 样式进行注入至 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 inlist="margin:4px; .box">Button</div>;
};
复制代码

使用预设自定义样式、组件、 css-values

cssin 提供了一整套预设的自定义样式集合及 css-value 集合,它精心设计、开箱即用,亦能够做为一个自定义样式集合的参照标本

默认状况下 cssin 并未配置它,若是咱们须要能够以下配置:

import "cssin/commonSheets"; // 引入 sheets集合
import "cssin/commonCSSValues"; // 引入 css-value 集合
复制代码

commonSheets 中的内容:

自定义样式名 映射样式 使用
dis display dis: flex;
items align-items items: 20px;
justify justify-content justify: start;
self align-self self: center;
content align-content content: end;
z z-index z: 10;
p padding p: 5rem;
px pading-left, padding-right px: 5rem;
py padding-top, padding-bottom py: 5rem;
pl padding-left pl: 5rem;
pt padding-top pt: 5rem;
pr padding-right pr: 5rem;
pb padding-bottom pb: 5rem;
m margin m: 5rem;
mx margin-left, margin-right mx: 5rem;
my margin-top, margin-bottom my: 5rem;
ml margin-left ml: 5rem;
mt margin-top mt: 5rem;
mr margin-right mr: 5rem;
mb margin-bottom mb: 5rem;
w width w: 5rem;
w-min min-width w-min: 5rem;
w-max max-width w-max: 5rem;
w-min-max min-width, max-width w-min-max: 5rem;
h height h: 5rem;
h-min min-height h-min: 5rem;
h-max max-height h-max: 5rem;
h-min-max min-height, max-height h-min-max: 5rem;
b border: ${v} solid; b: 5rem;
bl border-left: ${v} solid; bl: 5rem;
bt border-top: ${v} solid; bt: 5rem;
br border-right: ${v} solid; br: 5rem;
bb border-bottom: ${v} solid; bb: 5rem;
bc border-color bc: #f00;
radius border-radius radius: 2rem;
font font-size font: 1.25rem;
bg background background: #f00;
bgc background-color bgc: #f00;
linear transition: all ${v} linear; linear: 0.3s;
ease transition: all ${v} ease; ease: 0.3s;
ease-in transition: all ${v} ease-in; ease-in: 0.3s;
ease-out transition: all ${v} ease-out; ease-out: 0.3s;
ease-in-out transition: all ${v} ease-in-out; ease-in-out: 0.3s;
move-x transform: translateX(${v}); move-x: 50%;
move-y transform: translateY(${v}); move-y: 50%;
move-z transform: translateZ(${v}); move-z: 50%;
rotate transform: rotate(${v}deg); rotate: 180;
scale transform: scale(${v}, ${v}); scale: 0.7;
如下均为组件 组件不须要设置值
col dis:flex; flex-direction:column; col;
row dis:flex; flex-direction:row; row;
center col; justify:center; items:center; center;
fixed position:fixed; fixed;
static position:static; static;
absolute position:absolute; absolute;
relative position:relative; relative;
sticky position:sticky; sticky;
left left:0px; left;
top top:0px; top;
right right:0px; right;
bottom bottom:0px; bottom;
bold font-weight: bold; bold;

commonCSSValues 设置了一些 css-value, 其中的颜色、尺寸分类、投影均取自于 tailwindCSS 的配置:

使用预设的示例:

import React from "react";
import cssin from "cssin";

// 使用预设的自定义样式和 css-value 配合使用
export default () => {
  return (
    <div className={cssin` bg:--gray-200; p:--2; font:--font-sm; box-shadow:--shadow-xl `} > Button </div>
  );
};
复制代码

咱们能够查看这两个文件,它们只是使用 cssin API 的简单配置,也欢迎有朋友提供更好的自定义样式及组件:

commonSheets.ts

commonCSSValues.ts

性能开销

cssin 虽然是运行时建立 css 样式,可是它有着极低的性能开销。

咱们能够看到,建立重复执行 500 次,每次大约建立 20 条样式,只消耗了 1.6ms, 这是由于 cssin 会对总体属性作缓存,还会对子属性建立 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
复制代码

如今开始使用它:

$ npm i cssin --save
复制代码

盼望 Star 或提出贡献,仓库地址:

github.com/ymzuiku/css…

相关文章
相关标签/搜索