一、Less 简介

olinonee大约 46 分钟

一、Less 简介

1.Less 概述

Less (Leaner Style Sheets 的缩写) 是一门向后兼容的 CSS 扩展语言。因为 Less 看起来就像 CSS,所以学习起来很容易。它是 CSS,只是多了一点东西。

2.安装和使用

2.1 node 环境中

# 全局安装 less 库
npm install -g less

# 使用 lessc 命令将 styles.less 文件编译成 styles.css 文件
> lessc styles.less styles.css

2.2 浏览器环境中

<!-- 引入本地编写的 index.less 文件-->
<link rel="stylesheet/less" type="text/css" href="index.less" />
<!-- 引入 less-js cdn 脚本文件 -->
<script src="https://cdn.jsdelivr.net/npm/less"></script>

2.3 扩展

  • 如果是 vscode 作为开发工具,在插件市场中可以搜索安装 “Easy LESS” 插件,可以轻松将 *.less 文件自动编译成 *.css 文件。
vscode EasyLess 插件
vscode EasyLess 插件

二、变量

在单个位置控制常用值。

1.概述

在之前编写 CSS 样式表,相同的值在 CSS 样式表中重复数十次甚至数百次的情况并不少见,比如:

a,
.link {
  color: #428bca;
}
.widget {
  color: #fff;
  background: #428bca;
}

less 变量提供了一种从单个位置控制这些值的方法,从而使代码更易于维护:

// 双斜杠注释不会出现在编译的 css 样式中
/* 星斜杠注释会出现在编译的 css 样式中 */

// 变量
@link-color: #428bca; // 天蓝色
@link-color-hover: darken(@link-color, 10%);

// 使用
a,
.link {
  color: @link-color;
}

a:hover {
  color: @link-color-hover;
}

.widget {
  width: 100px;
  height: 100px;
  color: #fff;
  background: @link-color;
}

编译为:

/* 星斜杠注释会出现在编译的 css 样式中 */
a,
.link {
  color: #428bca;
}
a:hover {
  color: #3071a9;
}
.widget {
  width: 100px;
  height: 100px;
  color: #fff;
  background: #428bca;
}

2.可变插值

上面的示例着重于使用变量来控制 CSS 规则中的值,但它们也可以用在其他地方,例如选择器名称、属性名称、URL@import 语句。

2.1 选择器

版本:v1.4.0

// 变量
@my-selector: banner;

// 使用
.@{my-selector} {
  width: 200px;
  height: 100px;
  background-color: green;
  font-weight: bold;
  line-height: 100px;
  text-align: center;
  margin: 0 auto;
}

编译为:

.banner {
  width: 200px;
  height: 100px;
  background-color: green;
  font-weight: bold;
  line-height: 100px;
  text-align: center;
  margin: 0 auto;
}

2.2 URLs

// 变量
@images-prefix: "../images";

// 使用
body {
  color: #444;
  background: url("@{images-prefix}/shanghai.jpg");
  background-repeat: no-repeat;
  background-size: cover;
}

编译为:

body {
  color: #444;
  background: url("../images/shanghai.jpg");
  background-repeat: no-repeat;
  background-size: cover;
}

2.3 导入语句

版本:v1.4.0

语法:@import "@{themes}/dark.less";

请注意,在 v2.0.0 之前,仅考虑在根或当前作用域内声明的变量,并且在查找变量时仅考虑当前文件和调用文件。

例子:

新建 themes 目录,并在 themes 目录下新建 dark.less 文件,代码如下:

@bc-color: #000;
@color: #fff;

html {
  background-color: @bc-color;
  color: @color;
}

导入主题,代码如下:

// 变量
@themes: "../themes";

// 使用
@import "@{themes}/dark.less";

2.4 属性

版本:v1.6.0

@property: color;

.box {
  @{property}: #0ee;
  background-@{property}: #999;
}

编译为:

.box {
  color: #0ee;
  background-color: #999;
}

3.可变变量

在 Less 中,可以使用另一个变量定义一个变量的名称。

/*  在 Less 中,你可以使用另一个变量定义一个变量的名称。*/
@primary:  green;
@secondary: blue;

.outer {
  @color: primary;

  .inner {
    background-color: gray;
    color: @@color;
  }
}

编译为:

/*  在 Less 中,你可以使用另一个变量定义一个变量的名称。*/
.outer .inner {
  background-color: gray;
  color: green;
}

4.延迟评估

变量在使用前不必声明。

有效的 Less 片段:

.lazy-eval {
  width: @var;
}

@var: @a;
@a: 9%;

这也是有效的 Less:

.lazy-eval {
  width: @var;
  @a: 9%;
}

@var: @a;
@a: 100%;

都编译成:

.lazy-eval {
  width: 9%;
}

两次定义变量时,使用变量的最后定义,从当前作用域向上搜索。 这类似于 css 本身,其中定义中的最后一个属性用于确定值。

例如:

@var: 0;
.class {
  @var: 1;
  .brass {
    @var: 2;
    three: @var;
    @var: 3;
  }
  one: @var;
}

编译为:

.class {
  one: 1;
}
.class .brass {
  three: 3;
}

本质上,每个作用域都有一个 "final" 值,类似于浏览器中的属性,就像这个使用自定义属性的例子:

.header {
  --color: white;
  color: var(--color);  // 颜色是黑色
  --color: black;
}

这意味着,与其他 CSS 预处理语言不同,Less 变量的行为与 CSS 非常相似。

5.作为变量的属性(新!)

版本:v3.0.0

可以使用 $prop 语法轻松地将属性视为变量。 有时这可以使你的代码更轻一些。

.box {
  color: #efefef;
  background-color: $color;
}

编译为:

.box {
  color: #efefef;
  background-color: #efefef;
}

请注意,与变量一样,Less 将选择当前/父作用域内的最后一个属性作为 "final" 值。

.block {
  color: red; 
  .inner {
    background-color: $color; 
  }
  color: blue;  
} 

编译为:

.block {
  color: red; 
  color: blue;  
} 
.block .inner {
  background-color: blue; 
}

6.默认变量

有时会收到对默认变量的请求 - 只有在变量尚未设置的情况下才能设置它。此功能不是必需的,因为可以通过在之后放置定义来轻松覆盖变量。

例如:

创建 library.less,代码如下:

@base-color: green;
@dark-color: darken(@base-color, 10%);

html {
  background-color: @dark-color;
  color: @base-color;
}

使用 library.less,代码如下:

// 使用库
@import "../themes/library.less";
@base-color: red;

这工作正常,因为延迟加载 - @base-color 被覆盖并且 @dark-color 是深红色。

三、父级选择器

使用 & 引用父选择器

& 运算符表示嵌套规则的父选择器,在将修改类或伪类应用于现有选择器时最常使用:

a {
  color: blue;
  &:hover {
    color: green;
  }
}

结果是:

a {
  color: blue;
}

a:hover {
  color: green;
}

注意

如果没有 &,上面的示例将导致 a :hover 规则(匹配 <a> 标签内的悬停元素的后代选择器),这不是我们通常想要的嵌套 :hover

"父选择器" 运算符有多种用途。基本上任何时候你需要以默认方式之外的其他方式组合嵌套规则的选择器。 例如,& 的另一个典型用途是生成重复的类名:

.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");
}

3.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;
}

3.2 更改选择器顺序

将选择器添加到继承的(父)选择器之前可能很有用。这可以通过将 & 放在当前选择器之后来完成。例如,在使用 Modernizr 时,你可能希望根据支持的功能指定不同的规则:

.header {
  .menu {
    border: 1px solid red;
    border-radius: 5px;
    .no-borderradius & {
      border-radius: 0;
    }
  }
}

选择器 .no-borderradius & 会将 .no-borderradius 添加到其父 .header .menu 的前面,以在输出中形成 .no-borderradius .header .menu

.header .menu {
  border: 1px solid red;
  border-radius: 5px;
}
.no-borderradius .header .menu {
  border-radius: 0;
}

3.3 组合爆炸

& 也可用于在逗号分隔列表中生成选择器的所有可能排列:

p,
a,
ul,
li {
  border-top: 2px dotted #366;

  &+& {
    border-top: 0;
  }
}

这扩展到指定元素的所有可能 (16) 组合:

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;
}

四、@import 规则

从其他样式表导入样式

在标准 CSS 中,@import @ 规则必须在所有其他类型的规则之前。 但是 Less 不关心你把 @import 语句放在哪里。

