cssin

cssin 2.0 删减了许多API,要使用原有版本,请使用 master/tag/1.6.7 版本

Usage no npm install needed!

<script type="module">
  import cssin from 'https://cdn.skypack.dev/cssin';
</script>

README

cssin 2.0 删减了许多API,要使用原有版本,请使用 master/tag/1.6.7 版本

我们先看看我们最终的目标, 我们可以如何描述代码:

原生JS用法:

const view = document.createElement('div');

cssin`bg:#f00; @m:display:none; hover:radius:8px; radius:4px`(view);

React用法:

import React from "react";

export default () => (
  <button className={cssin`bg:#f00; @m:display:none; hover:radius:8px; radius:4px`}>
    我是一个按钮
  </button>
);

或者:

import React from "react";

export default () => (
  <button cssin="bg:#f00; @m:display:none; hover:radius:8px; radius:4px">
    我是一个按钮
  </button>
);

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

cssin

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

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

cssin 与众不同。

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

cssin 生成的每个相同的样式值可以被重复引用,而不是重新创建。

理念

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

简短高效的描述我的样式,并且不离开 js;当然也不放弃 css 的任何一个特性

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

旨在定制

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 cssin="background-color:#f66; hover:background-color:#f33; padding:8px; color:#000; border:2px solid #f33; @m: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; @m:radius:4px;`}>
      Button
    </div>
  );
};

或者极限简洁:

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

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

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

或许一段话就可以描述清楚 cssin

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

export default () => {
  return (
    <div cssin="background-color:#f66; hover:background-color:#f33; padding:8px; color:--button-color; border:2px solid #f33; @m:border-radius:4px;">
      Button
    </div>
  );
};

上述代码有点像内联样式,但是又有一些不同,因为它可以实现伪类及更好的自定义,我们逐步分析:

  • 和编写内联样式一样的编写 css 样式, 如: background-color: #f66; padding: 4px;
  • 直接使用伪类, 伪类在属性名之前,使用:分割如: hover:background-color=#f33
  • 可以直接描述媒体查询等功能, 媒体查询对象使用@开头, 如: @m: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`@m: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
      cssin="hover:background:#f00 !important;"
      style={{
        background: "#00f",
        fontSize: "20px"
      }}
    >
      Button
    </div>
  );
};

由于这个模式很常见,所以在 cssin 中,它可以使用 ! 直接表示 !important:

...
export default () => {
  return (
    <div
      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; @m:border-radius:8px;

变成:

btn:#f33, 4px; hover:bg:#f33; @m: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 cssin="btn:#f33, 4px; hover:bg:#f33; @m:radius:8px">Button</div>;
};

由于使用 cssin , 我们不会需要有 css 代码,所以可以降低项目首屏的资源请求。

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

订制媒体查询

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

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

// 默认的媒体查询
const max = "@media (max-width: ";
const min = "@media (min-width: ";

addSheets({
  "@xs": (v: string) => `${max}640px){${v}}`,
  "@s": (v: string) => `${max}720px){${v}}`,
  "@m": (v: string) => `${max}1024px){${v}}`,
  "@l": (v: string) => `${max}1280px){${v}}`,
  "@xl": (v: string) => `${max}1920px){${v}}`,
  "@!xs": (v: string) => `${min} 640px){${v}}`,
  "@!s": (v: string) => `${min}720px){${v}}`,
  "@!m": (v: string) => `${min}1024px){${v}}`,
  "@!l": (v: string) => `${min}1280px){${v}}`,
  "@!xl": (v: string) => `${min}1920px){${v}}`,
  "@ios": (v: string) => `${min}${os.isIOS ? "0px" : "9999px"}){${v}}`,
  "@android": (v: string) => `${min}${os.isAndroid ? "0px" : "9999px"}){${v}}`,
  "@pc": (v: string) => `${min}${os.isPc ? "0px" : "9999px"}) {${v}}`,
  "@mobile": (v: string) => `${min}${os.isMobile ? "0px" : "9999px"}){${v}}`
});
// 我们覆盖 @m 以及创建一个 @xxl
addSheets({
  "@m": v => `@media (min-width: 800px) {${v}}`,
  "@xl": v => `@media (min-width: 1920px) {${v}}`
});

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

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

订制组件

我们希望把刚刚的代码简写成更精巧的组件, 组件其实是一组样式集

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

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

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

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

// 最终只需要包裹一个单词的声明
export default () => {
  return <div cssin="button">Button</div>;
};

注意,组件不可以和伪类或者媒体查询进行组合,因为组件内部就已经包含了伪类或媒体查询

性能开销

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/cssin