css in js以及css的一些动画
css-in-js 作为一个理念较新的开发思路,拥有如下几个明显的优缺点。
优点:
缺点:
除了上述缺点外,css-in-js 还有三点深度使用后才能察觉的坑:
!impprtant
定义。运行时解析,是 css-in-js 方案永远跨不过去的困境,即便对于编译时 css-in-js 方案来说,也免不了在渲染时做额外的逻辑执行拖慢渲染速度
原因是当 React 重渲染组件时,需要重新解析样式定义,并序列化 className,当渲染非常频繁时会导致明显的性能瓶颈,而解决方法是把样式定义抽出来,但这样就损失了第三个优点,即无法读取 js 变量了
css-in-js 增加了 8~16kb 其实是在强行堆缺点了,除非你的项目只有一行 css 定义。如果我们只考虑传输时的包体积与 HTML 中样式定义数量,而忽略运行时产生的性能负担,那么 css-in-js 在大型项目无疑是最优的。
原因就是 css-in-js 样式是按需插入的,没有渲染的组件就不会插入样式。甚至渲染了的组件也不一定会插入样式,因为 css-in-js 可以对包含相同样式定义的场景做 className 合并,类似于 webpack 打包时,可以把不同模块公共代码抽到一个 chunk 里
PostCSS 的主要功能只有两个:第一个就是前面提到的把 CSS 解析成 JavaScript 可以操作的 抽象语法树结构(Abstract Syntax Tree,AST),第二个就是调用插件来处理 AST 并得到结果。
PostCSS 一般不单独使用,而是与已有的构建工具进行集成。PostCSS 与主流的构建工具,如 Webpack、Grunt 和 Gulp 都可以进行集成。完成集成之后,选择满足功能需求的 PostCSS 插件并进行配置。
安装
## postcss往往不单独使用
npm install postcss postcss-loader autoprefixer cssnano postcss-cssnext
## 其中autoprefixer是添加前缀的,解决浏览器兼容问题。比如:-ms-transform:rotate(7deg);
## cssnano是处理压缩的
## postcss-cssnext是另一种css语法
配置
{
loader:'postcss-loader',
options:{
ident: 'postcss', //说明options里面插件的使用是针对于谁的,我们这里是针对于postcss的
plugins:[ //这里的插件只是这对于postcss
require('autoprefixer')() //引入添加前缀的插件,第二个空括号是将该插件执行
]
}
}
css module不是将 CSS 改造成编程语言,而是功能很单纯,只加入了局部作用域和模块依赖,这恰恰是网页组件最急需的功能。
因此,CSS Modules 很容易学,因为它的规则少,同时又非常有用,可以保证某个组件的样式,不会影响到其他组件。
CSS Modules 提供各种插件,支持不同的构建工具。本文使用的是 Webpack 的css-loader
插件,因为它对 CSS Modules 的支持最好,而且很容易使用。
webpack中配置
module.exports = {
entry: __dirname + '/index.js',
output: {
publicPath: '/',
filename: './bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'stage-0', 'react']
}
},
{
test: /\.css$/,
loader: "style-loader!css-loader?modules"
},
]
}
};
全局样式
CSS Modules 允许使用:global(.className)
的语法,声明一个全局规则。凡是这样声明的class
,都不会被编译成哈希字符串。
CSS Modules 还提供一种显式的局部作用域语法:local(.className)
,等同于.className
继承/导入
在 CSS Modules 中,一个选择器可以继承另一个选择器的规则,这称为"组合"
.className {
background-color: blue;
}
.title {
composes: className;
color: red;
}
选择器也可以继承其他CSS文件里面的规则。
.title {
composes: className from './another.css';
color: red;
}
CSS Modules 支持使用变量,不过需要安装 PostCSS 和 postcss-modules-values。
安装postcss
npm install --save postcss-loader postcss-modules-values
在webpack中配置
var values = require('postcss-modules-values');
module.exports = {
entry: __dirname + '/index.js',
output: {
publicPath: '/',
filename: './bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel',
query: {
presets: ['es2015', 'stage-0', 'react']
}
},
{
test: /\.css$/,
loader: "style-loader!css-loader?modules!postcss-loader"
},
]
},
postcss: [
values
]
};
定制hash字符串
css-loader
默认的哈希算法是[hash:base64]
,这会将.title
编译成._3zyde4l1yATCOkgn-DBWEL
这样的字符串。
在webpack中配置
module: {
loaders: [
// ...
{
test: /\.css$/,
loader: "style-loader!css-loader?modules&localIdentName=[path][name]---[local]---[hash:base64:5]"
},
]
}
styled-components 是一个常用的 css in js 类库。和所有同类型的类库一样,通过 js 赋能解决了原生 css 所不具备的能力,比如变量、循环、函数等。诸如 sass&less 等预处理可以解决部分 css 的局限性,但还是要学习新的语法,而且需要对其编译,其复杂的 webpack 配置也总是让开发者抵触。而 styled-components 很好的解决了这些问题,很适合 React 技术栈的项目开发。
安装
npm install --save styled-components
使用
import styled from 'styled-components';
const Wrapper = styled.section`
margin: 0 auto;
width: 300px;
text-align: center;
`;
const Button = styled.button`
width: 100px;
color: white;
background: skyblue;
`;
render(
<Wrapper>
<Button>Hello World</Button>
</Wrapper>
);
基于props定制组件
React传入的所有 props 都可以在定义组件时获取到,这样就可以很容易实现组件主题的定制。如果没有 styled-components 的情况下,需要使用组件 style 属性或者定义多个 class 的方式来实现。
const Button = styled.button`
background: ${props => props.primary ? 'palevioletred' : 'white'};
color: ${props => props.primary ? 'white' : 'palevioletred'};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
render(
<div>
<Button>Normal</Button>
<Button primary>Primary</Button>
</div>
);
样式继承
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const TomatoButton = Button.extend`
color: tomato;
border-color: tomato;
`;
维持其他属性
有时候正在使用第三方库,style-component使用时可以一并引入
const Password = styled.input.attrs({
type: 'password',
})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
const Button = styled.button.attrs({
className: 'small',
})`
background: black;
color: white;
cursor: pointer;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid black;
border-radius: 3px;
`;
注入全局
import styled, { injectGlobal } from 'styled-components';
injectGlobal`
@font-face {
font-family: 'Operator Mono';
src: url('../fonts/Operator-Mono.ttf');
}
body {
margin: 0;
}
`;
支持动画
import { keyframes } from 'styled-components';
const fadeIn = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
}
`;
const FadeInButton = styled.button`
animation: 1s ${fadeIn} ease-out;
`;
安装
yarn add @vanilla-extract/css @vanilla-extract/webpack-plugin
在webpack中配置
const { VanillaExtractPlugin } = require('@vanilla-extract/webpack-plugin')
module.exports = {
entry: './src/index.js',
// ....
plugins: [new VanillaExtractPlugin()]
};
使用
import { style } from '@vanilla-extract/css';
export const parentClass = style({
background: 'red',
':hover': {
background: 'blue',
},
});
export const childClass = style({
selectors: {
'&:nth-child(2n)': {
background: '#fafafa',
},
[`${parentClass} &`]: {
color: 'pink',
},
},
});
创建样式集合
import { styleVariants } from '@vanilla-extract/css';
const base = style({ padding: 12 });
const backgrounds = {
primary: 'blue',
secondary: 'aqua'
} as const;
export const variant = styleVariants(
backgrounds,
(background) => [base, { background }]
);
全局样式
import { style, globalStyle } from '@vanilla-extract/css';
export const parentClass = style({});
globalStyle(`${parentClass} > div`, {
color: 'red'
});
const Demo = () => (
<div className={parentClass}>
<Select/>
</div>
)
创建主题
// themes.css.ts
import { createTheme } from '@vanilla-extract/css';
export const [themeA, vars] = createTheme({
color: {
brand: 'blue'
},
font: {
body: 'arial'
}
});
export const themeB = createTheme(vars, {
color: {
brand: 'pink'
},
font: {
body: 'comic sans ms'
}
});
相比styled-component的优点:
安装
npm install @vanilla-extract/css @vanilla-extract/esbuild-plugin
Es-build配置
const { vanillaExtractPlugin } = require('@vanilla-extract/esbuild-plugin');
require('esbuild').build({
entryPoints: ['app.ts'],
bundle: true,
plugins: [vanillaExtractPlugin()],
outfile: 'out.js',
}).catch(() => process.exit(1))
最好和postcss一起使用
const {
vanillaExtractPlugin
} = require('@vanilla-extract/esbuild-plugin');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
async function processCss(css) {
const result = await postcss([autoprefixer]).process(
css,
{
from: undefined /* suppress source map warning */
}
);
return result.css;
}
require('esbuild')
.build({
entryPoints: ['app.ts'],
bundle: true,
plugins: [
vanillaExtractPlugin({
processCss
})
],
outfile: 'out.js'
})
.catch(() => process.exit(1));
安装
npm install @vanilla-extract/css @vanilla-extract/vite-plugin
使用
import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin';
// vite.config.js
export default {
plugins: [vanillaExtractPlugin()]
}
安装
npm install @vanilla-extract/css @vanilla-extract/babel-plugin @vanilla-extract/next-plugin
安装
const {
createVanillaExtractPlugin
} = require('@vanilla-extract/next-plugin');
const withVanillaExtract = createVanillaExtractPlugin();
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = withVanillaExtract(nextConfig);
可以在css-in-js中计算css样式的值
npm install @vanilla-extract/css-utils
使用
import { calc } from '@vanilla-extract/css-utils';
const styles = {
height: calc.multiply('var(--grid-unit)', 2)
};
零运行时的css in js样式
使用
import { stylesheet } from "astroturf";
const height = 2;
const styles = stylesheet`
.btn {
appearance: none;
height: ${height}rem;
display: inline-block;
padding: .5rem 1rem;
}
.primary {
color: white:
border: 1px solid white;
background-color: taupe;
&:hover {
color: taupe:
border-color: taupe;
background-color: white;
}
}
`;
const Button = ({ primary }) => {
const button = document.createElement("button");
button.classList.add(styles.btn, primary && styles.primary);
return button;
};
使用css
import React from "react";
import { css } from "astroturf";
const btn = css`
color: black;
border: 1px solid black;
background-color: white;
`;
export default function Button({ children }) {
return (
<button
{...props}
css={css`
color: blue;
border: 1px solid blue;
padding: 0 1rem;
`}
>
{children}
</button>
);
// return <button className={btn}>{children}</button>;
}
使用
import * as React from "react";
import { styled } from "astroturf/react";
const Button = styled("button")`
border: 1px solid transparent;
&.disabled {
opacity: 0.6;
}
&.variant-primary {
color: blue;
border-color: blue;
}
`;
<Button variant="primary" disabled />;
Linaria 是一个 零运行时 的JSS 框架,其特点有:
Linaria 同时支持 Webpack , Rollup 和 Sevlte
和 Webpack 配合很容易,只需要将 babel-loader 加上 linaria/loader
即可,一定确保 linaria/loader
紧挨着且在 babel-loader 之后
{
test: /\.js$/,
use: [
{ loader: 'babel-loader' },
{
loader: 'linaria/loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
cacheDirectory: '.linaria-cache', // 缓存所在文件见,默认 .linaria-cache
extension: '.linaria.css', // CSS 文件处于中间态时的命名,默认 .linaria.css
preprocessor: 'stylis', // 定义 css 的预处理器,默认为 stylis
},
}
],
}
此外,为了将收集到的样式抽取出来,你需要另外一个 Webpack 插件 mini-css-extract-plugin
, 执行 npm i -D css-loader mini-css-extract-plugin
来安装
然后导入 mini-css-extract-plugin
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: process.env.NODE_ENV !== 'production',
},
},
{
loader: 'css-loader',
options: {
sourceMap: process.env.NODE_ENV !== 'production',
},
},
],
},
在react中使用
import { css, cx } from 'linaria';
const box = css`
animation: rotate 1s linear infinite;
@keyframes rotate {
{ from: 0deg; }
{ to: 360deg; }
}
`;
const yarn = css`
color: violet;
`;
const fun = css`
display: flex;
`;
function App({ isPlaying }) {
return <Playground className={cx(box, yarn, isPlaying && fun)} />;
}
cx()
这个函数看着很像一个流行库 classnames
,但还是有点区别的, cx()
不处理对象
安装linaria/react库
npm i linaria/react --dev
使用
import { styled } from 'linaria/react';
const Card = styled.div`
border: 1px solid #fff;
border-radius: 4px;
`
const App = () => {
return <Card className={cs(active && 'active', className)}>1</Card>
}
服务端api
在做 SSR 时我们不仅需要将相应的 HTML 代码进行返回,也需要将 需要的 样式代码返回,这就那些 关键的 的 CSS 代码,我们可以通过利用 collect()
函数来抽离出关键的 CSS 代码
import { collect } from 'linaria/server';
const css = fs.readFileSync('./dist/styles.css', 'utf8');
const html = ReactDOMServer.renderToString(<App />);
const { critical, other } = collect(html, css);
// critical – returns critical CSS for given html
// other – returns the rest of styles
collect()
会根据元素的 class 属性,将用到的 CSS 代码抽离出来,这样就可以跟随 html 一起返回
需要注意的被抽离出来的 css 代码选择器的顺序会变乱掉,这使得如果你的样式依赖选择器顺序的权重,可能就会出现意料的之外的错误,不过由于 Linaria 生成的 class 命名都是唯一的,所以一般不会出现这个问题,但与其他的库协作时需要注意到这点
Linaria 是基于 CSS 变量的,大部分现代浏览器支持这个特性,但是对于 IE 11 以及以下,是不支持的,所以如果你需要支持 IE 11 ,也许 Linaria 不是你最好的选择
安装
npm install --save @emotion/react
使用
import { jsx } from '@emotion/react'
let SomeComponent = props => {
return (
<div
css={{
color: 'hotpink'
}}
/>
)
}
@emotion/css
import { css } from '@emotion/css';
const customStyle = css`
margin-bottom: 4px;
`
const App = () => {
return <p className={labelStyle}>1</p>;
}
https://github.com/emotion-js/emotion
安装
npm install @picocss/pico
在react中使用
npm install @stitches/react
Tailwind CSS就是一个使用公用程序类的CSS框架,这个公用类仅仅只是一个CSS样式类,其中包含着各种各样的CSS样式供开发者开箱即用而不需要再编写复杂繁多的CSS,这些CSS样式大多是安全的能让页面看上去更美观。同时它也支持自由扩展,可以在原有的基础类中针对自己的需求进行个性化定制。
Tailwind的优势
安装
yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9
添加配置文件tailwind.config
tailwind.config.js可以改变tailwindcss的基础配置,以做到让开发者的定制化开发,在配置中加入某些类以便可以在全局中使用
const colors = require('tailwindcss/colors')
module.exports = {
purge: ['./src/**/*.html', './src/**/*.tsx', './src/**/*.ts'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {
spacing: {
1: '1px',
2: '2px',
3: '3px',
4: '4px',
5: '5px',
6: '6px',
7: '7px',
8: '8px',
9: '9px',
10: '10px',
11: '11px',
12: '12px',
13: '13px',
14: '14px',
15: '15px',
16: '16px',
17: '17px',
18: '18px',
19: '19px',
20: '20px',
21: '21px',
22: '22px',
23: '23px',
24: '24px',
25: '25px',
26: '26px',
27: '27px',
28: '28px',
29: '29px',
30: '30px',
31: '31px',
32: '32px',
34: '34px',
36: '36px',
38: '38px',
40: '40px',
44: '44px',
48: '48px',
52: '52px',
56: '56px',
60: '60px',
},
},
fontSize: {
xs: '12px',
base: '14px',
lg: '16px',
xl: '18px',
'2xl': '20px',
'3xl': '22px',
'4xl': '24px',
'5xl': '26px',
'6xl': '28px',
'7xl': '30px',
},
},
variants: {
// 移除响应式版本 https://www.tailwindcss.cn/docs/optimizing-for-production#-7
appearance: [],
extend: {},
},
plugins: [],
};
使用时在组件中开箱即用,不需要import
export default function Header() {
return <div>
<div className="relative top-4 bg-red-300"></div>
</div>
}
用tailwind编写class
.header {
@apply relative top-4 bg-blue-300
}
在tsx中引入
import './index.css'
export default function Header() {
return <div>
<div classNames="header">header</div>
</div>
}
复用class中的类
.header {
@apply relative top-4 bg-blue-300
}
.header {
@apply header;
@apply mt-3;
@apply bg-red-300;
}
在tsx中引入
import './index.css'
export default function Header() {
return <div>
<div classNames="header">header</div>
<div classNames="header1">header1</div>
</div>
}
typewind支持类型安全地使用tailwind,并且是零运行时
如果使用monorepo,可以自定义typewind引入tailwind的配置文件路径
{
"typewind": {
"configPath": "./path/to/your/tailwind.config.cjs"
}
}
使用
import { tw } from 'typewind';
export default function Button() {
return (
<button className={tw.bg_blue_500.text_white.rounded.py_3.px_4}>
Click Me
</button>
);
}
// 输出
// <button className="bg-blue-500 text-white rounded py-3 px-4">Click Me</button>
Windi CSS 是下一代工具优先的 CSS 框架。
如果你已经熟悉了 Tailwind CSS,可以把 Windi CSS 看作是按需供应的 Tailwind 替代方案,它为你提供了更快的加载体验,完美兼容 Tailwind v2.0,并且拥有很多额外的酷炫功能。
import React from 'react'
import tw from 'twin.macro'
import { Button, Logo } from './components'
const styles = {
// Move long class sets out of jsx to keep it scannable
container: ({ hasBackground }) => [
tw`flex flex-col items-center justify-center h-screen`,
hasBackground && tw`bg-gradient-to-b from-electric to-ribbon`,
],
}
const App = () => (
<div css={styles.container({ hasBackground: true })}>
<div tw="flex flex-col justify-center h-full gap-y-5">
<Button variant="primary">Submit</Button>
<Button variant="secondary">Cancel</Button>
<Button isSmall>Close</Button>
</div>
<Logo />
</div>
)
export default App
也可以直接
PurgeCSS 是一个用来删除未使用的 CSS 代码的工具。可以将它作为你的开发流程中的一个环节。 当你构建一个网站时,你可能会决定使用一个 CSS 框架,例如 TailwindCSS、Bootstrap、MaterializeCSS、Foundation 等,但是,你所用到的也只是框架的一小部分而已,大量 CSS 样式并未被使用。
接下来就该 PurgeCSS 上场了。PurgeCSS 通过分析你的内容和 CSS 文件,首先它将 CSS 文件中使用的选择器与内容文件中的选择器进行匹配,然后它会从 CSS 中删除未使用的选择器,从而生成更小的 CSS 文件。
安装
npm install purgecss --save-dev
使用
import PurgeCSS from 'purgecss';
const purgeCSSResults = await new PurgeCSS().purge({
content: ["**/*.html"],
css: ["**/*.css"],
});