// this-is-valid.less
@bgcolor: red;

// index.less
.foo {
  background: @bgcolor;
}
@import "this-is-valid.less";

4.1 文件扩展名

Less 可能会根据文件扩展名对 @import 语句进行不同的处理:

  • 如果文件有 .css 扩展名,它将被视为 CSS 并且 @import 语句保持原样(参见下面的 内联选项)。
  • 如果它有任何其他扩展名,它将被视为 Less 并导入。
  • 如果它没有扩展名,则会附加 .less 并将其作为导入的 Less 文件包含在内。

例子:

@import "foo";      // foo.less 被导入
@import "foo.less"; // foo.less 被导入
@import "foo.php";  // foo.php 被导入作为 Less 文件
@import "foo.css";  // 语句按原样输出

以下选项可用于覆盖此行为。

4.2 导入选项

Less 为 CSS @import CSS @ 规则提供了几个扩展,以提供比你可以对外部文件执行的操作更大的灵活性。

语法:@import (keyword) "filename";

已实现以下导入选项:

  • reference:使用 Less 文件但不输出它
  • inline:在输出中包含源文件但不处理它
  • less:将文件视为 Less 文件,无论文件扩展名是什么
  • css:将文件视为 CSS 文件,无论文件扩展名是什么
  • once:只包含文件一次(这是默认行为)
  • multiple:多次包含文件
  • optional:找不到文件时继续编译

每个 @import 允许多个关键字,你必须使用逗号分隔关键字:

例子:@import (optional, reference) "foo.less";

4.2.1 reference

使用 @import (reference) 导入外部文件,但除非引用,否则不将导入的样式添加到编译输出。

发布于 v1.5.0

例子:@import (reference) "foo.less";

想象一下,reference 在导入文件中用引用标志标记每个 @ 规则 和选择器,正常导入,但是当生成 CSS 时,"reference" 选择器(以及任何仅包含引用选择器的媒体查询)不会输出。 reference 样式不会出现在你生成的 CSS 中,除非引用样式用作 mixinsextended

此外,reference 根据使用的方法(mixinextend)产生不同的结果:

  • extend: 当一个选择器被扩展时,只有新的选择器被标记为未被引用,并被拉入引用 @import 语句的位置。
  • mixins: 当 reference 样式用作 隐式混合 时,其规则被混入,标记为 "不引用",并正常出现在引用的位置。

参考范例

这允许你通过执行以下操作仅从 Bootstrap 等库中提取特定的、有针对性的样式:

.navbar:extend(.navbar all) {}

并且你将从 Bootstrap 中仅引入与 .navbar 相关的样式。

4.2.2 inline

使用 @import (inline) 包含外部文件,但不处理它们。

发布于 v1.5.0

例子:@import (inline) "not-less-compatible.css";

当 CSS 文件可能不兼容时,你将使用它;这是因为虽然 Less 支持大多数已知标准的 CSS,但它在某些地方不支持注释,并且在不修改 CSS 的情况下不支持所有已知的 CSS hack。

因此,你可以使用它在输出中包含文件,以便所有 CSS 都在一个文件中。

4.2.3 less

使用 @import (less) 将导入的文件视为 Less,而不考虑文件扩展名。

发布于 v1.4.0

例子:

@import (less) "foo.css";

4.2.4 less

使用 @import (css) 将导入的文件视为常规 CSS,而不考虑文件扩展名。 这意味着导入语句将保持原样。

发布于 v1.4.0

例子:

@import (css) "foo.less";

输出

@import "foo.less";

4.2.5 once

@import 语句的默认行为。 这意味着该文件仅导入一次,该文件的后续导入语句将被忽略。

发布于 v1.4.0

这是 @import 语句的默认行为。

例子:

@import (once) "foo.less";
@import (once) "foo.less"; // 这行语句将被忽略

4.2.6 multiple

使用 @import (multiple) 允许导入多个同名文件。这是与一次相反的行为。

发布于 v1.4.0

例子:

// file: foo.less
.a {
  color: green;
}
// file: main.less
@import (multiple) "foo.less";
@import (multiple) "foo.less";

输出

.a {
  color: green;
}
.a {
  color: green;
}

4.2.7 optional

使用 @import (optional) 允许仅在文件存在时导入该文件。如果没有 optional 关键字,Less 会在导入找不到的文件时抛出 FileError 并停止编译。

发布于 v2.3.0

五、继承

Extend 是一个 Less 伪类,它将它所放置的选择器与与其引用的匹配的选择器合并。

发布于 v1.4.0

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

在上面的规则集中,:extend 选择器会将 "继承选择器" (nav ul) 应用于 .inline 类,无论 .inline 类出现在哪里。声明块将保持原样,但不会引用扩展(因为扩展不是 css)。

所以如下:

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

输出

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

请注意 nav ul:extend(.inline) 选择器如何将输出为 nav ul - 继承在输出之前被删除并且选择器块保持原样。如果该块中没有放置任何属性,那么它将从输出中删除(但继承仍然可能影响其他选择器)。

5.1 继承语法

继承要么附加到选择器,要么放入规则集中。它看起来像一个带有选择器参数的伪类,可选地后跟关键字 all

例子:

.a:extend(.b) {}

// 上面的代码块和下面的代码块编译结果相同
.a {
  &:extend(.b);
}
.c:extend(.d all) {
  // 继承 “.d” 的所有实例,例如 “.x.d” 或  “.d.x”
}
.c:extend(.d) {
  // 仅继承选择器为 “.d” 的实例
}

它可以包含一个或多个要继承的类,以逗号分隔。

例子:

.e:extend(.f) {}
.e:extend(.g) {}

// 上面的代码块和下面的代码块编译结果相同
.e:extend(.f, .g) {}

5.2 附加到选择器的继承

附加到选择器的继承看起来像一个普通的伪类,将选择器作为参数。一个选择器可以包含多个继承子句,但所有继承都必须位于选择器的末尾。

  • 在选择器之后继承:pre:hover:extend(div pre)
  • 允许选择器和继承之间有空格:pre:hover :extend(div pre)
  • 允许多个继承:pre:hover:extend(div pre):extend(.bucket tr) - 注意这与 pre:hover:extend(div pre, .bucket tr) 相同;
  • 继承必须在最后: pre:hover:extend(div pre).nth-child(odd)

如果规则集包含多个选择器,则其中任何一个都可以具有 extend 关键字。在一个规则集中继承的多个选择器:

.big-division,
.big-bag:extend(.bag),
.big-bucket:extend(.bucket) {
  // body
}

5.3 继承内部规则集

可以使用 &:extend(selector) 语法将 extend 放入规则集的主体中。 将 extend 放入主体是将其放入该规则集的每个选择器的快捷方式。

在主体内继承:

pre:hover,
.some-class {
  &:extend(div pre);
}

与在每个选择器之后添加继承完全相同:

pre:hover:extend(div pre),
.some-class:extend(div pre) {}

5.4 继承嵌套选择器

extend 能够匹配嵌套的选择器。主要关注 less

例子:

.bucket {
  // 带有目标选择器的嵌套规则集
  tr {
    color: blue;
  }
}

// 嵌套规则集被识别
.some-class:extend(.bucket tr) {
  background-color: green;
} 

输出

.bucket tr,
.some-class {
  color: blue;
}
.some-class {
  background-color: green;
}

本质上,extend 着眼于编译后的 css,而不是原始的 less

例子:

.bucket {
  // 带有目标选择器的嵌套规则集
  tr & {
    color: blue;
  }
}

// 嵌套规则集被识别
.some-class:extend(tr .bucket) {
  background-color: green;
}

输出

tr .bucket,
.some-class {
  color: blue;
}
.some-class {
  background-color: green;
}

5.5 精确匹配继承

默认情况下扩展会查找选择器之间的精确匹配。选择器是否使用前缀星(*.class)并不重要。两个第 n 个表达式具有相同的含义并不重要,它们需要具有相同的形式才能匹配。唯一的例外是属性选择器中的引号,less 知道它们具有相同的含义并匹配它们。

例子:

.a.class,
.class.a,
.class > .a {
  color: blue;
}

// 这将不匹配上面的任何选择器
.test:extend(.class) {
  background-color: green;
}

输出:

.a.class,
.class.a,
.class > .a {
  color: blue;
}
.test {
  background-color: green;
}

