Less, 更加高效好用的动态CSS(ii)

前言

之前我们学过了less的结构化嵌套和变量,下面我们该学习一下less中的一些高端操作了。比如混合继承什么的,这些可都是高端操作!!这里我们就先来简单的聊聊混合。

简单的混合用法

混合这个计算机名词我是真的没听过,不过要说是复合或者组合的话,我还清楚一点。两个类有一种关系叫做复合,在一个类中包含了另一个类,是一种has-a的关系。不知道混合和复合之间有什么关系。

官方文档对复合的解释是——混合(Mixin)是一种将一组属性从一个规则集包含(或混入)到另一个规则集的方法。那么这样看来混合和复合是真的一个意思嘛。

现在我们有一个样式需要使用,但是我们并不想将这个样式在需要的地方都写一遍,那么原生CSS中,我们做的事情就是将这些样式写成一个类,在需要的地方加上这个类就行了,这个解决办法其实还算蛮好的。但是有时候这个样式属性名都是一样的,但是里面个别的属性的参数不一致,这个时候该怎么做?那只能没辙了。原生的CSS是真的美欧办法来解决这个问题。那less肯定是可以为我们解决这个问题的。

现在我们有这样的一个样式。

.bordered {
      border-top: dotted 1px black;
      border-bottom: solid 2px black;
      border-radius: 20px;
}

此时有一个元素需要使用这个样式,我们只需要这样子做就行了。

.box1 {
    width: 200px;
    height: 200px;
    background: red;
    .bordered()
}

编译出来的结果如下。

.bordered {
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
  border-radius: 20px;
}

.box1 {
  width: 200px;
  height: 200px;
  background: red;
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
  border-radius: 20px;
}

这个编译结果我们其实不是很满意,上面的.bordered我们是当作一个混合来使用的,但是less也将其编译到了CSS代码中去了,这完全是一个没有用的的操作。不过是可以解决的,只需要在后面加上一个括号,less就知道你是想写一个混合,并不想将它作为一个CSS类来使用。

.bordered() {
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
  border-radius: 20px;
}

如我们想的那样,混合还可以拥有参数,less果然是强大,这混合加上的参数可以完美的解决我们的问题了。就比如上面的这个问题,边框可能需要是不同的颜色,边框的圆角也可能是不一样大的。

.bordered(@color: black, @radius: 20px) {
  border-top: dotted 1px @color;
  border-bottom: solid 2px @color;
  border-radius: @radius;
}

上面我们不仅设置了参数,还设置了默认的参数。我们可以像下面这样使用这个混合。

.bordered()
.bordered(red)
.bordered(red, 40px)

因为函数的参数有默认值,是可以空着不写的。但是这并不代表我们不可以只对@radius赋值而缺省@color,我们可以使用下面的方式给形参(姑且这么说吧)赋值。

.bordered(@radius: 50%)

匹配模式

上面就是不带参数和带参数(以及带上了默认的参数)的混合的用法,但是混合也并不是只有这么一点东西,下面需要了解的是组合的匹配模式。

首先是参数个数的匹配,下面是两个同名的函数,但是参数的个数是不相等的。

.mixins(@color, @size) {
    color: @color;
    font-size: @size;
}

.mixins(@opacity) {
    opacity: @opacity;
}

和其他的语言一样(javasript这个东西似乎是一个意外),函数的参数的个数也是函数的签名的一部分(强类型语言甚至函数的参数的类型也是函数的签名的一部分)。

.mixins(red, 10px);
.mixins(0.5);

上面的调用的是不同的混合,这也算是一种简单的混合的调用。下面来介绍一个比较重要的混合模式。

还是使用上面的border作为例子,现在我们的需求是要有一个东西来做四个方向的边框的圆角,该整么办呢?我寻思这个应该是非常的好办的。直接撸四个混合分别叫做borderRadiusTopLeft。。。。等等等的,需要什么地方的混合就调用对应的函数就行了。不过这个东西是不是有一点点low呢?我们是否可以使用同一个函数完成这样的功能呢?毕竟咱们老年人记忆力差记不清那么多的函数。

