在 Web 发展的这几年, 为了让 CSS 富有逻辑性,短板不那么严重,涌现出了 一些神奇的预处理语言。 它们让 CSS 彻底变成一门 可以使用 变量 、循环 、继承 、自定义方法等多种特性的标记语言,逻辑性得以大大增强。
一、预处理语言的诞生
其中 就我所知的有三门语言:Sass、Less 、Stylus 。
- Sass 诞生于 2007 年,Ruby 编写,其语法功能都十分全面,可以说 它完全把 CSS 变成了一门编程语言。另外 在国内外都很受欢迎,并且它的项目团队很是强大 ,是一款十分优秀的预处理语言。
- Less 诞生于 2009 年,受Sass的影响创建的一个开源项目。 它扩充了 CSS 语言,增加了诸如变量、混合(mixin)、函数等功能,让 CSS 更易维护、方便制作主题、扩充(引用于官网)。
- Stylus 诞生于 2010 年,来自 Node.js 社区,语法功能也和 Sass 不相伯仲,是一门十分独特的创新型语言。
二、选择预处理语言
这是一个十分纠结的问题。
- 在网上讨论看来,Sass 与 Stylus 相比于 Less 功能更为丰富,但对于学习成本以及适应时间 ,Less 稍胜一筹,这也是我选择 Less 的原因。
- Less 没有去掉任何 CSS 的功能,而是在现有的语法上,增添了许多额外的功能特性,所以学习 Less 是一件非常舒服的事情。
如果你之前没有接触过预处理语言,纠结应该学哪一个,不如先看看 下面 Less 的介绍,我相信你会爱上它的。
三、准备工作
1. 引入
1 2
| <link rel="stylesheet/less" href="style.less"> <script src="less.min.js"></script>
|
需要注意的是,link 标签一定要在 Less.js 之前引入,并且 link 标签的 rel 属性要设置为stylesheet/less
。
1 2
| <link rel="stylesheet/less" href="style.less"> <script src="less.min.js"></script>
|
2. npm安装
如果你也是 Webpack 的使用者,还需要配合 less-loader 进行处理
四、正文开始
1. 变量
我们常常在 CSS 中 看到同一个值重复多次,这样难易于代码维护。 理想状态,应是下面这样:
1 2 3 4 5
| const bgColor="skyblue";
$(".post-content").css("background-color",bgColor); $("#wrap").css("background-color",bgColor); $(".arctive").css("background-color",bgColor);
|
只要我们修改 bgColor这一个变量, 整个页面的背景颜色都会随之改变。
而 Less 中的变量十分强大,可化万物,值得一提的是,其变量是常量 ,所以只能定义一次,不能重复使用。
(1)值变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| /* Less */ @color: #999; @bgColor: skyblue; // 不要添加引号 @width: 50%;
#wrap { color: @color; background: @bgColor; width: @width; }
/* 生成后的 CSS */ #wrap { color: #999; background: skyblue; width: 50%; }
|
以 @ 开头 定义变量,并且使用时 直接 键入 @名称。
在平时工作中,我们就可以把 常用的变量 封装到一个文件中,这样利于代码组织维护。
1 2 3 4 5 6 7
| @lightPrimaryColor: #c5cae9; @textPrimaryColor: #fff; @accentColor: rgb(99, 137, 185); @primaryTextColor: #646464; @secondaryTextColor: #000; @dividerColor: #b6b6b6; @borderColor: #dadada;
|
(2)选择器变量:让选择器成为动态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| /* Less */ @mySelector: #wrap; @Wrap: wrap;
@{mySelector}{ // 变量名 必须使用大括号包裹 color: #999; width: 50%; } .@{Wrap}{ color:#ccc; } #@{Wrap}{ color:#666; }
/* 生成的 CSS */ #wrap{ color: #999; width: 50%; } .wrap{ color:#ccc; } #wrap{ color:#666; }
|
(3)属性变量: 可减少代码书写量
1 2 3 4 5 6 7 8 9 10 11 12
| /* Less */ @borderStyle: border-style; @Soild:solid; #wrap{ @{borderStyle}: @Soild;//变量名 必须使用大括号包裹 }
/* 生成的 CSS */ #wrap{ border-style:solid; }
|
(4)url变量
1 2 3 4 5 6 7 8 9 10 11
| /* Less */ @images: "../img"; // 需要加引号 body { background: url("@{images}/dog.png");//变量名 必须使用大括号包裹 }
/* 生成的 CSS */ body { background: url("../img/dog.png"); }
|
(5)声明变量
- 定义声明变量: @name: { 属性: 值 ;};
- 使用:@name();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| /* Less */ @background: { background:red; }; #main { @background(); } @Rules: { width: 200px; height: 200px; border: solid 1px red; }; #con { @Rules(); }
/* 生成的 CSS */ #main{ background:red; } #con{ width: 200px; height: 200px; border: solid 1px red; }
|
(6)变量运算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| /* Less */ @width:300px; @color:#222;
#wrap { width:@width-20; // 加减法时,以第一个数据的单位为准 height:@width-20*5; // 乘除法时,注意单位要统一 margin:(@width-20)*5; color:@color*2; background-color:@color + #111; }
/* 生成的 CSS */ #wrap{ width:280px; height:200px; margin:1400px; color:#444; background-color:#333; }
|
(7)变量作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| /* Less */ @var: @a; @a: 100%;
#wrap { // 就近原则 width: @var; @a: 9%; }
/* 生成的 CSS */ #wrap { width: 9%; }
|
(8)用变量去定义变量
1 2 3 4 5 6 7 8 9 10 11 12
| /* Less */ @fnord: "I am fnord."; @var: "fnord";
#wrap::after{ content: @@var; // 将@var替换为其值 content:@fnord; }
/* 生成的 CSS */ #wrap::after{ content: "I am fnord."; }
|
2、嵌套
(1)&:代表上一层选择器的名字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| /* Less */ #header{ &:after{ content:"Less is more!"; } .title{ font-weight:bold; } &_content{//理解方式:直接把 & 替换成 #header margin:20px; } } /* 生成的 CSS */ #header::after{ content:"Less is more!"; } #header .title{ //嵌套了 font-weight:bold; } #header_content{//没有嵌套! margin:20px; }
|
(2)媒体查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| // 以前的媒体查询要把一个元素分开写 #wrap { width:500px; } @media screen and (max-width:768px) { #wrap { width:100px; } }
// less /* Less */ #main { //something...
@media screen { @media (max-width:768px){ width:100px; } } @media tv { width:2000px; } }
/* 生成的 CSS */ @media screen and (maxwidth:768px){ #main{ width:100px; } } @media tv{ #main{ width:2000px; } }
|
3、混合方法
(1)无参数方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| /* Less */ .card { // 等价于 .card() . 与 # 皆可作为 方法前缀。 background: #f6f6f6; -webkit-box-shadow: 0 1px 2px rgba(151, 151, 151, .58); box-shadow: 0 1px 2px rgba(151, 151, 151, .58); } #wrap{ .card; // 等价于.card(); }
/* 生成的 CSS */ #wrap{ background: #f6f6f6; -webkit-box-shadow: 0 1px 2px rgba(151, 151, 151, .58); box-shadow: 0 1px 2px rgba(151, 151, 151, .58); }
// 其中 .card 与 .card() 是等价的。 个人建议,为了避免 代码混淆,应加上()
|
(2)默认参数方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| /* Less */ .border(@a:10px,@b:50px,@c:30px,@color:#000){ // 如果没传参就使用默认参数 border:solid 1px @color; box-shadow: @arguments; // 指代的是 全部参数,犹如 JS 中的 arguments } #main{ .border(0px,5px,30px,red); // 传参必须带着单位 } #wrap{ .border(0px); } #content{ .border;// 等价于 .border() }
/* 生成的 CSS */ #main{ border:solid 1px red; box-shadow:0px,5px,30px,red; } #wrap{ border:solid 1px #000; box-shadow: 0px 50px 30px #000; } #content{ border:solid 1px #000; box-shadow: 10px 50px 30px #000; }
|
(3)方法的匹配模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| /* Less */ .triangle(top,@width:20px,@color:#000){ border-color:transparent transparent @color transparent ; } .triangle(right,@width:20px,@color:#000){ border-color:transparent @color transparent transparent ; }
.triangle(bottom,@width:20px,@color:#000){ border-color:@color transparent transparent transparent ; } .triangle(left,@width:20px,@color:#000){ border-color:transparent transparent transparent @color; } .triangle(@_,@width:20px,@color:#000){ border-style: solid; border-width: @width; } #main{ .triangle(left, 50px, #999) // 第一个参数 left 要会找到方法中匹配程度最高的,如果匹配程度相同,将全部选择,并存在着样式覆盖替换。如果匹配的参数 是变量,则将会匹配,如 @_ }
/* 生成的 CSS */ #main{ border-color:transparent transparent transparent #999; border-style: solid; border-width: 50px; }
|
(4)方法的命名空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| /* Less */ #card(){ background: #723232; .d(@w:300px){ width: @w; #a(@h:300px){ height: @h;//可以使用上一层传进来的方法 } } } #wrap{ // 在 CSS 中> 选择器,选择的是 儿子元素,就是 必须与父元素 有直接血源的元素 #card > .d > #a(100px); // 父元素不能加 括号 } #main{ #card .d(); } #con{ // 不得单独使用命名空间的方法 // .d() 如果前面没有引入命名空间 #card ,将会报错 #card; // 等价于 #card(); .d(20px); //必须先引入 #card }
/* 生成的 CSS */ #wrap { height:100px; } #main { width:300px; } #con { width:20px; }
|
(5)方法的条件筛选
Less 没有 if else,可是它有 when
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| /* Less */ #card{ // and 运算符 ,相当于 与运算 &&,必须条件全部符合才会执行 .border(@width,@color,@style) when (@width>100px) and(@color=#999){ border:@style @color @width; }
// not 运算符,相当于 非运算 !,条件为 不符合才会执行 .background(@color) when not (@color>=#222){ background:@color; }
// , 逗号分隔符:相当于 或运算 ||,只要有一个符合条件就会执行 .font(@size:20px) when (@size>50px) , (@size<100px){ font-size: @size; } } #main { #card>.border(200px,#999,solid); #card .background(#111); #card > .font(40px); }
/* 生成后的 CSS */ #main{ border:solid #999 200px; background:#111; font-size:40px; }
|
(6)数量不定的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| /* Less */ .boxShadow(...){ // ... ,犹如 ES6 的扩展运算符 box-shadow: @arguments; } .textShadow(@a,...){ text-shadow: @arguments; } #main{ .boxShadow(1px,4px,30px,red); .textShadow(1px,4px,30px,red); }
/* 生成后的 CSS */ #main{ box-shadow: 1px 4px 30px red; text-shadow: 1px 4px 30px red; }
|
(7)方法使用 important!
1 2 3 4 5 6 7 8 9 10 11 12 13
| /* Less */ .border{ border: solid 1px red; margin: 50px; } #main{ .border() !important; } /* 生成后的 CSS */ #main { border: solid 1px red !important; margin: 50px !important; }
|
(8)循环方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| /* Less */ .generate-columns(4);
// Less并没有直接提供for循环功能,但是可以通过递归实现 .generate-columns(@n, @i: 1) when (@i =< @n) { .column-@{i} { width: (@i * 100% / @n); } .generate-columns(@n, (@i + 1)); }
/* 生成后的 CSS */ .column-1 { width: 25%; } .column-2 { width: 50%; } .column-3 { width: 75%; } .column-4 { width: 100%; }
|
(9)属性拼接方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| // +_ 代表的是 空格;+ 代表的是 逗号。
// 1. 逗号 .boxShadow() { box-shadow+: inset 0 0 10px #555; } .main { .boxShadow(); box-shadow+: 0 0 20px black; } /* 生成后的 CSS */ .main { box-shadow: inset 0 0 10px #555, 0 0 20px black; }
// 2. 空格 .Animation() { transform+_: scale(2); } .main { .Animation(); transform+_: rotate(15deg); }
/* 生成的 CSS */ .main { transform: scale(2) rotate(15deg); }
|
(10)官网Demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /* Less */ .average(@x, @y) { @average: ((@x + @y) / 2); }
div { .average(16px, 50px); // 调用 方法 padding: @average; // 使用返回值 }
/* 生成的 CSS */ div { padding: 33px; }
|
4、继承
extend 是 Less 的一个伪类。它可继承 所匹配声明中的全部样式。
(1)extend关键字的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| /* Less */ .animation{ transition: all .3s ease-out; .hide{ transform:scale(0); } } #main{ &:extend(.animation); } #con{ &:extend(.animation .hide); }
/* 生成后的 CSS */ .animation,#main{ transition: all .3s ease-out; } .animation .hide , #con{ transform:scale(0); }
|
(2)all全局搜索替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| /* Less */ #main{ width: 200px; } #main { &:after { content:"Less is good!"; } } #wrap:extend(#main all) {}
/* 生成的 CSS */ #main,#wrap{ width: 200px; } #main:after, #wrap:after { content: "Less is good!"; }
|
(3)减少代码重复性(方法与extend对比)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| // 从表面 看来,extend 与 方法 最大的差别,就是 extend 是同个选择器共用同一个声明,而 方法 是使用自己的声明,这无疑 增加了代码的重复性。
// 方法示例 与上面的 extend 进行对比: .Method{ width: 200px; &:after { content:"Less is good!"; } } #main{ .Method; } #wrap{ .Method; }
/* 生成的 CSS */ #main{ width: 200px; &:after{ content:"Less is good!"; } } #wrap{ width: 200px; &:after{ content:"Less is good!"; } }
|
(4)导入
1 2 3 4 5 6 7 8 9 10
| // 1. 导入 less 文件 可省略后缀 import "main"; // 等价于 import "main.less";
2. @import 的位置可随意放置 #main{ font-size:15px; } @import "style";
|
(5)reference
1 2 3 4
| // 使用@import (reference)导入外部文件,但不会添加 把导入的文件 编译到最终输出中,只引用。 @import (reference) "bootstrap.less";
#wrap:extend(.navbar all){}
|
(6)once
1 2 3
| // @import语句的默认行为。这表明相同的文件只会被导入一次,而随后的导入文件的重复代码都不会解析。 @import (once) "foo.less"; @import (once) "foo.less"; // this statement will be ignored
|
(7)multiple
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // 使用@import (multiple)允许导入多个同名文件。 // file: foo.less .a { color: green; } // file: main.less @import (multiple) "foo.less"; @import (multiple) "foo.less";
/* 生成后的 CSS */ .a { color: green; } .a { color: green; }
|
5、函数
(1)判断类型
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 1. isnumber 判断是否是数字 isnumber(#ff0); // false isnumber(blue); // false isnumber("string"); // false isnumber(1234); // true isnumber(56px); // true isnumber(7.8%); // true isnumber(keyword); // false isnumber(url(...)); // false
// 2. iscolor 判断给定的值 是否 是一个颜色。
// 3. isurl 判断给定的值 是否 是一个 url 。
|
(2)颜色操作
1 2 3 4 5
| * saturate: 增加一定数值的颜色饱和度。 * lighten:增加一定数值的颜色亮度。 * darken:降低一定数值的颜色亮度。 * fade:给颜色设定一定数值的透明度。 * mix:根据比例混合两种颜色。
|
(3)数字函数
1 2 3 4 5 6 7
| * ceil:向上取整。 * floor:向下取整。 * percentage:将浮点数转换为百分比字符串。 * round:四舍五入。 * sqrt:计算一个数的平方根。 * abs:计算数字的绝对值,原样保持单位。 * pow:计算一个数的乘方。
|
6、其他
(1)注释
1 2
| * /* */ CSS原生注释,会被编译在 CSS 文件中。 * / / Less提供的一种注释,不会被编译在 CSS 文件中。
|
(2) 避免编译
1 2 3 4 5 6 7 8 9 10
| /* Less */ #main{ width:~'calc(300px-30px)'; }
/* 生成后的 CSS */ #main{ width:calc(300px-30px); }
|
(3) 变量拼串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| .judge(@i) when(@i=1){ @size:15px; } .judge(@i) when(@i>1){ @size:16px; } .loopAnimation(@i) when (@i<16) { .circle:nth-child(@{i}){ .judeg(@i); border-radius:@size @size 0 0; animation: ~"circle-@{i}" @duration infinite @ease; transition-delay:~"@{i}ms"; } @keyframes ~"circle-@{i}" { // do something... } .loopAnimation(@i + 1); }
|
(4)使用 JS
因为 Less 是由 JS 编写,所以 Less 有一得天独厚的特性:代码中使用 Javascript 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| /* Less */ @content:`"aaa".toUpperCase()`; #randomColor{ @randomColor: ~"rgb(`Math.round(Math.random() * 256)`,`Math.round(Math.random() * 256)`,`Math.round(Math.random() * 256)`)"; } #wrap{ width: ~"`Math.round(Math.random() * 100)`px"; &:after{ content:@content; } height: ~"`window.innerHeight`px"; alert:~"`alert(1)`"; #randomColor(); background-color: @randomColor; }
/* 生成后的 CSS */ // 弹出 1 #wrap{ width: 随机值(0~100)px; height: 743px;//由电脑而异 background: 随机颜色; } #wrap::after{ content:"AAA"; }
|
7、结束语
前几个月 , 有个 CSS in JS 的概念非常火,现在 看来 JS in CSS 也未曾不可。 我觉得完全可以根据 Less 这个特性来造个轮子,JS来控制 CSS ,形成 动态属性,如果成功 很可能会改变 现在前端的打开姿势。
从我学习 Web 以来,无数前辈告诉我,Web 有三大基石,JS 控制行为,HTML 控制结构,CSS 控制样式。我一直再想 为什么要分为 3 个语言?为什么不能合成一个语言?或者说 就没有更合适的吗?在这几年的 Web 发展中,页面从 MVC 到 现在的 MVVC,结构正在发生着改变,但 其根本 不过是造了一个又一个 完美的轮子。我们为什么就不能打破传统呢?让 前端的 三大基石 变成一座大山呢 ? 请试想一下,那该是个怎样的世界。