选择器 *.class.class 是等价的,但 extend 不会匹配它们:

*.class {
  color: blue;
}
// 这将不匹配 *.class 选择器
.no-star:extend(.class) {
  background-color: green;
}

输出:

*.class {
  color: blue;
}
.no-star {
  background-color: green;
}

伪类的顺序确实很重要。选择器 link:hover:visitedlink:visited:hover 匹配同一组元素,但 extend 将它们视为不同的:

.link:hover:visited {
  color: blue;
}

.selector:extend(.link:visited:hover) {
  background-color: green;
}

输出

.link:hover:visited {
  color: blue;
}
.selector {
  background-color: green;
}

5.6 第 n 表达式

n 表达形式很重要。 第 n 表达式 1n+3n+3 是等价的,但 extend 不会匹配它们:

li:nth-child(1n+3) {
  color: blue;
}

.child:extend(li:nth-child(n+3)){
  background-color: green;
}

输出

li:nth-child(1n+3) {
  color: blue;
}
.child {
  background-color: green;
}

属性选择器中的引用类型无关紧要。以下所有内容都是等价的。

[title=identifier] {
  color: blue;
}

[title='identifier'] {
  color: blue;
}

[title="identifier"] {
  color: blue;
}

.noQuote:extend([title=identifier]) {
  background-color: orange;
}

.singleQuote:extend([title='identifier']) {
  background-color: purple;
}

.doubleQuote:extend([title="identifier"]) {
  background-color: skyblue;
}

输出

[title=identifier],
.noQuote,
.singleQuote,
.doubleQuote {
  color: blue;
}
[title='identifier'],
.noQuote,
.singleQuote,
.doubleQuote {
  color: blue;
}
[title="identifier"],
.noQuote,
.singleQuote,
.doubleQuote {
  color: blue;
}
.noQuote {
  background-color: orange;
}
.singleQuote {
  background-color: purple;
}
.doubleQuote {
  background-color: skyblue;
}

5.7 继承 "all"

当你在继承参数中最后指定 all 关键字时,它会告诉 Less 将该选择器作为另一个选择器的一部分进行匹配。选择器将被复制,然后选择器的匹配部分将被继承替换,从而形成一个新的选择器。

例子:

.a.b.test,
.test.c {
  color: orange;
}

.test {
  &:hover {
    color: green;
  }
}

.replacement:extend(.test all) {
  background-color: gray;
}

输出:

.a.b.test,
.test.c,
.a.b.replacement,
.replacement.c {
  color: orange;
}
.test:hover,
.replacement:hover {
  color: green;
}
.replacement {
  background-color: gray;
}

将这种操作模式视为本质上进行非破坏性搜索和替换。

5.8 继承选择器插值

extend 不能够匹配选择器和变量。如果选择器包含变量,extend 将忽略它。但是,extend 可以附加到插值选择器。

官方:带有变量的选择器将不会被匹配
实际测试:可以匹配带有变量的选择器

@variable: .bucket;

// 插值选择器
@{variable} {
  color: blue;
}

// 不执行任何操作,也就是找不到匹配项
.some-class:extend(.bucket) {
  background-color: red;
}

输出:

.bucket,
.some-class {
  color: blue;
}
.some-class {
  background-color: red;
}

并在目标选择器中使用变量扩展不匹配:

.bucket {
  color: blue;
}

// 插值选择器不会匹配任何项
.some-class:extend(@{variable}) {
  background-color: red;
}
@variable: .bucket;

输出:

.bucket {
  color: blue;
}
.some-class {
  background-color: red;
}

但是,附加到插值选择器的 :extend 有效:

官方:附加到插值选择器的 :extend 有效
实际测试:附加到插值选择器的 :extend 无效

.bucket {
  color: blue;
}
@{variable}:extend(.bucket) {
  background-color: red;
}
@variable: .selector;

输出:

.bucket {
  color: blue;
}
.selector {
  background-color: red;
}

5.9 作用域/ @media 内部的继承

目前,@media 声明中的 :extend 只会匹配同一媒体声明中的选择器:

@media print {
  // extend 内部的 selector 
  .screenClass:extend(.selector) {
    background-color: gray;
  } 
  // 在相同的媒体中进行匹配
  .selector {
    color: black;
  }
}

.selector {
  color: red;
}

@media screen {
  .selector {
    color: blue;
  }
}

编译成:

@media print {
  .screenClass {
    background-color: gray;
  }
  .selector,
  .screenClass {
    color: black;
  }
}
.selector {
  color: red;
}
@media screen {
  .selector {
    color: blue;
  }
}

注意

继承与嵌套 @media 声明内的选择器不匹配。

@media screen {
  .screenClass:extend(.selector) {
    background-color: gray;
  } 
  // 嵌套媒体-继承的规则集将忽略它
  @media (min-width: 1023px) {
    .selector { 
      color: blue;
    }
  }
}

这编译成:

@media screen {
  .screenClass {
    background-color: gray;
  }
}
@media screen and (min-width: 1023px) {
  .selector {
    color: blue;
  }
}

顶层继承匹配所有内容,包括嵌套媒体内的选择器:

@media screen {
  .selector {
    color: blue;
  }
  @media (min-width: 1023px) {
    .selector {
      color: blue;
    }
  }
}

// 顶层扩展匹配所有内容,包括嵌套媒体内的选择器
.topLevel:extend(.selector) {
  background-color: gray;
} 

编译成:

@media screen {
  .selector,
  .topLevel {
    color: blue;
  }
}
@media screen and (min-width: 1023px) {
  .selector,
  .topLevel {
    color: blue;
  }
}
.topLevel {
  background-color: gray;
}

5.10 重复检测

目前没有重复检测。

例子:

.alert-info,
.widget {
  color: red;
}

.alert:extend(.alert-info, .widget) {
  background-color: gray;
}

输出

.alert-info,
.widget,
.alert,
.alert {
  color: red;
}
.alert {
  background-color: gray;
}

5.11 继承的用例

5.11.1 经典用例

经典用例是避免添加基类。 例如,如果你有

.animal {
  background-color: black;
  color: white;
}

并且你想要一种动物子类型来覆盖背景颜色,那么你有两个选择,首先更改你的 HTML

<a class="animal bear">Bear</a>
.animal {
  background-color: black;
  color: white;
}
.bear {
  background-color: brown;
}

或者简化 html 并在你的 less 中使用 extend。 例如

<a class="bear">Bear</a>
.animal {
  background-color: black;
  color: white;
}
.bear {
  &:extend(.animal);
  background-color: brown;
}

5.11.2 减少 CSS 大小

Mixins 将所有属性复制到一个选择器中,这会导致不必要的重复。因此,可以使用 extends 而不是 mixins 将选择器向上移动到你希望使用的属性,这会导致生成更少的 CSS

示例 - 使用 mixin

.my-inline-block {
  display: inline-block;
  font-size: 50px;
}
// 使用 mixin 混合方式
.thing1 {
  .my-inline-block;
}

.thing2 {
  .my-inline-block;
}

输出

.my-inline-block {
  display: inline-block;
  font-size: 50px;
}
.thing1 {
  display: inline-block;
  font-size: 50px;
}
.thing2 {
  display: inline-block;
  font-size: 50px;
}

示例 - 使用 extend

.my-inline-block {
  display: inline-block;
  font-size: 50px;
}
// 使用继承方式
.thing1 {
  &:extend(.my-inline-block);
}

.thing2 {
  &:extend(.my-inline-block);
}

注意

推荐使用继承方式,继承方式会生成更少的 CSS。

5.11.3 组合样式/更高级的混合

另一个用例是作为混入的替代方案 - 因为混入只能与简单的选择器一起使用,如果你有两个不同的 html 块,但需要对两者应用相同的样式,你可以使用 extends 来关联两个区域。

li.list > a {
  color: orange;
}

button.list-style {
  &:extend(li.list > a);
}

输出

li.list > a,
button.list-style {
  color: orange;
}

六、合并属性

merge 功能允许将来自多个属性的值聚合到单个属性下的逗号或空格分隔列表中。merge 对于背景和转换等属性很有用。

6.1 逗号

用逗号附加属性值

发布于 v1.5.0

例子:

// 在 Less 中,使用 + 符号将两个类或属性组合在一起是一种简写方式,可以避免重复定义相同的类或属性。
.mixin() {
  box-shadow+: inset 0 0 10px #555;
}

