css还可以这么写?

作为一个前端,毫无疑问css肯定是最基础的一项技能之一。css是一个标记语言,没有编程语言的诸多特性,比如变量定义,复用,嵌套等,所以相应的开发效率也受到限制。
在追求效率和自动化的当下,涌现了一批解决方案,像是css预编译语言Less, Sass等,解决css命名冲突的css-modules,react中css的最佳实践styled-components等。

本篇文章不在于探讨css的技巧学习,而在于讨论css的这些提升开发效率的方案。

Less

Less, Sass, Styluscss预编译语言,给css赋予了编程特性。拿 Less 来说,它扩展了 CSS 语言,增加了变量、Extend、Mixin、函数等特性,也支持import导入文件,使 CSS 更易维护和扩展。本篇简单介绍一下Less的一些特性,详细的教程可以上Less官网查看。

怎么样使用Less?

我们可以在 命令行 直接使用less,也可以通过 node api 去使用less,或者通过 webpackgulpgrunt等的 less插件 去使用,甚至可以在浏览器端使用,非常灵活。这里简单说一下在命令行中使用less。

$ npm i less -g
# 当less被安装之后,就可以使用全局命令lessc
$ lessc bootstrap.less bootstrap.css

一、变量

变量使css代码更易维护。

比如有个主色 #ef8376,在整个样式表中,我们有多处使用这个颜色。如果主色变动的话,比如主色要变成 #000,我们就要手动去全局替换这个变量,而有一些 #ef8376我们却不希望替换掉,这样就造成了极大的困扰。

如果我们使用less的话,就可以这么写:

@primaryColor: #ef8376;

.banner {
  background-color: @primaryColor;
  .text {
    color: @primaryColor;
    border-color: #ef8376;
  }
}

我们要修改主色,只需要将 @primaryColor 修改为 '#000'即可。

二、Extend

Extend让我们可以用伪类的写法去合并一些类

比如:

nav ul {
  &:extend(.inline);
  background: blue;
}
.inline {
  color: red;
}

会编译成:

nav ul {
  background: blue;
}
.inline,
nav ul {
  color: red;
}

三、Mixin

Mixin既有Extend继承已有类的特性,也有其他高级的特性,比如支持变量,支持像使用方法一样使用mixin

支持变量