.mixins(test, @parameter);

上面就是一个匹配模式,意思是调用这个函数的第一个参数必须是test,只有匹配了才可以调用。知道了这个功能我们可以很容易的写出需求的代码。

.border(topLeft, @color: red, @radius: 20px) {
    border-top: dotted 1px @color;
    border-bottom: solid 2px @color;
    border-top-left-radius: @radius;
}

.border(topRight, @color: red, @radius: 20px) {
    border-top: dotted 1px @color;
    border-bottom: solid 2px @color;
    border-top-right-radius: @radius;
}

.border(bottomLeft, @color: red, @radius: 20px) {
    border-top: dotted 1px @color;
    border-bottom: solid 2px @color;
    border-bottom-left-radius: @radius;
}

.border(bottomRight, @color: red, @radius: 20px) {
    border-top: dotted 1px @color;
    border-bottom: solid 2px @color;
    border-bottom-right-radius: @radius;
}

这样我们就可以只使用一个函数完成上面的功能了(准确的来说是使用了四个同名的函数)。不过很容易看出一个问题,那就是上面的四个函数都有冗余的代码,重复了好多次,我们最好是将他们单独放置在一个组合中,然后在这四个组合中挨个进行调用。less早就想到了我们需要这个组合,所以说他已经提前为我们写好了。我们只需要将匹配的那部分改成@_(可以匹配任意的部内容),然后将公共的样式写在其中就行了。

 .border(topLeft, @color: red, @radius: 20px) {
    // border-top: dotted 1px @color;
    // border-bottom: solid 2px @color;
    border-top-left-radius: @radius;
}

.border(topRight, @color: red, @radius: 20px) {
    // border-top: dotted 1px @color;
    // border-bottom: solid 2px @color;
    border-top-right-radius: @radius;
}

.border(bottomLeft, @color: red, @radius: 20px) {
    // border-top: dotted 1px @color;
    // border-bottom: solid 2px @color;
    border-bottom-left-radius: @radius;
}

.border(bottomRight, @color: red, @radius: 20px) {
    // border-top: dotted 1px @color;
    // border-bottom: solid 2px @color;
    border-bottom-right-radius: @radius;
}

// 调用上面的四个任意的混合的时候,都会先来调用这里的公共样式的混合。
.border(@_, @color: red, @radius: 20px) {
    border-top: dotted 1px @color;
    border-bottom: solid 2px @color;
}

还有一个比较类似的东西,在C++type_traits中也是可以看到的,那就是调用函数的值的条件检查。

.condition(@index, @para) when (@index = 1) {
    background-color: @para;
}

.condition(@index, @para) when (@index >= 2) {
    color: @para;
}

.test5 {
    .condition(1, red);
    .condition(3, yellow);
}

和上面一样,这也是两个同名的函数,参数也都是一样的,但是函数的后面跟着一个when条件。当index = 1的时候调用第一个函数,当index >= 2的时候调用第二个函数。这个东西一般来说都是用于递归的。至于递归,这里就不多bb了,后面说到bootstrap源码的时候自然要说到递归的鬼东西。

上面的代码编译的结果如下。

.test5 {
  background-color: #ff0000;
  color: #ffff00;
}

可变参数及arguments

说到arguments就不得不提到javascript这门神奇的语言,还是和上面说的一样,JavaScript是一门函数的参数不是函数签名的语言,那么就导致了我们调用函数的时候根本不知道传入了什么东西。一个有两个参数的函数,我们可以不传参数,也可以传入一百个参数,这有点儿荒谬,不过有时候还是蛮方便的。javascript中将函数传入的参数都放置在一个数组arguments中,在函数中我们就可以直接遍历这个数组得到函数的所有的参数。

let sum = function() {
    let res = 0
    for (var i = 0; i < arguments.length; i++) {
        res += arguments[i]
    }
    return res
}

console.log(sum(1,2,3,4,5))

上面的函数可以计算任意参数数字的和,但是这个函数甚至没有任何的参数。。。