.my-class {
  .mixin();
  box-shadow+: 0 0 20px black;
}

输出

.my-class {
  box-shadow: inset 0 0 10px #555, 0 0 20px black;
}

6.2 空格

用空格附加属性值

发布于 v1.7.0

例子:

.mixin() {
  // + 符号将两个类或属性组合在一起是一种简写方式,_符号表示属性用空格隔开,+_ 必须组合使用表达空格隔开,直接 _ 符号会报错
  transform+_: scale(2);
  background-color: skyblue;
}

.my-class {
  .mixin();
  transform+_: rotate(15deg);
}

输出

.my-class {
  transform: scale(2) rotate(15deg);
  background-color: skyblue;
}

为了避免任何无意的连接,merge 要求在每个连接挂起声明上都有一个明确的 ++_ 标志。

七、混入

现有样式的 "mix-in" 属性

你可以混合使用类选择器和 ID 选择器,例如

.a, #b {
  color: red;
}

// 混合类选择器
.mixin-class {
  .a();
}

// 混合 ID 选择器
#mixin-id {
  #b();
}

结果是:

.a,
#b {
  color: red;
}
.mixin-class {
  color: red;
}
#mixin-id {
  color: red;
}

从历史上看,mixin 调用中的括号是可选的,但可选的括号已被弃用,在未来的版本中将是必需的。

.a(); // 必需
.a; // 目前可用,但已弃用,请不要使用
.a (); // 括号前的空格也不建议使用

7.1 带括号的混入

如果想创建一个 mixin 但你不希望那个 mixin 出现在你的 CSS 输出中,请在 mixin 定义之后加上括号。

// 不带括号的混入编译之后会出现在 css 文件中
.my-mixin {
  color: red;
}

// 带括号的混入编译之后不会出现在 css 文件中
.my-other-mixin() {
  background: orange;
}

.class {
  .my-mixin();
  .my-other-mixin();
}

输出

.my-mixin {
  color: red;
}
.class {
  color: red;
  background: orange;
}

7.2 混入中的选择器

Mixin 不仅可以包含属性,还可以包含选择器。

例如:

// 带括号的混入编译之后不会出现在 css 中
.my-hover-mixin() {
  &:hover {
    border: 1px solid red;
  }
}

button {
  .my-hover-mixin();
}

输出

button:hover {
  border: 1px solid red;
}

7.3 命名空间

如果你想在更复杂的选择器中混合属性,你可以堆叠多个 id 或类。

#outer() {
  .inner {
    color: red;
  }
}

.c {
  #outer.inner();
}

注意

旧版的 Less 语法允许 > 和名称空间与混入之间的空格。此语法已弃用,可能会被删除。目前,这些做同样的事情。

#outer > .inner(); // 已被遗弃
#outer .inner(); // 已被遗弃
#outer.inner(); // 首选

像这样命名混入可以减少与其他库混入或用户混入的冲突,但也可以成为 "库" 组混入的一种方式。

例子:

#my-library {
  .my-mixin() {
    color: orange;
  }
}

// 像这样使用
.class {
  #my-library.my-mixin();
}

结果为:

.class {
  color: orange;
}

7.4 受保护的命名空间

如果一个命名空间有一个守卫,只有当守卫条件返回真时,它定义的混合才会被使用。名称空间守卫的评估与混合上的守卫完全相同,因此以下两个混合的工作方式相同:

/* ------------ 案例一 ------------ */
// 定义变量
@mode: huge;

// 方式一:定义混入命名空间,当变量 mode 为 huge 的时候生效
#namespace when (@mode =huge) {
  .mixin() {
    color: red;
  }
}

// 方式二:定义混入命名空间,与方式一工作方式相同
#namespace {
  .mixin() when (@mode =huge) {
    font-size: 50px;
  }
}

// 使用混入命名空间
.element1 {
  #namespace.mixin();
}

.element2 {
  #namespace.mixin();
}


/* ------------ 案例二 ------------ */
// 定义条件变量
@condition: true;

// 定义混合命名空间,多条件嵌套使用
#sp_1 when (@condition) {
  color: red;
  #sp_2 when (@condition) {
    background-color: skyblue;
    // not 表示用于不执行,将 not 能成功执行
    .mixin() when not (@condition) {
      color: green;
      font-size: 40px;
    }
  }
}

// 使用混合命名空间
.element3 {
  #sp_1();
  #sp_2();
  .mixin();
}

编译为:

/* ------------ 案例一 ------------ */
.element1 {
  color: red;
  font-size: 50px;
}
.element2 {
  color: red;
  font-size: 50px;
}
/* ------------ 案例二 ------------ */
#sp_1 {
  color: red;
}
#sp_1 #sp_2 {
  background-color: skyblue;
}
.element3 {
  color: red;
  background-color: skyblue;
}
.element3 #sp_2 {
  background-color: skyblue;
}

假定 default 函数对所有嵌套命名空间和混入具有相同的值。下面的 mixin 永远不会被执行;它的条件保证是假的:

// 定义一个带有参数的混合器,使用参数化混合,根据是否传递了颜色参数来决定是否应用混合器
.mixin(@color) {
  color: @color;
  font-size: 40px;
}

// default() 函数只能在参数化 mixin guards(混合器守卫)中使用
// 定义无参混合命名空间,使用 default() 函数,使用默认颜色值
#sp_4() when (default()) {
  #sp_5() when(default()) {
    background-color: skyblue;
    // 默认颜色为绿色
    .mixin(green);
  }
}

// 定义有参混合命名空间,使用自定义颜色
#sp_6(@customColor) when (default()) {
  #sp_7(@color) when(default()) {
    background-color: darken(@color, 10%);
    // 自定义颜色
    .mixin(@customColor);
  }
}

// 使用无参混合命名空间
.element4 {
  #sp_4();
  #sp_5();
}

// 使用有参混合命名空间
.element5 {
  #sp_6(orange);
  #sp_7(green);
}

编译为:

.element4 {
  background-color: skyblue;
  color: green;
  font-size: 40px;
}
.element5 {
  background-color: #004d00;
  color: orange;
  font-size: 40px;
}

7.5 !important 关键字

mixin 调用后使用 !important 关键字将其继承的所有属性标记为 !important

例子:

.foo (@bg: skyblue, @color: #900) {
  background: @bg;
  color: @color;
}

.unimportant {
  .foo();
}

.important {
  .foo() !important;
  background-color: green;
}

结果是:

.unimportant {
  background: skyblue;
  color: #900;
}
.important {
  background: skyblue !important;
  color: #900 !important;
  background-color: green;
}

7.6 参数混合

7.6.1 如何将参数传递给 mixins

mixins 也可以接受参数,这些参数是在混合时传递给选择器块的变量。

例如:

.border-radius(@radius) {
  -webkit-border-radius: @radius;
     -moz-border-radius: @radius;
          border-radius: @radius;
}

以下是如何将其混合到各种规则集中:

#header {
  .border-radius(4px);
}
.button {
  .border-radius(6px);
}

参数混合也可以为其参数设置默认值:

.border-radius(@radius: 5px) {
  -webkit-border-radius: @radius;
     -moz-border-radius: @radius;
          border-radius: @radius;
}

现在可以像这样调用它:

#header {
  .border-radius();
}

它将包括一个 5px 的边框半径。

还可以使用不带参数的参数混合。如果想从 CSS 输出中隐藏规则集,但又想将其属性包含在其他规则集中,这将很有用:

.wrap() {
  text-wrap: wrap;
  white-space: -moz-pre-wrap;
  white-space: pre-wrap;
  word-wrap: break-word;
}

pre {
  .wrap()
}

会输出:

pre {
  text-wrap: wrap;
  white-space: -moz-pre-wrap;
  white-space: pre-wrap;
  word-wrap: break-word;
}

7.6.2 参数分隔符

参数当前以分号或逗号分隔。

最初,参数只用逗号分隔,但后来添加了分号以支持将逗号分隔的列表值传递给单个参数。

注意

Less 4.0 开始,你可以使用括号转义 [~()] 来封装列表值,例如 .name(@param1: ~'red')。这类似于引用转义语法: ~"quote"。这可能会使你的代码库中不需要分号分隔符。 在 Less 中,~(tilde)的主要作用是用于取消引号。这是因为在 Less 中,字符串通常需要被引号包裹,但有时候你可能希望在特定的地方使用未引号的字符串,比如作为 CSS 选择器或属性值。