.foo (@bg, @color: '#000') {
  background: @bg;
  color: @color;
}
.unimportant {
  .foo(#f5f5f5);
}
.important {
  .foo(#121212) !important;
}

会编译成:

.unimportant {
  background: #f5f5f5;
  color: #000;
}
.important {
  background: #121212 !important;
  color: #000 !important;
}

像方法一样使用Mixin

.count(@x, @y) {
  @margin: ((@x + @y) / 2);
  @padding: ((@x + @y) / 4)
}

div {
  margin: .count(16px, 16px)[@margin];
  padding: .count(16px, 16px)[@padding];
}

.loop(@counter) when (@counter > 0) {
  .loop((@counter - 1));    // next iteration
  width: (10px * @counter); // code for each iteration
}

.text {
  .loop(5); // launch the loop
}

会编译成:

div {
  margin: 16px;
  padding: 8px;
}

.text {
  width: 10px;
  width: 20px;
  width: 30px;
  width: 40px;
  width: 50px;
}

四、Import导入文件

// head.less
.banner {
  background-color: red;
}
// footer.css
.footer {
  background-color: green;
}
@import './head.less';
@import css './footer.css';

会编译成:

.banner {
  background-color: red;
}
.footer {
  background-color: green;
}

五、方法

Less支持一些常用的辅助方法

比如darkenlighten用来加深或淡化颜色。

body {
  background-color: darken(hsl(90, 80%, 50%), 20%);
  color: lighten(hsl(90, 80%, 50%), 20%);
}

会编译成:

body {
  background-color: #4d8a0f;
  color: #b3f075;
}

css-modules

css-modules 相较于 Less 来说有所不同,css-modules 只是拓展了 css 的写法,解决了css的块作用域和全局作用域,而不是将css变成一门编程语言。

为什么需要 css-modules?

Css一直以来都有一个问题,就是css定义的类都是全局的,我们虽然可以通过不同的命名空间或是加前缀的方式去避免类的混淆和冲突,但是在写法上却不是那么的干净利落,而且一旦重构css的话,也会造成很大的困扰。

为了让我们能随意的写类名而不需要考虑冲突或是覆盖,css-modules 便出现了。

css-modules提供了 块作用域 :local 和 全局作用域 :global,这两个特性就能很好的避免css的命名冲突。

怎么使用?

首先来说一下怎么使用 css-modules

当我们在使用webpack的时候,最简单的用法是通过 css-loader 来开启对 css-modules 的支持。如下:

{
  test: /\.css$/,
  use: [
    {
      loader: 'css-loader',
      options: {
        modules: true, // 开启对css-modules的支持
        localIdentName: '[name]__[local]___[hash:base64:5]' // 生成的类名的格式
      }
    }
  ]
}

同时可以配合less-loaderpostcss使用。注意:在结合less-loader的时候可能出现对url的兼容问题。见:https://github.com/webpack-contrib/less-loader/issues/109#issuecomment-253797335 。而且 less-loader 的维护者认为结合 less-loadercss-modules没什么必要。。

一、作用域

css-modules提供了两个关键字,:local:global

比如这种写法:

// App.css
:local(.banner) {
  background: red;
}
:local(.banner .text) {
  color: yellow;
}
.center {
  color: green;
}
:global(.global-class-name) {
  color: blue;
}

会编译成:

.App__banner___3NbRo {
  background: red;
}
.App__banner___3NbRo .App__text___2j1Ht {
  color: yellow;
}
.App__center___3eDJo {
  background: green;
}
.global-class-name {
  color: blue;
}

:global 声明的类不会被编译,会保持不变。

同时,我们在js中引入css,写法如下:

/**
 * styles是什么呢?styles其实是一个经过处理过的类名的集合。
 * 
 * 比如上边这个css文件,处理后的style对象是这样的:
 * 
 * {
 *   banner: 'App__banner___3NbRo',
 *   text: 'App__banner___3NbRo App__text___2j1Ht',
 *   center: 'App__center___3eDJo'
 * }
 * 
 * 这样我们就可以理解为什么css-modules可以避免明明冲突了。
 * 命名都按照我们设置的hash规则重写了,保证了类名的唯一,并且在生成的html结构里也进行了替换,还何来冲突?
 */
import styles from './App.css';
import React from 'react';

const html = () => {
  return <div class={styles.banner}>
    <span class={style.text}>HAHAHAHHAHAHA</span>
  </div>;
};

export default html;

二、Composition - 混合组成

css-modules支持多个类的混合组成。比如:

.colorRed {
  color: red
}

.text {
  composes: colorRed;
  background: #000;
}

会编译成:

.App__colorRed___yoG_f {
  color: red
}

.App__text___2j1Ht {
  background: #000;
}

可以看到,生成的css中并没有任何的变化,那这个composes做了什么呢?其实在通过js引用的对象内发生了变化。如下:

{
  "colorRed": "App__colorRed___yoG_f",
  "text": "App__text___2j1Ht App__colorRed___yoG_f"
}

那么在通过 styles.text 使用 text 类的时候,其实也同时使用了 colorRed 类,达到了混合组成的效果。

三、Import - 引用

css-modules 支持引用其他文件的类。

比如:

// green.css
.green {
  color: green;
}
// text.css
.text {
  background-color: red;
  composes: green from './green.css';
}

会编译成:

.green__green___1v20L {
  color: green;
}
.text__text__2jfs0 {
  background-color: red;
}

其实跟 一样,生成的css并没有什么改动,其实改变的是生成js对象的内容:

import styles from './text.css';

// styles = {green: 'green__green___1v20L', text: 'text__text__2jfs0 green__green___1v20L'}

styled-components

styled-components, 可能是React中css的最佳实践了,如果你喜欢,你也可以叫它styled-react-components : )。想象一下,像写react组件一样去写css,是一种什么样的体验?

如下,你可以这样来写样式:

import React from 'react';
import styled from 'styled-components';

const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

export default () => <Wrapper>
  <Title>Hello World, this is my first styled component!</Title>
</Wrapper>;

styled-components会自动帮你在 运行时 生成一个样式表,插入到 <head> 下的 <style> 标签中,比如上边的代码,会在运行是生成如下代码:

<head>
  <style data-styled-components>
    /* sc-component-id: model__Title-cooNNd */
    .model__Title-cooNNd {} .jHitSF{font-size:1.5em;text-align:center;color:palevioletred;}
    /* sc-component-id: model__Wrapper-bEJrHK */
    .model__Wrapper-bEJrHK {} .ipFfju{padding:4em;background:papayawhip;}
  </style>
</head>
<body>
  <section class="model__Wrapper-bEJrHK ipFfju">
    <h1 class="model__Title-cooNNd jHitSF">Hello World, this is my first styled component!</h1>
  </section>
</body>

我们可以看到,我们在js中写的样式,被插入到了 <style>中,并且生成了一个随机的类名,而且这个类名,也是被 react-dom 生成的DOM结构所引用。

受益于 styled-components,我们贯彻了 react万物皆组件 的思想,使我们在css的组件化上又推进了一步(发布一个纯css组件试试?) : )

在这篇文章里,我会简单探讨一下 style-components 的用法和特性。

如何使用?

styled-components 一般配合着 react 使用,当然也支持 vuevue-styled-components)。抛开这两个来说,你也可以直接在原生js下使用:

<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>

我们这里讲配合 react 的用法。

一、首先,安装依赖

$ npm i styled-components
# 配合着babel来使用
$ npm i -D babel-plugin-styled-components

二、配置 .babelrc (当然,我们需要安装 webpack ,配置webpack的config,并且需要需要安装 babel-preset-envbabel-preset-react,这里不赘述)

{
  "presets": ["env", "react"],
  "plugins": ["styled-components"]
}

经过以上简单的配置之后,就可以在项目中使用 styled-components 了。

工具

当然,现在的 styled-components 也是支持了 stylelintjest,所以,你也不用担心样式检查和测试了 :)

下边儿说一下 styled-components 的一些用法和特性。 官方文档在这儿: https://www.styled-components.com/docs/basics

一、动态样式赋值

你可以传props给组件,让组件根据所传的props的值动态改变样式。

const Button = styled.button`
  /* 根据props的值动态改变样式的值 */
  background: ${props => props.primary ? 'palevioletred' : 'white'};
  color: ${props => props.primary ? 'white' : 'palevioletred'};
`;

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;
`;
// 创建一个新Button组件,继承自Button,并对Button进行样式添加和覆盖
const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

render(
  <div>
    <Button>Normal Button</Button>
    <TomatoButton>Tomato Button</TomatoButton>
  </div>
);

三、组件标签替换

比如,你创建了一个Button组件,你想把button标签变成a标签,但是样式还是button的样式。那么你可以通过 withComponent 方法轻松做到。

const Button = styled.button`
  display: inline-block;
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

// 把<button>标签替换成<a>标签
const Link = Button.withComponent('a')

// 继承Link组件
const TomatoLink = styled(Link)`
  color: tomato;
  border-color: tomato;
`;

render(
  <div>
    <Button>Normal Button</Button>
    <Link>Normal Link</Link>
    <TomatoLink>Tomato Link</TomatoLink>
  </div>
);

四、动画

// 这个keyframe会随机生成一个name
const rotate360 = keyframes`
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
`;

const Rotate = styled.div`
  display: inline-block;
  animation: ${rotate360} 2s linear infinite;
  padding: 2rem 1rem;
  font-size: 1.2rem;
`;

render(
  <Rotate>&lt; 💅 &gt;</Rotate>
);

五、Media Query

const Content = styled.div`
  background: papayawhip;
  height: 3em;
  width: 3em;

  @media (max-width: 700px) {
    background: palevioletred;
  }
`;

render(
  <Content />
);

六、嵌套写法

styled-components支持嵌套写法,这个特性是从 Sass 移植过来的。

const EqualDivider = styled.div`
  display: flex;
  margin: 0.5rem;
  padding: 1rem;
  background: papayawhip;
  ${props => props.vertical && 'flex-direction: column;'}

  > * {
    flex: 1;

    &:not(:first-child) {
      ${props => props.vertical ? 'margin-top' : 'margin-left'}: 1rem;
    }
  }
`;

const Child = styled.div`
  padding: 0.25rem 0.5rem;
  background: palevioletred;
`;

render(
  <div>
  <EqualDivider>
    <Child>First</Child>
    <Child>Second</Child>
    <Child>Third</Child>
  </EqualDivider>
  <EqualDivider vertical>
    <Child>First</Child>
    <Child>Second</Child>
    <Child>Third</Child>
  </EqualDivider>
  </div>
);

七、配合其他css类库使用

比如你在项目中引入了 bootstrap.css,应该怎么和bootstrap中的类配合使用呢?

const Button = styled.button.attrs({
  // 生成的classList中会包含small
  className: 'small'
})`
  background: black;
`;

render(
  <div>
    <Button>Styled Components</Button>
    <Button>The new way to style components!</Button>
  </div>
);

八、优先级

怎么样覆盖高优先级的样式呢?当然我们可以通过 !important来做,不过 styled-components 更推荐下边这种做法:

const MyStyledComponent = styled(AlreadyStyledComponent)`
  &&& {
    color: palevioletred;
    font-weight: bold;
  }
`;

每个 & 替换为生成的类,那么生成的CSS是这样的:

.MyStyledComponent-asdf123.MyStyledComponent-asdf123.MyStyledComponent-asdf123 {
  color: palevioletred;
  font-weight: bold;
}

那么怎么覆盖内联样式呢?如下:

const MyStyledComponent = styled(InlineStyledComponent)`
  &[style] {
    font-size: 12px !important;
    color: blue !important;
  }
`;

styled-components 颠覆了传统的样式写法,像写组件一样写css,配合 react 恰到好处 :)

至于在 Lesscss-modulesstyled-components 中到底选择哪一个,就要看你的应用场景和需求了。

本章完

我的Github: https://github.com/PengJiyuan

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容