下面就来介绍一下less中的arguments是如何使用的。

在less中arguments是代表组合中传入的所以的参数。比如下面的这个例子。

.arguments(@para1, @para2, @para3) {
    background: @arguments;
}

.test {
    .arguments(red, no-repeat, cover)
}

编译出来的结果如下。

.test {
  background: #ff0000 no-repeat cover;
}

虽然看起来是非常的方便,但是总感觉是没那么必要使用这个变量,就算不使用也问题不大。

不过骚年是否知道可变参数呢?如果我们使用了可变的参数根本就不知道参数的个数或者名字,那该如何直接使用呢?下面先来介绍一下什么是可变参数以及如何使用可变参数。

可变参数这个玩意很多语言都是有的,就连c++这们古老的语言都拥有可变参数的写法,形式就是...。在less中使用可变参数也是使用...表示很多个参数。既然是可变参数,那么就必须放在最后面,不然如果我们在可变参数的后面再跟着其他的参数,我们到底该如何使用呢?

.more-argu(@para, ...) {
    background: @arguments;
}

.test2 {
    .more-argu(10px, red, 20%, cover)
}

上面就是可变参数的写法,不过上面的函数表示的是1-N个参数的函数,其实这样写是没啥意义的,因为想要取得可变参数中的内容就需要使用@arguments参数,但是@arguments表示的所有的参数,我们没有办法获取真的的可变参数中的内容。不过我们还是有办法的。

.more-argu(@para, @rest...) {
    background: @rest;
}

.test2 {
    .more-argu(10px, red, 20%, cover)
}

为这些可变的参数取一个名字,一般情况下是使用的@rest(官方文档中是这么写的)。

混合的命名空间

所谓的命名空间,我们最经常看到的地方就是在C++中,当我们写C++代码的时候可能会不由自主的打上一行using namespace std;其含义就是使用标准库中的命名空间,那么我们就可以使用标准库的中函数而不用每次都加上std::了。命名空间就是将不同部分的函数的作用范围隔离开。比如C++的标准库中有sort函数,然后你又写了一个sort函数,如果没有命名空间的话,两个函数就会出现重定义或者其他的什么问题,有了命名空间之后,我们就可以使用std::sort告诉编译器我要使用的是标准库的sort函数,而不是我自己写的sort函数。

#my-less-lib {
    .mixins() {
        background-color: skyblue;
    }
}

#other-less-lib {
    .minxis() {
        margin: 0;
        padding: 0;
    }
}

.test3 {
    #my-less-lib > .mixins();
}

.test4 {
    #other-less-lib > .minxis();
}

上面的代码中就定义了两个不同的命名空间。my-less-libother-less-lib,两个命名空间中都有相同的名字的混合minxis。此时对他们我们就可以使用命名空间就行区别了。

上面的代码的编译结果如下。

.test3 {
  background-color: skyblue;
}
.test4 {
  margin: 0;
  padding: 0;
}

还有所谓的条件命名空间,这里就不多扯蛋了,感觉应该是没啥用的东西。

将混合作为函数来使用

这也是一种蛮高端的操作,我们可以将混合作为函数那样子进行使用。不过并不是我们想象中的在混合中使用return语句。就比如说我们讲sql中的存储过程作为函数来使用,也不是通过return的方式,而是传入out | in out参数来实现的。不过这里less中的混合作为函数来使用和他们都有一点点的不一样。

.average(@x, @y) {
  @result: ((@x + @y) / 2);
  prop: 20px;
}

div {
  // call a mixin and look up its "@result" value
  padding: .average(16px, 50px)[@result];
  margin: .average(1px, 2px)[prop];
}

编译结果如下。

div {
    padding: 33px;
    margin: 20px;
}

需要注意的是这个需要较高的less版本(less 3.5)。这里就简单的说到这了。

总结

上面对less的混合的说明已经算是足够详细了,但是还有一个比较重要的less中的递归没有说,因为篇幅有限,还是留到下次再说吧。下面要对less中的继承进行一点说明。


一枚小菜鸡