例子:

  • 两个参数,每个参数都包含逗号分隔列表:.name(1, 2, 3; something, else)
  • 三个参数,每个参数包含一个数字:.name(1, 2, 3)
  • 使用虚拟分号创建一个 mixin 调用,其中一个参数包含一个逗号分隔的 css 列表:.name(1, 2, 3;)。 注意:如果结尾的分号看起来很奇怪,你可能更喜欢:.name(~'1,2,3')
  • 编写逗号分隔默认值的方法:
    • @param-values: red, blue; .name(@param1: @param-values)
    • .name(@param1: red, blue;)
    • .name(@param1: ~'red, blue')

使用案例:

/* 在 Less 中,~(tilde)的主要作用是用于取消引号。这是因为在Less中,字符串通常需要被引号包裹,但有时候你可能希望在特定的地方使用未引号的字符串,比如作为 CSS 选择器或属性值。 */
/* 使用案例 1 —— 在选择器中使用未引号的字符串 */
@button-prefix: ~'btn';

.@{button-prefix} {
  cursor: pointer;
  height: 32px;
  padding: 0 15px;
  border-radius: 4px;
  font-weight: 400;
  line-height: 1.499;
  box-shadow: 0 2px 0 rgba(0, 0, 0, .015);
  text-align: center;
  transition: all .3s cubic-bezier(.645, .045, .355, 1);
  font-size: 14px;
}

.@{button-prefix}-success {
  background-color: green;
  color: white;
  border: 1px solid green;

  &:hover {
    background-color: darken(green, 5%);
  }
}

.@{button-prefix}-normal {
  background-color: skyblue;
  color: white;
  border: 1px solid skyblue;

  &:hover {
    background-color: darken(skyblue, 5%);
  }
}

.@{button-prefix}-error {
  background-color: red;
  color: white;
  border: 1px solid red;

  &:hover {
    background-color: darken(red, 5%);
  }
}

/* 使用案例 2  —— 在属性值中使用未引号的字符串 */
.box-shadow(@x: 1px, @y: 1px, @blur: 1px, @color: #000) {
  box-shadow:~"@{x} @{y} @{blur} @{color}";
  background-color: skyblue;
}

.box {
  .box-shadow();
}

编译为:

/* 在 Less 中,~(tilde)的主要作用是用于取消引号。这是因为在Less中,字符串通常需要被引号包裹,但有时候你可能希望在特定的地方使用未引号的字符串,比如作为 CSS 选择器或属性值。 */
/* 使用案例 1 —— 在选择器中使用未引号的字符串 */
.btn {
  cursor: pointer;
  height: 32px;
  padding: 0 15px;
  border-radius: 4px;
  font-weight: 400;
  line-height: 1.499;
  box-shadow: 0 2px 0 rgba(0, 0, 0, 0.015);
  text-align: center;
  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
  font-size: 14px;
}
.btn-success {
  background-color: green;
  color: white;
  border: 1px solid green;
}
.btn-success:hover {
  background-color: #006700;
}
.btn-normal {
  background-color: skyblue;
  color: white;
  border: 1px solid skyblue;
}
.btn-normal:hover {
  background-color: #71c5e7;
}
.btn-error {
  background-color: red;
  color: white;
  border: 1px solid red;
}
.btn-error:hover {
  background-color: #e60000;
}
/* 使用案例 2  —— 在属性值中使用未引号的字符串 */
.box {
  box-shadow: 1px 1px 1px #000;
  background-color: skyblue;
}

7.6.3 重载混入

定义多个具有相同名称和参数数量的混合是合法的。Less 将使用所有可以应用的属性。如果你使用带有一个参数的 mixin,例如 .mixin(green);,然后将使用具有一个强制参数的所有 mixin 的属性:

.mixin(@color) {
  color-1: @color;
}

.mixin(@color, @padding: 2) {
  color-2: @color;
  padding-2: @padding;
}

.mixin(@color, @padding, @margin: 2) {
  color-3: @color;
  padding-3: @padding;
  margin-3: @margin;
}

.some .selector {
  .mixin(#008000);
}

编译成:

.some .selector {
  color-1: #008000;
  color-2: #008000;
  padding-2: 2;
}

7.6.4 命名参数

mixin 引用可以通过名称而不是位置来提供参数值。任何参数都可以通过其名称来引用,并且它们不必按任何特殊顺序排列:

.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;
}

7.6.5 @arguments 变量

@argumentsmixin 中有特殊含义,它包含调用 mixin 时传递的所有参数。如果你不想处理单个参数,这很有用:

.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.6.6 高级参数和 @rest 变量

如果你希望你的 mixin 接受可变数量的参数,你可以使用 ...。在变量名之后使用它会将这些参数分配给变量。

// 匹配 0~N 个参数
.mixin(...) {
  font-size: 1.5rem;
}

// 完全匹配 0 个参数
.mixin() {
  font-size: 1.5rem;
}

// 匹配 0~1 个参数
.mixin(@a: 1) {
  font-size: 1.5rem;
}

// 匹配 0~N 个参数
.mixin(@a: 1, ...) {
  font-size: 1.5rem;
}

// 匹配 1~N 个参数
.mixin(@a, ...) {
  font-size: 1.5rem;
}

此外:

// @rest 绑定到 @a 之后的参数
.mixin(@a, @rest...) {
  // @arguments 绑定到所有参数
  font-size: @arguments;
}

7.7 模式匹配

有时,可能希望根据传递给它的参数更改混入的行为。从一些基本的东西开始:

.mixin(@s, @color) {}

.class {
  .mixin(@switch, #888);
}

现在假设希望 .mixin 表现不同,基于 @switch 的值,可以这样定义 .mixin

.mixin(dark, @color) {
  color: darken(@color, 10%);
}

.mixin(light, @color) {
  color: lighten(@color, 10%);
}

.mixin(@_, @color) {
  display: block;
}

现在,如果运行:

@switch: light;

.class {
  .mixin(@switch, red);
}

将得到以下 CSS

.class {
  color: #ff3333;
  display: block;
}

传递给 .mixin 的颜色变浅了。如果 @switch 的值为 dark,则结果将是较深的颜色。

这是发生了什么:

  • 第一个 mixin 定义不匹配,因为它期望 dark 作为第一个参数。
  • 第二个 mixin 定义匹配,因为它需要 light
  • 第三个 mixin 定义匹配,因为它期望任何值。

仅使用匹配的 mixin 定义。变量匹配并绑定到任何值。除了变量之外的任何东西都只与等于其自身的值匹配。

我们也可以匹配元数,下面是一个例子:

.mixin1(@a) {
  color: @a;
}
.mixin1(@a, @b) {
  // fade 函数降低颜色变量透明度,可用在按钮展示以及主题切换
  color: fade(@a, @b);
}

.class1 {
  .mixin1(green, 20%);
}

现在如果用一个参数调用 .mixin,将得到第一个定义的输出,但如果用两个参数调用它,将得到第二个定义,即 @a 淡化为 @b

编译为:

.class1 {
  color: rgba(0, 128, 0, 0.2);
}

7.8 使用混入作为函数

mixin 调用中选择属性和变量

7.8.1 属性/值访问器

发布于 v3.5.0

从 Less 3.5 开始,你可以使用属性/变量访问器从已评估的混入规则中选择一个值。这可以让你使用类似于函数的混合。

例子:

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

div {
  // 调用混入并查找它的 “@result” 值
  padding: .average(16px, 50px)[@result];
}

结果是:

div {
  padding: 33px;
}

7.8.2 覆盖混入值

如果有多个匹配的混入,所有规则都会被评估和合并,并返回具有该标识符的最后一个匹配值。这类似于 CSS 中的级联,它允许你混合 "重载" 的值。

// library.less
#library() {
  .mixin() {
    prop: foo;
  }
}

// customize.less
@import "./library";
#library() {
  .mixin() {
    prop: bar;
  }
}

.box {
  my-value: #library.mixin[prop];
}

输出:

.box {
  my-value: bar;
}

7.8.3 未命名的查找

如果没有在 [@lookup] 中指定查找值,而是在 mixin 或规则集调用之后写入 [],则所有值将级联并且将选择最后声明的值。

意义:上面例子中的平均 mixin 可以写成:

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

div {
  // 调用一个混合并且找它的常量值
  padding: .average(16px, 50px)[];
}

