「LESS」详解 - 入门到精通
# 前言
学习less之前,个人并不是很喜欢用预编译语言,自认为CSS
功底够扎实。无奈在这里自己碰了次壁,某类人认为这是技能水平的一个衡量标准,把鼻子都给碰扁了,就要知道疼。一直认为LESS没啥用,其实不然!
看个例子,能写出它的编译后结果吗?如果是你,你能写出这样的生成器吗?不能的话,就好好看看本文吧。不吹,有用! 答案在文中。
.generate-columns(4);
.generate-columns(@n, @i: 1) when (@i =< @n) {
.column-@{i} {
width: (@i * 100% / @n);
}
.generate-columns(@n, (@i + 1));
}
# 啥是 LESS?
LESS (Leaner Style Sheets 的缩写),是一款向后兼容的CSS 预编译语言,与css 很像,学习成本不高。less对CSS增加了少许方便的扩展。当然,因为less只是预编译语言,浏览器并不识别,因此,需要 less.min.js文件将less样式转化成css样式
# 变量 Variables
1. 变量修改值
【格式】 @变量名: 值
与编程语言类似,LESS可以定义变量,且变量可以参与计算,也可以被其他地方所调用。 它大大的提高了代码的可维护性,对于整个模块的样式,或许只需要修改变量所代表的值,就能修改整体页面的视效。
// Variables
@link-blue: #428bca;
@link-color-hover: darken(@link-color, 10%);
@light-blue: @link-blue + #111111;
// Usage
a, link {
color: @link-blue;
}
a:hover {
color: @link-color-hover;
}
.header {
color: @light-blue;
}
编译结果:
a, link {
color: #428bca;
}
a:hover {
color: #36a1d2;
}
.header {
color: #539cda;
}
【注释】1. 你可以 点击这里 学习更多类似 darken 的用法。
- 第二行设置了darken减少20%的透明度。
- 第三行色值相加处理,色值计算规则按照十六进制按位相加执行。
2. 用于插值
上述用法针对于在CSS规则中使用变量控制值,除此之外,还可以应用到别的地方,比如说控制选择器名
,属性名
,URLs
和 @import
声明。
说明:在该模块学习中,发现有些地方使用了{},有些地方又没有。到底如何使用? 其实很简单,当变量作为值直使用时不需要加{},当变量作为修改选择器名,属性名等以窜改;添写,插补身份起作用时,则需要加{}。结合案例来看看怎么处理:
(1). 修改选择器
// Variables
@my-selector: banner;
// Usage
.@{my-selector} {
font-size: 24px;
编译结果
.banner {
font-size: 24px;
}
(2). 修改URLs
// Variables
@images: "../img";
// Usage
body {
color: #444444;
background: url("@{images}/white-sand.png");
}
编译结果
body {
color: #444444;
background: url("../img/white-sand.png");
}
(3). import 声明
【语法】@import "@{themes}/tidal-wave.less"
在含有@import
的文件中调用某个变量,该变量会被在当前文件作用域和被调用文件作用域中查找变量的值。
//Variables
@themes: '../../src /theme';
// Usage
@import "@{themes}/tidal-wave.less";
(4). 修改属性
变量的强大功能还为我们提供了修改所要属性的能力,有了这个功能,我们甚至能随意设置我们所要的属性名。
// Variables
@prop: color
// Usage
body {
@{prop}: #0ee;
background-@{prop}: #999;
}
编译结果
body {
color: #0ee;
background-color:#999;
}
3. 双层变量控制
在less中,可以用一个变量去定义另一个变量名
@primary: green;
@secondary: blue;
.section {
@color: primary;
.element {
color: @@color;
}
}
编译结果
.section .element {
color:green;
}
4. 作用域 (懒加载)
变量在被使用前是不会被赋值的,来看一个作用域优先级调用问题:
.lazy-eval {
width: @var;
}
@var: @a;
@a: 9%;
假如在子作用域中也含有@a变量,即
.lazy-eval {
width: @var;
@a: 9%;
}
@var: @a;
@a: 100%;
编译结果如下:
.lazy-eval {
width: 9%;
}
子作用域中含有a变量,父作用域中也含有a变量,但因子作用域优先级高,因此优先加载子作用域。这导致父作用域的变量a未被加载。由于懒加载规则,他也不会被加载。但是,当某个变量在相同优先级作用域下被定义两次时,最后一个定义将生效覆盖前一个,此时两个变量都被加载。来看下边这个例子:
@var: 0;
.class {
@var: 1;
.brass {
@var: 2;
three: @var;
@var: 3;
}
one: @var;
}
很经典的例子,注意one
和three
的值。less编译器并不是阅读一句css就编译一句,而是将整个块都构建成渲染树,再编译出最终结果。
编译结果:
.class {
one: 1;
}
.class .brass {
three: 3;
}
5. 属性作为变量使用
【语法】$prop
将属性作为变量使用,也能减少代码量
.content {
color: #efefef;
backgroung-color: $color;
}
编译结果
.content {
color: #efefef;
background-color: #efefef;
}
【注意】该用法同样有作用域和多次定义优先级问题。如下代码
.block {
color: red;
.content {
background-color: $color;
}
color: blue;
}
编译结果
.block {
color: red;
color: blue;
}
.block .content {
background-color: blue;
}
注意这里的background-color
的值是blue
不是 red
。
6. 构建一个公用的默认样式(变量) LESS 文件
既然LESS为css提供了变量功能,我们也就能像javascript一样为css写一个"配置文件
",该文件主要存放我们的可重用样式,避免相似组件样式的大量重复编写。
// library
@base-color: green;
@dark-color: darken(@base-color: 10%);
// use of library
@import "library.less";
@base-color: red;
【说明】虽然引入了可能较大的library.less
文件,包含很多别的组件样式代码,但你不必担心,因为懒加载的特性,多余的代码并不会被加载编译,最终编译得到的.css
文件就是我们想要的那部分代码。
# 父选择器的使用
【符号】&
- 父类的别名
父选择器经常使用在伪类
或修饰类
上,个人认为这是个预设的宏定义变量
a: {
color: blue;
&:hover {
color: green;
}
}
编译结果
a {
color: blue;
}
a:hover {
color: green
}
【说明】在该例中,如果没有使用&
符号,得到结果是 a :hover
即 :hover
是 a
的子类。加了之后,:hover
是a
的修饰类。
有了&
之后,我们可以对变量做一些简写,例如:
.button {
&-ok {
background-image: url("ok.png");
}
&-cancel {
background-image: url("cancel.png");
}
&-custom {
background-image: url("custom.png");
}
}
编译结果
.button-ok {
background-image: url("ok.png");
}
.button-cancel {
background-image: url("cancel.png");
}
.button-custom {
background-image: url("custom.png");
}
&
就是以字符串形式被插入到类名中,类似宏定义的效果。当然这里我们也可以用${variable}形式实现该功能。
(1). 同时使用多个&
既然可以随意替换字符串,当然也可以一次性使用多个&符号。
.link {
& + & {
color: red;
}
& & {
color: green;
}
&& {
color: blue;
}
&, &ish {
color: cyan;
}
}
编译结果
.link + .link {
color: red;
}
.link .link {
color: green;
}
.link.link {
color: blue;
}
.link, .linkish {
color: cyan;
}
【特别注意】&
代表的是所有的父元素,而不是最近的父元素,但对于后面拼接字符串的情况,则只加在最近的一个父元素上,如下:
.grand {
.parent {
& > & {
color: red;
}
& & {
color: green;
}
&& {
color: blue;
}
&, &ish {
color: cyan;
}
}
}
编译结果为:
.grand .parent > .grand .parent {
color: red;
}
.grand .parent .grand .parent {
color: green;
}
.grand .parent.grand .parent {
color: blue;
}
.grand .parent, .grand .parentish {
color: cyan;
}
(2). 修改调用层级关系
其实这里形容的并不贴切,并不是真正的修改了层级关系,而是 生成这种调用关系而已。
.header {
.menu {
border-radius: 5px;
.no-borderradius & {
background-image: url('images/button-background.png');
}
}
}
该例中,在.no-borderradius
之后拼接了&
,从而形成了修改层级关系的感觉。编译结果
.header .menu {
border-radius: 5px;
}
.no-borderradius .header .menu {
background-image: url('images/button-background.png');
}
(3). & 的排列组合用法
&符号对多选择器名的样式在编译时能进行排列组合
p, a, ul, li {
border-top: 2px dotted #366;
& + & {
border-top: 0;
}
}
编译结果
p, a, ul, li {
border-top: 2px dotted #366;
}
p + p,
p + a,
p + ul,
p + li,
a + p,
a + a,
a + ul,
a + li,
ul + p,
ul + a,
ul + ul,
ul + li,
li + p,
li + a,
li + ul,
li + li {
border-top: 0;
}
# 样式追加 / 继承
【语法】&:extend
LESS 支持样式追加,即将&:extend()
中的样式加载到自身类下,注意,被追加的是自身,而不是extend
中的类
【用法】(1) 基础继承
.a:extend(.b) {}
// 写法等同于
.a {
&:extend(.b);
}
(2)还能继承包含某个类名的所有样式
.c:extend(.d all) {
// 继承所有包含.d 类的样式,如 ".x.d" 和 ".d.x"
}
.c:extend(.d) {
// 只继承 .d 类的样式
}
(3)同时继承多个类
.e:extend(.f) {}
.e:extend(.g) {}
// 写法等同于
.e:extend(.f, .g) {}
【实例】
nav ul {
&:extend(.inline);
background: blue;
}
.inline {
color: red;
}
编译结果
nav ul {
background: blue;
}
.inline,
nav ul {
color: red;
}
# 合并 Merge 与 混入Mixin
1. 值的合并与混入
合并是将两个类相同属性的两个值合并到要合并类的同名属性中;而混入则是将一个类中的属性及样式都加入到要混入的类中
(1)逗号合并 +
.mixin() { // 加入圆括号的类不会被编译到css文件中
box-shadow+: inset 0 0 10px #555;
}
.myclass {
.mixin();
box-shadow+: 0 0 20px black;
}
编译结果
.myclass {
box-shadow: inset 0 0 10px #555, 0 0 20px black;
}
(2)空格合并 +_
.mixin() {
transform+_: scale(2);
}
.myclass {
.mixin();
transform+_: rotate(15deg);
}
编译结果
.myclass {
transform: scale(2) rotate(15deg);
}
(3)自身参与编译的混入类: 类不带圆括号
混入写法: 直接将类名写到混入类中
.a, #b {
color: red;
}
.mixin-class {
.a();
}
.mixin-id {
#b();
}
编译结果
.a, #b {
color: red;
}
.mixin-class {
color: red;
}
.mixin-id {
color: red;
}
(4) 自身不参与编译的混入类: 类带圆括号
.my-mixin {
color: black;
}
.my-other-mixin() {
background: white;
}
.class {
.my-mixin;
.my-other-mixin;
}
编译结果
.my-mixin {
color: black;
}
.class {
color: black;
background: white;
}
2. 选择器的混入
less不仅可以做值混入,选择器也可以做混入,例如伪类或修饰类的混入
.my-hover-mixin() {
&:hover {
border: 1px solid red;
}
}
button {
.my-hover-mixin();
}
编译结果
button:hover {
border: 1px solid red;
}
3. 命名空间 Namespace
命名空间的用法并不专属于混入,而是less一种共有的使用方法,这里以混入为例介绍该用法。
(1)基础用法
如果我们只想混入一个复杂的less选择器样式中的一个模块,则可以使用类堆叠的方式及命名空间方式引入,对id选择器和类选择器的使用没有要求。如下:
#outer {
.inner {
color: red;
}
}
.t1 {
#outer > .inner;
}
.t2 {
#outer >.inner();
}
.t3 {
#outer .inner();
}
采用命名空间的办法,我们能得到该空间下的样式,而不会被其他资源样式所干扰。
(2)带条件的命名空间
如果一个命名空间带有条件,则仅当条件结果返回为真时混入样式才会生效。有条件的命名空间有两种用法,其效果相同,如下:
// Variable
@mode: huge;
// Usage
#namespace when (@mode=huge) {
.mixin() { /* */ }
}
#namespace {
.mixin() when (@mode=huge) { /* */ }
}
(3)default关键字
在嵌套的命名空间和混入中,所有的default
关键字的返回结果都相同,以下的样式将永远都不会被编译,因为至少有一个条件将返回false
#sp_1 when (default()) {
#sp_2 when (default()) {
.mixin() when not(default()) { /* */ }
}
}
4. !important 关键字
在编写css时有时候会用到 !important
关键字,既然在css中有的需求,less中肯定也会有,要不怎么编译生成该需求的css文件呢。在less中,可以对单个属性增加!important
,也可以对整个类追加!important
关键字。
.foo (@bg: #f5f5f5; @color: #900) {
background: @bg;
color: @color;
}
.unimportant {
.foo();
}
.important {
.foo() !important;
}
编译结果
.unimportant {
background: #f5f5f5;
color: #900;
}
.important {
background:#f5f5f5 !important;
color:#900 !important;
}
5. 函数式混入
1. 向方程传递参数
mixin可以携带参数,通过参数形式将变量传递给内部属性。往往我们都是先定义好方程式,再在需要调用位置传递参数
// Defined
.border-radius(@radius) {
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
border-radius: @radius;
}
// Usage
#header {
.border-radius(4px);
}
.button {
.border-radius(6px);
}
编译结果
#header {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.button {
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
2. 为参数赋默认值
函数是混入的参数可以给定默认值,这样在调用时,如果没有给定参数,编译器将使用默认值作为变量的值使用。
.border-radius(@radius: 5px) {
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
border-radius: @radius;
}
// Usage
#header {
.border-radius;
}
编译结果
#header {
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
3. 函数式混入不带参行为
不带参行为其实在之前讲过,作用就是自己不参与编译,因此,我们可以写一个作为样式生成器的函数方程式,在要用的地方调用,但又不会参与编译输出。
// 生成器
.wrap() {
text-wrap: wrap;
white-space: -moz-pre-wrap;
white-space: pre-wrap;
word-wrap: break-word;
}
// Usage
pre { .wrap }
编译结果
pre {
text-wrap: wrap;
white-space: -moz-pre-wrap;
white-space: pre-wrap;
word-wrap: break-word;
}
4. 方程式具名参数和不具名参数顺序问题
不具名参数必须按位置所在顺序传递,具名参数可以随意顺序。建议保持好习惯,还是按顺序传参
.mixin(@color: black; @margin: 10px; @padding: 20px) {
color: @color;
margin: @margin;
padding: @padding;
}
.class1 {
.mixin(@margin: 20px; @color: #33acfe);
}
.class2 {
.mixin(#efca44; @padding: 40px);
}
编译结果
.class1 {
color: #33acfe;
margin: 20px;
padding: 20px;
}
.class2 {
color: #efca44;
margin: 10px;
padding: 40px;
}
5. 函数式混入的返回值
函数式混入的编译结果也可以作为返回值加入混入类的作用域中参与其他属性的编译。
.mixin() {
@width: 100%;
@height: 200px;
}
.caller {
.mixin();
width: @width;
height: @height;
}
编译结果
.caller {
width: 100%;
height: 200px;
}
或许这个还不够明显,来看个更有代表性的例子
.average(@x, @y) {
@average: ((@x + @y) / 2);
}
div {
.average(16px, 50px); // "call" the mixin
padding: @average; // use its "return" value
}
编译结果
div {
padding: 33px;
}
6. 函数式混入的 "if / else"
当然LESS并没有if/else
;但利用一些条件判断,也能实现这种效果,如下:
.mixin (@a) when (lightness(@a) >= 50%) {
background-color: black;
}
.mixin (@a) when (lightness(@a) < 50%) {
background-color: white;
}
.mixin (@a) {
color: @a;
}
// Usage
.class1 { .mixin(#ddd) }
.class2 { .mixin(#555) }
结合LESS的懒加载,编译结果如下
.class1 {
background-color: black;
color: #ddd;
}
.class2 {
background-color: white;
color: #555;
}
7. 其他条件判断操作符
除了以上情况外,还有许多操作符,如>, >=, =, =<, <
,关键字true
表示永真。混入可以拼接多个判断条件。
@media: mobile;
.mixin (@a) when (@media = mobile) { width: @a }
.mixin (@a) when (@media = desktop) { width: @b }
.max (@a; @b) when (@a > @b) { width: @a }
.max (@a; @b) when (@a < @b) { width: @b }
6. 关键字 @argument
也很容易理解的一个参数,在javascritp中,argument
表示的是函数的参数集,那么,在LESS的函数式混入里,他也表示传递的所有参数的集合
.box-shadow(@x: 0; @y: 0; @blur: 1px; @color: #000) {
-webkit-box-shadow: @arguments;
-moz-box-shadow: @arguments;
box-shadow: @arguments;
}
.big-block {
.box-shadow(2px; 5px);
}
编译结果
.big-block {
-webkit-box-shadow: 2px 5px 1px #000;
-moz-box-shadow: 2px 5px 1px #000;
box-shadow: 2px 5px 1px #000;
}
7. 递归混入 Recursive
Less 中,混入也能调用它自身,形成递归混入。在编写时,结合循环条件或匹配模板,能生成一个循环调用的结构。
常见的递归混入的使用案例就是生成一组网格类
.generate-columns(4);
.generate-columns(@n, @i: 1) when (@i =< @n) {
.column-@{i} {
width: (@i * 100% / @n);
}
.generate-columns(@n, (@i + 1));
}
编译结果
.column-1 {
width: 25%;
}
.column-2 {
width: 50%;
}
.column-3 {
width: 75%;
}
.column-4 {
width: 100%;
}
8. 类型检查函数
LESS 提供了一些基础的类型检查函数,又称为is
函数,有:
- iscolor()
- isnumber()
- isstring()
- iskeyword()
- isurl()
也可以检查值的单位类型。有:
- ispixel()
- ispercentage()
- isem()
- isunit()
# CSS 条件判断
其实上文中已经讲过很多关于css 的条件判断,这里提出是因为它其实属于一大类,基本用法如下:
button when (@my-option = true) {
color: white;
}
如果有多个类需要根据同一个条件判断,则可以打包处理,处理方式可以利用&
符号
& when (@my-option = true) {
button {
color: white;
}
a {
color: blue;
}
}
# @import 导入
1. 导入别的Less文件
LESS可以导入其他文件中编写好的less
文件样式表。在CSS的标准规则中,@import的导入位置需要在所有的css样式之前。而在LESS中,因为整个文件是被先构造成渲染树再执行编译的,所以,@import 在哪个位置并无所谓。
.foo {
background: #900;
}
@import "this-is-valid.less";
2. 导入别的类型的文件
不仅支持导入.less
文件,还可以导入别的类型的文件。
- 导入css文件,作用域中的内容将被当做css使用
- 缺失后缀名,被当做带有
.less
后缀名LESS文件使用。 - 其他后缀名文件也被当做
.less
文件使用
@import "foo"; // foo.less is imported
@import "foo.less"; // foo.less is imported
@import "foo.php"; // foo.php imported as a Less file
@import "foo.css"; // statement left in place, as-is
3. @import可以带修饰符,可以带一个,也可以带多个
【语法】@import (optional,keyword) "foo.less"
reference
: use a Less file but do not output it
inline
: include the source file in the output but do not process it
less
: treat the file as a Less file, no matter what the file extension
css
: treat the file as a CSS file, no matter what the file extension
once
: only include the file once (this is default behavior)
multiple
: include the file multiple times
optional
: continue compiling when file is not found
# @plugin导入JS
1. @plugin 使用规则
导入规则和@import类似,只是@plugin要导入的是js文件,例如my-plugin.js
,这些文件可以自己实现的,也可以是网络上的,目的是引入包含一些封装好的函数功能,如Math.PI
等
@plugin "my-plugin"; // automatically appends .js if no extension
在导入文件中可以引用被导入文件作用域中的函数
registerPlugin({
install: function(less, pluginManager, functions) {
functions.add('pi', function() {
return Math.PI;
});
}
})
// Usage
@plugin "my-plugin";
.show-me-pi {
value: pi();
}
编译结果
.show-me-pi {
value: 3.141592653589793;
}
2. plugin 的作用域问题
假设有两个文件lib1.js 和 lib2.js
// lib1.js
// ...
functions.add('foo', function() {
return "foo";
});
// ...
// lib2.js
// ...
functions.add('foo', function() {
return "bar";
});
// ...
我们这么调用
.el-1 {
@plugin "lib1";
value: foo();
}
.el-2 {
@plugin "lib2";
value: foo();
}
编译结果
.el-1 {
value: foo;
}
.el-2 {
value: bar;
}
# 后语
虽然只是一门预编译语言,但其实它的功能非常强大。很多不爱前端的人就是被CSS拦在门外,而less解决了css没有语法的问题,技多不压身,多学习,没坏处。如果你能看到这里,欢迎到评论区交流。给我点个爱心~