输出是相同的:

div {
  padding: 33px;
}

对于混入调用别名的规则集或变量,同样的级联行为也是如此。

@dr: {
  value: foo;
}

.box {
  my-value: @dr[];
}

这输出:

.box {
  my-value: foo;
}

7.9 递归混入

7.9.1 创建循环

在 Less 中,混入可以调用自己。当与守卫表达式和模式匹配结合使用时,此类递归混合可用于创建各种迭代/循环结构。

例子:

.loop(@counter) when (@counter > 0) {
  // 下一次循环
  .loop((@counter - 1));
  // 每次迭代值
  width: (10px * @counter);
}

div {
  // 调用循环
  .loop(5);
}

输出:

div {
  width: 10px;
  width: 20px;
  width: 30px;
  width: 40px;
  width: 50px;
}

使用递归循环生成 CSS 网格类的通用示例:

.generate-columns(@n, @i: 1) when (@i =< @n) {
  .column-@{i} {
    width: (@i * 100% / @n);
    background-color: rgb(@i * 10, @i * 20, @i * 30);
  }

  .generate-columns((@n), (@i + 1));
}

.generate-columns(4);

输出:

.column-1 {
  width: 25%;
  background-color: #0a141e;
}
.column-2 {
  width: 50%;
  background-color: #14283c;
}
.column-3 {
  width: 75%;
  background-color: #1e3c5a;
}
.column-4 {
  width: 100%;
  background-color: #285078;
}

7.10 混入守卫

当你想匹配表达式时,守卫很有用,而不是简单的值或元数。如果你熟悉函数式编程,你可能已经遇到过它们。

为了尽可能接近 CSS 的声明性质,Less 选择通过受保护的混入而不是 if/else 语句来实现条件执行,这与 @media 查询功能规范保持一致。

让我们从一个例子开始:

.mixin(@a) when (lightness(@a) >=50%) {
  background-color: #000;
}

.mixin(@a) when (lightness(@a) < 50%) {
  background-color: #fff;
}

.mixin(@a) {
  color: @a;
}

关键是 when 关键字,它引入了一个守卫序列(这里只有一个守卫)。现在,如果运行以下代码:

.class1 {
  .mixin(#ddd)
}

.class2 {
  .mixin(#555)
}

输出:

.class1 {
  background-color: #000;
  color: #ddd;
}
.class2 {
  background-color: #fff;
  color: #555;
}

7.10.1 守卫比较运算符

守卫中可用的比较运算符的完整列表是:>>===<<。 此外,关键字 true 是唯一的真值,这使得这两个混入等效:

.truth(@a) when (@a) {
  font-size: 60px;
}

.truth(@a) when (@a = true) {
  font-size: 50px;
}

除关键字 true 之外的任何值都是假的:

.class {
.truth(40); // 将不符合上述任何定义。
// .truth(true);
}

请注意,你还可以将参数相互比较,或与非参数进行比较:

@media: mobile;

.mixin(@a) when (@media = mobile) {
  font-size: 20px;
}

.mixin(@a) when (@media = desktop) {
  font-size: 30px;
}

.max(@a; @b) when (@a > @b) {
  width: @a
}

.max(@a; @b) when (@a < @b) {
  width: @b
}

7.10.2 守卫逻辑运算符

你可以将逻辑运算符与守卫一起使用。语法基于 CSS 媒体查询。

使用 and 关键字组合守卫:

.mixin(@a) when (isnumber(@a)) and (@a > 0) {}

你可以通过用逗号,分隔守卫来模拟 or 运算符。如果任何一个守卫评估为真,它被认为是一场比赛:

.mixin(@a) when (@a > 10), (@a < -10) {}

使用 not 关键字否定条件:

.mixin(@b) when not (@b > 0) {}

7.10.3 类型检查函数

最后,如果你想根据值类型匹配混入,你可以使用 is 函数:

.mixin(@a; @b: 0) when (isnumber(@b)) {}

.mixin(@a; @b: black) when (iscolor(@b)) {}

下面是基本的类型检查函数:

  • iscolor
  • isnumber
  • isstring
  • iskeyword
  • isurl

如果你想检查一个值除了是一个数字之外是否在一个特定的单位中,你可以使用以下之一:

  • ispixel
  • ispercentage
  • isem
  • isunit

7.11 混入别名

发布于 v3.5.0

7.11.1 将混入调用分配给变量

混入可以被分配给一个变量作为变量调用,也可以用于映射查询。

#theme.dark.navbar {
	.colors(light) {
		primary: purple;
		secondary: black;
	}
	.colors(dark) {
		primary: green;
		secondary: grey;
	}
}

.navbar {
	@colors: #theme.dark.navbar.colors(dark);
	background: @colors[primary];
	border: 1px solid @colors[secondary];
}

输出:

.navbar {
  background: green;
  border: 1px solid grey;
}

7.11.2 变量调用

整个混入调用都可以使用别名并称为变量调用。如:

#library() {
  .colors() {
    background: green;
  }
}

.box {
  @alias: #library.colors();
  @alias();
}

输出:

.box {
  background: green;
}

注意

与根(root)类中使用的混入不同,分配给变量和不带参数调用的混入调用始终需要括号。以下内容无效。

.box {
  @alias: #library.colors;
  @alias(); // 错误:无法计算变量调用 @alias
}

这是因为如果变量被分配一个选择器列表或一个混入调用是不明确的。例如,在 Less 3.5+ 中,这个变量可以这样使用。

.box {
  @alias: #library.colors;
  @{alias}{
    font-size: 1rem;
  }
}

输出:

.box #library.colors {
  font-size: 1rem;
}

八、分离规则集

8.1 将规则集分配给变量

发布于 v1.7.0

分离的规则集是一组 css 属性、嵌套规则集、媒体声明或存储在变量中的任何其他内容。你可以将它包含到规则集或其他结构中,并且它的所有属性都将被复制到那里。你还可以将它用作混入参数并将其作为任何其他变量传递。

简单示例:

// 声明分离规则集
@detached-ruleset: {
  background: red;
}; // 分号在 3.5.0+ 中是可选的

// 使用分离规则集
.box {
  @detached-ruleset();
}

编译成:

.box {
  background: red;
}

分离规则集调用后的括号是强制性的(除非后跟查找值)。调用 @detached-ruleset; 不起作用。

当想要定义一个来抽象出封装媒体查询中的一段代码或不支持的浏览器类名时,它很有用。可以将规则集传递给混入,以便混入可以封装内容,例如:

.desktop-and-old-ie(@rules) {
  @media screen and (min-width: 1200px) {
    @rules();
  }
  
  html.lt-ie9 & {
    // 针对 IE8 及以下版本添加额外的样式
    @rules();
  }
}

header {
  background-color: blue;

  .desktop-and-old-ie({
    background-color: red;
  });
}

这里的 desktop-and-old-ie 混入定义了媒体查询和根类,这样就可以使用混入来封装一段代码。这将输出:

header {
  background-color: blue;
}
@media screen and (min-width: 1200px) {
  header {
    background-color: red;
  }
}
html.lt-ie9 header {
  background-color: red;
}

现在可以将规则集分配给变量或传递给混合,并且可以包含完整的 Less 功能集,例如:

@my-ruleset: {
  .my-selector {
    background-color: #000;
  }
}

例如,甚至可以利用媒体查询冒泡

@my-ruleset: {
  .my-selector {
    background-color: #000;

    @media tv {
      background-color: #888;
    }
  }
}

@media(orientation: portrait) {
  @my-ruleset();
}

输出:

@media (orientation: portrait) {
  .my-selector {
    background-color: #000;
  }
}
@media (orientation: portrait) and tv {
  .my-selector {
    background-color: #888;
  }
}

一个分离的规则集调用解锁(返回)它所有的混入到调用者,就像混入调用一样。但是,它不返回变量。

// 带有混入的分离规则集
@detached-ruleset: {
  font-size: 50px;
  .mixin() {
    color: blue;
  }
}

// 调用分离的规则集
.caller {
  @detached-ruleset();
  .mixin();
}

结果是:

.caller {
  font-size: 50px;
  color: blue;
}

私有变量:

@detached-ruleset: {
  // 私有变量
  @color: blue;
};

.caller {
  // 语法错误
  color: @color;
}

8.2 作用域

分离的规则集可以在定义和调用的地方使用所有可访问的变量和混合。换句话说,定义和调用者作用域都对它可用。如果两个作用域包含相同的变量或混入,则声明作用域值优先。

声明作用域是定义分离规则集主体的作用域。将分离的规则集从一个变量复制到另一个变量不能修改其作用域。规则集不会仅仅通过在新作用域内被引用而获得对新作用域的访问权。

最后,分离的规则集可以通过解锁(导入)到其中来访问作用域。

注意

通过调用混入将变量解锁到作用域内已被弃用。使用属性/变量访问器。

8.2.1 定义和调用者作用域可见性

一个分离的规则集可以看到调用者的变量和混合:

@detached-ruleset: {
  // 此处未定义的变量
  caller-variable: @caller-variable;
  // 此处未定义的混入
  .caller-mixin();
}

selector {
  // 使用分离规则集
  @detached-ruleset();

  // 定义分离规则集中所需的变量和混入
  @caller-variable: value;
  .caller-mixin() {
    variable: declaration;
  }
}

编译成:

selector {
  caller-variable: value;
  variable: declaration;
}

从定义中可访问的变量和混入胜过调用者中可用的那些:

@variable: global;
@detached-ruleset: {
  // 使用全局变量,因为从定义的分离规则集中是可访问的
  variable: @variable; 
};

selector {
  @detached-ruleset();
  // 在调用器中定义的变量将被忽略
  @variable: value; 
}

编译成:

selector {
  variable: global;
}

8.2.2 引用不会修改分离的规则集作用域

规则集不会仅仅通过在此处被引用而获得对新作用域的访问权限:

@detached-1: {
  // 此处编译错误:@one 是未定义的
  scope-detached: @one @two;
};

.one {
  @one: visible;

  .two {
    // 复制/重命名规则集
    @detached-2: @detached-1;
    // 规则集中此变量不可见
    @two: visible;
  }
}

.use-place {
  .one>.two();
  @detached-2();
}

抛出错误:

variable @one is undefined

8.2.3 导入将修改分离的规则集作用域

分离的规则集通过在作用域内解锁(导入)来获得访问权限:

#space {
  .importer-1() {
    // 定义分离规则集
    @detached: {
      scope-detached: @variable;
    };
  }
}

.importer-2() {
  // 未锁定的分离规则集可以看到这个变量
  @variable: value;
  // 解锁/导入分离规则集
  #space > .importer-1();
}

.use-place{
  // 第二次解锁/导入分离规则集
  .importer-2();
  @detached();
}

编译成:

.use-place {
  scope-detached: value;
}

8.3 属性/变量访问器

8.3.1 查找值

发布于 v3.5.0

从 Less 3.5 开始,可以使用属性/变量访问器(也称为 "lookups")从变量(分离的)规则集中选择一个值。

@config: {
  option1: true;
  option2: false;
}

.mixin() when (@config[option1] = true) {
  selected: true;
}

.box {
  .mixin();
}

输出:

.box {
  selected: true;
}

如果查找返回的是另一个分离的规则集,你可以使用第二个查找来获取该值。

@config: {
  @colors: {
    primary: blue;
  }
}

.box {
  color: @config[@colors][primary];
}

输出:

.box {
  color: blue;
}

8.3.2 查找中的可变变量

返回的查找值本身可以是可变的。比如,可以这样:

@config: {
  @dark: {
    primary: darkblue;
  }
  @light: {
    primary: lightblue;
  }
}

.box {
  @lookup: dark;
  color: @config[@@lookup][primary];
}

输出:

.box {
  color: darkblue;
}

九、映射

发布于 v3.5.0

使用规则集和混入作为值映射

通过将命名空间与查找 [] 语法相结合,你可以将你的规则集/混入转换为映射。

@sizes: {
  mobile: 320px;
  tablet: 768px;
  desktop: 1024px;
}

.navbar {
  display: block;

  @media (min-width: @sizes[tablet]) {
    display: inline-block;
  }
}

输出:

.navbar {
  display: block;
}
@media (min-width: 768px) {
  .navbar {
    display: inline-block;
  }
}

由于命名空间和重载混入的能力,混入比映射更通用。

#library {
  .colors() {
    primary: green;
    secondary: blue;
  }
}

#library {
  .colors() {
    primary: grey;
  }
}

.button {
  color: #library.colors[primary];
  border-color: #library.colors[secondary];
}

输出:

.button {
  color: grey;
  border-color: blue;
}

你也可以通过混入别名使这更容易。那是:

.button {
  @colors: #library.colors();
  color: @colors[primary];
  border-color: @colors[secondary];
}

请注意,如果查找值生成另一个规则集,你可以附加第二个 [] 查找,如:

@config: {
  @options: {
    library-on: true;
  }
}

& when (@config[@options][library-on] = true) {
  .produce-ruleset {
    color: red;
  }
}

输出:

.produce-ruleset {
  color: red;
}

这样规则集和变量调用就可以模拟出 "命名空间" 的一种类型,类似于混入。

至于是否使用分配给变量的混合或规则集作为映射,由你决定。你可能希望通过重新声明分配给规则集的变量来替换整个映射。或者你可能想要 "合并" 单独的键/值对,在这种情况下,混入作为映射可能更合适。

9.1 在查找中使用可变变量

需要注意的一件重要事情是 [@lookup] 中的值是键(变量)名称 @lookup,并且未作为变量求值。如果希望键名本身是可变的,可以使用 @@variable 语法。

.foods() {
  @dessert: icecream;
}

@key-to-lookup: dessert;

.lunch {
  treat: .foods[@@key-to-lookup];
}

输出:

.lunch {
  treat: icecream;
}

十、作用域

Less 的一些额外的作用域特性

10.1 混入作用域特性

直观地,混入可以访问定义作用域。

#ns {
  @a: one;
  .mixin-1() {
    prop: @a;
  }
}

.rule {
  #ns.mixin-1();
}

输出:

.rule {
  prop: one;
}

10.2 弃用的混入作用域特性

这是可能在未来版本中删除的混入作用域功能列表。

10.2.1 (已弃用)混入可以访问调用者作用域

#ns {
  .mixin-1() {
    prop: @a;
  }
}

.rule {
  @a: one;
  // (已弃用)混入可以访问调用者作用域
  #ns.mixin-1();
}

输出:

.rule {
  prop: one;
}

这是违反直觉的,因为:

  1. 这在大多数其他语言中并不典型。
  2. 在查看定义时,混入将产生什么输出并不是很明显。

首选方法: 传入希望对混入可见的变量。

#ns {
  .mixin-1(@a) {
    prop: @a;
  }
}
.rule {
  #ns.mixin-1(@a: one);
}

10.2.2.(已弃用)调用者作用域可以访问混入中的变量

混入会将它们的变量推送到调用者作用域,但前提是该变量未在本地定义。

#ns {
  .mixin-1() {
    @a: one;
    @b: two;
  }
}

.rule {
  @b: three;
  #ns.mixin-1();
  prop-1: @a;
  prop-2: @b;
}

输出:

.rule {
  prop-1: one;
  prop-2: three;
}

这是违反直觉的,因为:

  1. 调用者作用域中更高的变量可以被覆盖。
  2. 这也不是典型的语言行为。
  3. 它不同于分离规则集的行为。

此外,随着映射的引入,你可以直接检索变量值(和混合)。

首选方法:

#ns {
  .mixin-1() {
    @a: one;
    @b: two;
  }
}

.rule {
  @returns: #ns.mixin-1();
  prop-1: @returns[@a];
  prop-2: @returns[@b];
}

输出:

.rule {
  prop-1: one;
  prop-2: two;
}

10.2.3.(已弃用)调用者作用域可以从混入访问混入

与弃用的变量行为类似,混入也被推入调用者作用域。然而,与变量不同的是,与合并作用域混入同名的混入被合并。

#ns {
  .mixin-1() {
    prop-1: one;
    prop-2: two;
  }
}

.rule {
  #ns();
  .mixin-1();
  .mixin-1() {
    prop-3: three;
  }
}

输出:

.rule {
  prop-1: one;
  prop-2: two;
  prop-3: three;
}

首选方法: 直接调用混入。

#ns {
  .mixin-1() {
    prop-1: one;
    prop-2: two;
  }
}

.rule {
  .mixin-1() {
    prop-3: three;
  }
  #ns.mixin-1 ();
  .mixin-1();
}

输出:

.rule {
  prop-1: one;
  prop-2: two;
  prop-3: three;
}

10.3 提示与技巧

issue: less/less.js/issues/1472open in new window

这里有一个技巧,用于定义变量并将它们保存在一些私有作用域内,以防止它们泄漏到全局空间。

& {
  // 变量
  @height: 100px;
  @width: 20px;
  // 不要在这个作用域中定义任何属性值(因为这样做会生成(错误的)输出)
  // font-size: 1px;

  .test {
    height: @height;
    width: @width;
  }
}

.rest {
  // 名称错误: 变量 @height 未定义
  // height: @height; 
}

此处,@height@width 仅为 & { ... } 创建的作用域定义。你还可以在规则中嵌套作用域:

.some-module {
  @height: 200px;
  @width: 200px;
  text-align: left;
  line-height: @height; // 200px

  & {
    // 覆盖原始值
    @height: 100px;
    @width: auto;

    .some-module__element {
      height: @height; // 100px
      width: @width; // auto
    }

    .some-module__element .text {
      line-height: (@height / 2); // 50px
    }
  }

  & {
    // 覆盖原始值
    @height: 50px;

    .some-module__another-element {
      height: @height; // 50px
      width: @width; // 200px
    }

    .some-module__another-element .text {
      line-height: (@height / 2); // 25px
    }
  }
}

输出:

.some-module {
  text-align: left;
  line-height: 200px;
}
.some-module .some-module__element {
  height: 100px;
  width: auto;
}
.some-module .some-module__element .text {
  line-height: 50px;
}
.some-module .some-module__another-element {
  height: 50px;
  width: 200px;
}
.some-module .some-module__another-element .text {
  line-height: 25px;
}

十一、CSS 守卫

"if" 的周围选择器

发布于 v1.5.0

与混入守卫一样,守卫也可以应用于 css 选择器,这是声明混入,然后立即调用它的语法糖。

例如,在 1.5.0 之前你必须这样做:

@my-option: true;

.my-optional-style() when (@my-option =true) {
  button {
    color: white;
  }
}

.my-optional-style();

现在,可以将守卫直接应用于样式。

@my-option: true;

button when (@my-option = true) {
  color: white;
}

还可以通过将其与 & 功能相结合来实现 if 类型声明,从而允许对多个守卫进行分组。

@my-option: true;

& when (@my-option =true) {
  button {
    color: white;
  }
  a {
    color: blue;
  }
}

请注意,还可以通过使用实际的 if() 函数和变量调用来实现类似的模式。如:

@my-option: true;

@dr: if(@my-option = true, {
  button {
    color: white;
  }
  a {
    color: blue;
  }
});
@dr();

十二、@plugin 规则

发布于 v2.5.0

导入 JavaScript 插件以添加 Less.js 功能和特性

12.1 编写第一个插件

使用 @plugin 规则类似于对 .less 文件使用 @import

@plugin 'my-plugin'; // 如果没有扩展名,自动添加 .js

由于 Less 插件在 Less 作用域内进行使用,因此插件定义可以非常简单。

my-plugin.js

// 方式一
registerPlugin({
  install: function (less, pluginManager, functions) {
    functions.add('pi', function () {
      return Math.PI;
    })
  }
})

或者可以使用 module.exports(可以在浏览器和 Node.js 中使用)。

module.export = {
  install: function (less, pluginManager, functions) {
    functions.add('pi', function () {
      return Math.PI;
    })
  }
}

请注意,其他 Node.js CommonJS 约定(如 require())在浏览器中不可用。在编写跨平台插件时请记住这一点。

可以使用插件做什么? 很多,但从基础开始。首先关注可能放入 install 函数中的内容。假设这样写:

// my-plugin.js
module.export = {
  install: function (less, pluginManager, functions) {
    functions.add('pi', function () {
      return Math.PI;
    })
  }
}
// 等等

恭喜!写了一个 Less 插件!

如果要在样式表中使用它:

@plugin 'my-plugin'; // 如果没有扩展名,自动添加 .js

.show-me-pi {
  value: pi();
}

编译:

.show-me-pi {
  value: 3.141592653589793;
}

但是,如果想将其与其他值相乘或执行其他 Less 操作,则需要返回一个正确的 Less 节点。否则样式表中的输出是纯文本(这可能适合你的目的)。

意思是,这是更正确的:

module.export = {
  install: function (less, pluginManager, functions) {
    functions.add('pi', function () {
      return new tree.Dimension(Math.PI);
    })
  }
}

注意

维度是一个带或不带单位的数字,如 "10px",它将是 less.Dimension(10, "px")。有关单位列表,请参阅 Less APIopen in new window

现在你可以在操作中使用你的函数。

@plugin 'my-plugin'; // 如果没有扩展名,自动添加 .js

.show-me-pi {
  value: pi() * 2;
}

你可能已经注意到你的插件文件有可用的全局变量,即函数注册表(functions 对象)和 less 对象。 这些是为了方便。

12.2 插件作用域

@plugin @ 规则添加的函数遵循 Less 作用域规则。这对于希望在不引入命名冲突的情况下添加功能的 Less 库作者来说非常有用。

例如,假设你有来自两个第三方库的 2 个插件,它们都有一个名为 "foo" 的函数。

lib1.js

module.export = {
  install: function (less, pluginManager, functions) {
    functions.add('foo', function () {
      return 'foo';
    });
  }
}

/* registerPlugin({
  install: function (less, pluginManager, functions) {
    functions.add('foo', function () {
      return 'foo';
    });
  }
}) */

lib2.js

module.export = {
  install: function (less, pluginManager, functions) {
    functions.add('foo', function () {
      return 'bar';
    });
  }
}

/* registerPlugin({
  install: function (less, pluginManager, functions) {
    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;
}

对于共享插件的插件作者来说,这意味着你还可以通过将它们放在特定作用域内来有效地创建私有函数。就像这样,这将导致错误:

.el {
    @plugin "lib1";
}
@value: foo();

Less 3.0 开始,函数可以返回任何类型的 Node 类型,并且可以在任何级别调用。

这意味着,这会在 2.x 中引发错误,因为函数必须是属性值或变量赋值的一部分:

.block {
  color: blue;
  @plugin "lib2";
  foo();
}

3.x 中,情况不再如此,函数可以返回 At-RulesRulesets、任何其他 Less 节点、字符串和数字(后两者被转换为 Anonymous 节点)。

12.3 空函数

有时你可能想要调用一个函数,但你不想要任何输出(例如存储一个值供以后使用)。在这种情况下,你只需要从函数返回 false

collections.js

var collection = [];

registerPlugin({
  install: function (less, pluginManager, functions) {
    functions.add('store', function (arg) {
      // 存储起来之后用
      collection.push(arg);
      return false;
    });
  }
})
@plugin 'collections';

@var: 32;
store(@var);

稍后你可以执行以下操作:

var collection = [];

registerPlugin({
  install: function (less, pluginManager, functions) {
    // 省略 store 函数

    // 检索存储的值
    functions.add('retrieve', function (arg) {
      return new tree.Value(collection);
    })
  }
})
.get-my-values {
  @plugin "collections";
  values: retrieve();   
}

编译:

.get-my-values {
  values: 32;
}

12.4 Less.js 插件对象

Less.js 插件应该导出具有一个或多个这些属性的对象。

// Less.js 插件对象
registerPlugin({
  // 插件首次被导入时立即调用,只调用一次
  install: function (less, pluginManager, functions) {},

  // 为 @plugin 的每个实例调用。
  use: function(context) {},

  // 当规则被评估时,为 @plugin 的每个实例调用。它只是在评估生命周期的后期
  eval: function(context) {},

  // 传递一个任意字符串给你的插件,例如 @plugin(args) 文件;这个字符串没有解析,所以它可以包含(几乎)任何东西
  setOptions: function(argumentString) {},

  // 设置最小兼容性版本字符串,也可以使用数组,如[3, 0]
  minVersion: ['3.0'],

  // 仅用于 less,用于解释终端中的选项
  printUsage: function() {},
})

install() 函数的 PluginManager 实例提供了添加访问者、文件管理器和后处理器的方法。

这里有一些示例仓库,显示了不同的插件类型。

12.5 预加载插件

虽然 @plugin 调用适用于大多数情况,但有时你可能希望在解析开始之前加载插件。

查阅:"使用 Less.js" 部分中的 预加载插件open in new window 介绍了如何做到这一点。