LESS 完全指南------从入门到工程实践
一篇覆盖 LESS 全核心特性、配以完整可运行示例与真实场景解析的深度技术博客。
目录
- [什么是 CSS 预处理器](#什么是 CSS 预处理器)
- [LESS 简介与核心概念](#LESS 简介与核心概念)
- 2.1 学习脉络:从语法到页面工程
- 2.2 语法模块与实战模块对照
- 2.3 官方文档与延伸阅读
- [LESS 的编译方式](#LESS 的编译方式)
- 3.1 [使用 less.js 在浏览器端编译](#使用 less.js 在浏览器端编译)
- 3.2 [使用 VSCode 插件自动编译](#使用 VSCode 插件自动编译)
- 3.3 [使用 Node.js CLI 编译](#使用 Node.js CLI 编译)
- 3.4 [LESS 编译原理深度解析](#LESS 编译原理深度解析)
- [LESS 核心语法详解](#LESS 核心语法详解)
- 4.1 注释
- 4.2 变量
- 4.3 混合(Mixins)
- 4.4 条件判断(Guards)
- 4.5 导入(Import)
- 4.6 嵌套(Nesting)
- 4.7 运算符(Operations)
- 4.8 内置函数(Functions)
- 4.9 命名空间混合与混合嵌套
- 4.10 图片路径与资源引用
- 4.11 [高级特性:prop、变量变量、:extend()、属性合并与递归循环](#高级特性:prop、变量变量、:extend()、属性合并与递归循环)
- [LESS 知识脉络总结](#LESS 知识脉络总结)
- 5.5 [特性 × 真实网站应用矩阵](#特性 × 真实网站应用矩阵)
- [PC 项目实战:用 LESS 构建完整页面](#PC 项目实战:用 LESS 构建完整页面)
- 6.0 [工程准备(container 与公共混合)](#工程准备(container 与公共混合))
- 经典应用场景与真实网站案例
- 7.1 主题换肤系统
- 7.2 响应式栅格系统
- 7.3 [按钮组件库(Mixin 模式)](#按钮组件库(Mixin 模式))
- 7.4 表单组件系统
- 7.5 导航菜单与下拉菜单
- 7.6 卡片组件与骨架屏
- 7.7 提示工具(Tooltip)与气泡确认框
- 7.8 加载状态与进度条
- 7.9 模态框(Modal)弹窗系统
- 7.10 标签页(Tabs)与手风琴(Accordion)
- [LESS vs SASS/SCSS 对比](#LESS vs SASS/SCSS 对比)
- 工程最佳实践与文件组织
- 9.5 [Design Token 命名体系](#Design Token 命名体系)
- 9.6 [LESS 与 CSS 自定义属性混合策略](#LESS 与 CSS 自定义属性混合策略)
- 9.7 [Vite 项目集成 LESS](#Vite 项目集成 LESS)
- 常见问题与避坑指南
- 总结
1 什么是 CSS 预处理器
名词解释
| 术语 | 说明 |
|---|---|
| CSS 预处理器 | 一种在 CSS 之上的脚本语言扩展,通过编译器将其转换为标准 CSS。提供变量、函数、混合、嵌套等编程能力。 |
| 编译(Compile) | 将预处理器语言(.less / .scss)翻译为浏览器可直接解析的 .css 文件的过程。 |
| Source Map | 编译产物与源文件之间的映射文件,供浏览器 DevTools 调试时定位原始 .less 位置。 |
| AST(抽象语法树) | 编译器将源代码解析后形成的树形数据结构,是代码转换的中间表示。 |
为什么需要 CSS 预处理器
原生 CSS 存在以下局限:
- 无变量:修改主题色需要全文件搜索替换
- 无嵌套:父子关系选择器需要重复书写父选择器
- 无复用:相同的一组属性无法抽取为"函数"
- 无运算:尺寸计算必须手工完成,难以响应式自适应
CSS 预处理器通过引入编程范式,极大提升了样式代码的可维护性和开发效率。
主流 CSS 预处理器
#mermaid-svg-WROw4812nreltf8I{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WROw4812nreltf8I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WROw4812nreltf8I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WROw4812nreltf8I .error-icon{fill:#552222;}#mermaid-svg-WROw4812nreltf8I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WROw4812nreltf8I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WROw4812nreltf8I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WROw4812nreltf8I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WROw4812nreltf8I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WROw4812nreltf8I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WROw4812nreltf8I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WROw4812nreltf8I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WROw4812nreltf8I .marker.cross{stroke:#333333;}#mermaid-svg-WROw4812nreltf8I svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WROw4812nreltf8I p{margin:0;}#mermaid-svg-WROw4812nreltf8I .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WROw4812nreltf8I .cluster-label text{fill:#333;}#mermaid-svg-WROw4812nreltf8I .cluster-label span{color:#333;}#mermaid-svg-WROw4812nreltf8I .cluster-label span p{background-color:transparent;}#mermaid-svg-WROw4812nreltf8I .label text,#mermaid-svg-WROw4812nreltf8I span{fill:#333;color:#333;}#mermaid-svg-WROw4812nreltf8I .node rect,#mermaid-svg-WROw4812nreltf8I .node circle,#mermaid-svg-WROw4812nreltf8I .node ellipse,#mermaid-svg-WROw4812nreltf8I .node polygon,#mermaid-svg-WROw4812nreltf8I .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WROw4812nreltf8I .rough-node .label text,#mermaid-svg-WROw4812nreltf8I .node .label text,#mermaid-svg-WROw4812nreltf8I .image-shape .label,#mermaid-svg-WROw4812nreltf8I .icon-shape .label{text-anchor:middle;}#mermaid-svg-WROw4812nreltf8I .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WROw4812nreltf8I .rough-node .label,#mermaid-svg-WROw4812nreltf8I .node .label,#mermaid-svg-WROw4812nreltf8I .image-shape .label,#mermaid-svg-WROw4812nreltf8I .icon-shape .label{text-align:center;}#mermaid-svg-WROw4812nreltf8I .node.clickable{cursor:pointer;}#mermaid-svg-WROw4812nreltf8I .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WROw4812nreltf8I .arrowheadPath{fill:#333333;}#mermaid-svg-WROw4812nreltf8I .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WROw4812nreltf8I .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WROw4812nreltf8I .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WROw4812nreltf8I .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WROw4812nreltf8I .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WROw4812nreltf8I .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WROw4812nreltf8I .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WROw4812nreltf8I .cluster text{fill:#333;}#mermaid-svg-WROw4812nreltf8I .cluster span{color:#333;}#mermaid-svg-WROw4812nreltf8I div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WROw4812nreltf8I .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WROw4812nreltf8I rect.text{fill:none;stroke-width:0;}#mermaid-svg-WROw4812nreltf8I .icon-shape,#mermaid-svg-WROw4812nreltf8I .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WROw4812nreltf8I .icon-shape p,#mermaid-svg-WROw4812nreltf8I .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WROw4812nreltf8I .icon-shape .label rect,#mermaid-svg-WROw4812nreltf8I .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WROw4812nreltf8I .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WROw4812nreltf8I .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WROw4812nreltf8I :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CSS 预处理器
LESS
SASS / SCSS
Stylus
Bootstrap v3 采用
Bootstrap v4+ 采用
Vue CLI 默认支持
Vue.js 早期版本
2 LESS 简介与核心概念
LESS (Leaner Style Sheets)是一种向后兼容 CSS 的样式语言扩展,由 Alexis Sellier 于 2009 年创建,后由 Less.js 核心团队维护。官方网站:https://lesscss.org,中文文档:https://less.bootcss.com。
LESS 的核心特性概览
#mermaid-svg-VuzSpb5SLzwwmO0A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VuzSpb5SLzwwmO0A .error-icon{fill:#552222;}#mermaid-svg-VuzSpb5SLzwwmO0A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VuzSpb5SLzwwmO0A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VuzSpb5SLzwwmO0A .marker.cross{stroke:#333333;}#mermaid-svg-VuzSpb5SLzwwmO0A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VuzSpb5SLzwwmO0A p{margin:0;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge{stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 text{fill:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth--1{stroke-width:17;}#mermaid-svg-VuzSpb5SLzwwmO0A .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-0{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-0{stroke-width:14;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-1{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-1{stroke-width:11;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 text{fill:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-2{stroke-width:8;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-3{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-3{stroke-width:5;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-4{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-4{stroke-width:2;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-5{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-5{stroke-width:-1;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-6{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-6{stroke-width:-4;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-7{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-7{stroke-width:-7;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-8{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-8{stroke-width:-10;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-9{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-9{stroke-width:-13;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 polygon,#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 text{fill:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .node-icon-10{font-size:40px;color:black;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .edge-depth-10{stroke-width:-16;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled circle,#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:lightgray;}#mermaid-svg-VuzSpb5SLzwwmO0A .disabled text{fill:#efefef;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-root rect,#mermaid-svg-VuzSpb5SLzwwmO0A .section-root path,#mermaid-svg-VuzSpb5SLzwwmO0A .section-root circle,#mermaid-svg-VuzSpb5SLzwwmO0A .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-VuzSpb5SLzwwmO0A .section-root text{fill:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-root span{color:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .section-2 span{color:#ffffff;}#mermaid-svg-VuzSpb5SLzwwmO0A .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-VuzSpb5SLzwwmO0A .edge{fill:none;}#mermaid-svg-VuzSpb5SLzwwmO0A .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-VuzSpb5SLzwwmO0A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LESS
变量 Variables
属性值变量
选择器变量
属性名变量
媒体查询变量
混合 Mixins
无参混合
有参混合
默认值混合
arguments 关键字
嵌套 Nesting
层级选择器
& 父引用
媒体查询嵌套
运算 Operations
加减乘除
单位处理
函数 Functions
颜色函数
数学函数
字符串函数
条件 Guards
when 条件
多重条件
导入 Import
LESS 文件导入
CSS 文件导入
2.1 学习脉络:从语法到页面工程
掌握 LESS 的推荐路径是:先能编译 → 再掌握变量与混合 → 用嵌套与导入组织工程 → 用 Guards 与函数处理复杂 UI → 最后落到完整 PC 页面模块。
【代码注释】
核心逻辑
- 学习路径分三层:编译 (LESS→CSS)→ 语法 (变量/混合/嵌套)→ 工程 (
@import拆分 + PC 页面模块)。 - Mermaid 流程图从
less.js/ Easy Less /lessc入口,经语法模块,落到variables.less→main.less工程链。
关键概念
| 层 | 解决的问题 |
|---|---|
| 编译层 | 源文件如何变成浏览器可读的 .css |
| 语法层 | 如何消除重复选择器、硬编码颜色 |
| 工程层 | 多人协作时文件如何拆分与入口汇总 |
注意点
- 生产环境禁止 依赖浏览器
less.js实时编译(阻塞渲染、体积大)。 - 先掌握 VSCode Easy Less 或 Vite/Webpack 预编译,再写复杂页面。
实战场景
- 个人练手:Easy Less 保存即出 CSS;团队项目:Vite
preprocessorOptions.less+additionalData全局注入变量。
2.2 语法模块与实战模块对照
| 序号 | 学习主题 | 核心语法点 | 典型产出 |
|---|---|---|---|
| 01 | 编译 LESS | type="text/less"、less.js、Easy Less |
得到可引用的 .css |
| 02 | 注释 | /* */ vs // |
可维护的模块说明 |
| 03 | 变量 | @var、@{prop}、@{sel}、转义 ~"..." |
主题色、断点、动态选择器 |
| 04 | 混合 | 无参/有参/默认值、@arguments |
居中、清除浮动、按钮皮肤 |
| 05 | 条件判断 | when、多方向三角形 |
下拉箭头、自适应间距 |
| 06 | 导入 | @import、导入顺序 |
多文件工程入口 |
| 07 | 嵌套 | 后代/子代/兄弟、& |
导航、列表、状态类 |
| 08 | 混合+嵌套 | 命名空间、&::after |
组件库内部组织 |
| 09 | 运算符 | + - * /、percentage() |
栅格宽度、响应式尺寸 |
| 10 | 函数 | darken、fade、mix |
色阶、半透明遮罩 |
| 00 | 工程准备 | .container、@import 链 |
统一版心与重置 |
| 01 | Topbar | Flex、&:not(:last-child) |
顶部链接栏 |
| 02 | Logo+搜索 | 浮动/布局、darken() |
品牌区与搜索框 |
| 03 | 页面导航 | 嵌套 li、&.active |
主导航高亮 |
2.3 官方文档与延伸阅读
| 来源 | 地址 | 适合查阅的内容 |
|---|---|---|
| LESS 官方 | lesscss.org | 语法、语言特性、变更说明 |
| LESS 中文站 | less.bootcss.com | 中文语法说明、函数列表 |
| 函数参考 | less.bootcss.com/functions | 颜色/数学/字符串函数 |
| MDN @import | developer.mozilla.org CSS @import | 编译后 CSS 中 @import 的行为 |
| Bootstrap 3 LESS 源码 | 开源仓库中的 less/ 目录 |
大型项目如何用变量+混合+栅格组织样式 |
【代码注释】
核心逻辑
- 对照表把「课堂序号」映射到「语法点」与「页面产出」(Topbar、Logo、导航等),便于按模块复习。
- 官方文档负责 API 准确性;Bootstrap 3
less/源码负责工程组织范例。
阅读 Bootstrap 源码时重点看
| 关注点 | 原因 |
|---|---|
变量命名 @brand-primary |
Design Token 雏形 |
Mixin 是否带 () |
区分「仅复用」与「输出类」 |
| 嵌套深度 | 超过 3~4 层难维护 |
bootstrap.less 入口 |
@import 顺序即依赖关系 |
实战场景
- 面试「如何组织大型 LESS 项目」→ 答 variables + mixins + components + 单一入口编译。
3 LESS 的编译方式
3.1 使用 less.js 在浏览器端编译
这是最简单的上手方式,适合快速原型验证,不推荐用于生产环境(每次页面加载都需要实时编译,性能较差)。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 浏览器端编译示例</title>
<!--
关键:type 必须写 "text/less",而非 "text/css"
浏览器不会直接解析,由 less.js 接管编译
-->
<style type="text/less">
/* 定义变量 */
@primary-color: #1a73e8;
@font-size-base: 16px;
@border-radius: 4px;
/* 使用变量 */
.card {
width: 320px;
padding: 20px;
border: 1px solid @primary-color;
border-radius: @border-radius;
font-size: @font-size-base;
color: @primary-color;
.card-title {
font-size: (@font-size-base * 1.25);
font-weight: bold;
margin-bottom: 12px;
}
.card-body {
line-height: 1.6;
color: #333;
}
}
</style>
<!-- less.js 必须在所有 type="text/less" 的 style 标签之后引入 -->
<script src="./less.js"></script>
</head>
<body>
<div class="card">
<div class="card-title">卡片标题</div>
<div class="card-body">这是卡片正文内容,演示 LESS 浏览器端实时编译。</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
<style type="text/less">内写 LESS 语法(变量、嵌套)。- 页面底部引入
less.js,DOM Ready 后扫描并编译,动态插入<style>标准 CSS。 - 浏览器最终解析的是编译后的 CSS,而非
.less原文。
关键 API
type="text/less":必须写对,写成text/css则变量语法无效。@变量名:编译期替换为字面量,如@primary-color→#1a73e8。
注意点
less.js必须放在所有text/less的 style 之后。- 嵌套
.card-title会展开为.card .card-title,与原生 CSS 等价。
实战场景
- 课堂演示、CodePen 快速原型;勿用于生产(首屏需等 JS 编译,SEO/性能差)。
3.2 使用 VSCode 插件自动编译
这是开发阶段最推荐的工作流,零配置,保存即编译。
步骤:
- 在 VSCode 扩展市场搜索并安装 Easy Less
- 新建
style/index.less文件,编写 LESS 代码 - 每次保存,Easy Less 自动在同目录生成
index.css - HTML 中引用编译后的 CSS 文件
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>VSCode 插件编译示例</title>
<!-- 引用编译后的 CSS,而非 .less 源文件 -->
<link rel="stylesheet" href="./style/index.css">
</head>
<body>
<div id="box">
<div class="item">子元素</div>
<p>段落文字</p>
</div>
</body>
</html>
对应的 style/index.less:
less
@fcolor: #900; /* 前景色(文字颜色) */
@bcolor: #099; /* 背景色 */
@len: 1000px; /* 容器主尺寸 */
#box {
margin-top: 40px;
padding: 20px;
width: @len;
height: (@len / 4); /* 运算:高度为宽度的四分之一 */
color: @fcolor;
background: @bcolor;
/* 嵌套:子元素选择器 */
.item {
width: 600px;
}
p {
padding: 20px;
}
}
编译后的 CSS:
css
#box {
margin-top: 40px;
padding: 20px;
width: 1000px;
height: 250px;
color: #900;
background: #099;
}
#box .item {
width: 600px;
}
#box p {
padding: 20px;
}
【代码注释】
核心逻辑
- 开发时写
index.less,保存后 Easy Less 调用lessc生成同目录index.css。 - HTML 只链接
.css,浏览器不直接接触 LESS 源文件。
关键语法(本例)
| LESS | 编译后 CSS | 说明 |
|---|---|---|
@len: 1000px |
width: 1000px |
变量替换 |
height: (@len / 4) |
height: 250px |
括号内才做除法 |
.item { ... } 嵌套在 #box 内 |
#box .item { ... } |
后代选择器展开 |
注意点
- 除法
(@len / 4)不加括号时,LESS 可能把/当 CSS 分隔符(见 §10.1)。 - 修改 LESS 后需保存才会更新 CSS;部署时只上传 CSS。
实战场景
- 尚硅谷 PC 项目、静态页教学:本地 Easy Less + 引用
style/index.css。
3.3 使用 Node.js CLI 编译
适合构建流水线(CI/CD)或与 Webpack/Vite 集成。
bash
# 全局安装 lessc
npm install -g less
# 编译单文件
lessc input.less output.css
# 编译并压缩输出
lessc --clean-css input.less output.min.css
# 生成 Source Map(方便调试)
lessc --source-map input.less output.css
Webpack 集成(项目中常见配置):
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader', // 将 CSS 注入 DOM
'css-loader', // 处理 @import 和 url()
'less-loader', // 将 LESS 编译为 CSS
],
},
],
},
};
【代码注释】
核心逻辑
lessc input.less output.css:命令行单次编译,适合 CI/CD。- Webpack/Vite 在构建阶段调用 LESS 编译器,产物打入
dist/。
Webpack Loader 链(从右到左执行)
.less 文件 → less-loader(编译)→ css-loader(处理 @import/url)→ style-loader(注入 DOM)
关键命令
| 命令 | 作用 |
|---|---|
lessc a.less b.css |
基础编译 |
lessc --clean-css |
压缩输出 |
lessc --source-map |
生成 .map 便于 DevTools 定位 .less 行号 |
注意点
- 生产用
MiniCssExtractPlugin提取独立 CSS,不用style-loader内联到 JS。 - Vite 只需
npm i -D less,无需less-loader(见 §9.7)。
实战场景
- Jenkins/GitHub Actions 构建前
lessc;React/Vue 项目用构建工具内置管道。
3.4 LESS 编译原理深度解析
了解编译器内部原理,有助于排查奇怪的编译行为、理解变量作用域与懒加载的本质。
编译管线:从 .less 到 .css
#mermaid-svg-XfwDPYx892JkWDnN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XfwDPYx892JkWDnN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XfwDPYx892JkWDnN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XfwDPYx892JkWDnN .error-icon{fill:#552222;}#mermaid-svg-XfwDPYx892JkWDnN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XfwDPYx892JkWDnN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XfwDPYx892JkWDnN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XfwDPYx892JkWDnN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XfwDPYx892JkWDnN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XfwDPYx892JkWDnN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XfwDPYx892JkWDnN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XfwDPYx892JkWDnN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XfwDPYx892JkWDnN .marker.cross{stroke:#333333;}#mermaid-svg-XfwDPYx892JkWDnN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XfwDPYx892JkWDnN p{margin:0;}#mermaid-svg-XfwDPYx892JkWDnN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XfwDPYx892JkWDnN .cluster-label text{fill:#333;}#mermaid-svg-XfwDPYx892JkWDnN .cluster-label span{color:#333;}#mermaid-svg-XfwDPYx892JkWDnN .cluster-label span p{background-color:transparent;}#mermaid-svg-XfwDPYx892JkWDnN .label text,#mermaid-svg-XfwDPYx892JkWDnN span{fill:#333;color:#333;}#mermaid-svg-XfwDPYx892JkWDnN .node rect,#mermaid-svg-XfwDPYx892JkWDnN .node circle,#mermaid-svg-XfwDPYx892JkWDnN .node ellipse,#mermaid-svg-XfwDPYx892JkWDnN .node polygon,#mermaid-svg-XfwDPYx892JkWDnN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XfwDPYx892JkWDnN .rough-node .label text,#mermaid-svg-XfwDPYx892JkWDnN .node .label text,#mermaid-svg-XfwDPYx892JkWDnN .image-shape .label,#mermaid-svg-XfwDPYx892JkWDnN .icon-shape .label{text-anchor:middle;}#mermaid-svg-XfwDPYx892JkWDnN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XfwDPYx892JkWDnN .rough-node .label,#mermaid-svg-XfwDPYx892JkWDnN .node .label,#mermaid-svg-XfwDPYx892JkWDnN .image-shape .label,#mermaid-svg-XfwDPYx892JkWDnN .icon-shape .label{text-align:center;}#mermaid-svg-XfwDPYx892JkWDnN .node.clickable{cursor:pointer;}#mermaid-svg-XfwDPYx892JkWDnN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XfwDPYx892JkWDnN .arrowheadPath{fill:#333333;}#mermaid-svg-XfwDPYx892JkWDnN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XfwDPYx892JkWDnN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XfwDPYx892JkWDnN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XfwDPYx892JkWDnN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XfwDPYx892JkWDnN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XfwDPYx892JkWDnN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XfwDPYx892JkWDnN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XfwDPYx892JkWDnN .cluster text{fill:#333;}#mermaid-svg-XfwDPYx892JkWDnN .cluster span{color:#333;}#mermaid-svg-XfwDPYx892JkWDnN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XfwDPYx892JkWDnN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XfwDPYx892JkWDnN rect.text{fill:none;stroke-width:0;}#mermaid-svg-XfwDPYx892JkWDnN .icon-shape,#mermaid-svg-XfwDPYx892JkWDnN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XfwDPYx892JkWDnN .icon-shape p,#mermaid-svg-XfwDPYx892JkWDnN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XfwDPYx892JkWDnN .icon-shape .label rect,#mermaid-svg-XfwDPYx892JkWDnN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XfwDPYx892JkWDnN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XfwDPYx892JkWDnN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XfwDPYx892JkWDnN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} .less 源文件
词法分析
Lexer
拆解 Token
语法分析
Parser
构建 AST
变量求值
Eval
懒加载 + 作用域
规则展开
Expand
Mixin/嵌套/递归
代码生成
CodeGen
输出 CSS 字符串
.css 输出文件
Source Map
调试映射文件
各阶段详解:
| 阶段 | 核心工作 | 典型产物 |
|---|---|---|
| 词法分析(Lexer) | 将源代码字符流拆解为 Token 序列(选择器、属性、值、@符号等) | [@, primary, :, #1a73e8, ;] |
| 语法分析(Parser) | 依据 LESS 文法将 Token 序列组装成抽象语法树(AST) | 嵌套的规则树节点 |
| 变量求值(Eval) | 在作用域链中查找变量值(懒加载、就近原则)、执行运算与函数调用 | 所有 @var 替换为字面量值 |
| 规则展开(Expand) | 将嵌套规则展开为平铺 CSS 选择器;递归调用 Mixin;处理 Guards 条件 | 平铺 CSS 规则树 |
| 代码生成(CodeGen) | 将规则树序列化为 CSS 字符串;可选压缩(去空白、注释)与 Source Map 生成 | .css 字符串 |
变量作用域与懒加载机制
LESS 采用词法作用域(Lexical Scope) ,变量查找遵循"当前块 → 上级块 → 全局"的链式规则,并在整个作用域内取最后一次赋值(懒加载):
less
@size: 10px; /* 全局:10px */
.outer {
@size: 20px; /* .outer 作用域:20px(最终值) */
width: @size; /* 输出:20px(取当前作用域最终值) */
.inner {
@size: 30px; /* .inner 作用域:30px */
height: @size; /* 输出:30px */
@size: 35px; /* 懒加载:覆盖上一行,.inner 最终值 = 35px */
}
}
.standalone {
width: @size; /* 输出:10px(全局作用域) */
}
编译结果:
css
.outer { width: 20px; }
.outer .inner { height: 35px; }
.standalone { width: 10px; }
关键规则: 懒加载使 LESS 变量行为与 CSS 的层叠规则一致------"最后定义有效",而非"先声明先生效"。
Mixin 调用时的变量隔离
Mixin 拥有独立的执行作用域,内部变量不会泄漏到调用方:
less
.mixin() {
@private: 99px; /* 私有变量,不会污染调用方 */
width: @private;
}
.box {
.mixin();
/* height: @private; */ /* ❌ 报错:@private 不在此作用域内 */
}
Source Map 与浏览器调试
Source Map 是一个 JSON 文件(.css.map),记录了编译后 CSS 每一行与原始 LESS 文件的位置映射关系,使浏览器 DevTools 能直接显示 .less 文件和行号:
bash
# 生成带 Source Map 的 CSS
lessc --source-map styles.less styles.css
# 输出 styles.css + styles.css.map
# styles.css 末尾自动追加:
# /*# sourceMappingURL=styles.css.map */
生产环境建议: 不要将 Source Map 部署到线上(暴露源码),只在开发环境生成,通过 .gitignore 排除或 CI 分离处理。
【代码注释】
核心逻辑(编译五阶段)
- Lexer :字符流 → Token(
@、:、#1a73e8等)。 - Parser:Token → AST(规则树、嵌套结构)。
- Eval :变量懒加载求值、运算、
darken()等函数。 - Expand:嵌套展平、Mixin 展开、Guards 分支匹配。
- CodeGen:输出 CSS 字符串 + 可选 Source Map。
变量懒加载(与示例对应)
- 同一作用域内最后一次
@size赋值生效 →.inner最终height: 35px。 - 与 CSS 层叠「后声明覆盖」直觉一致,但发生在编译期。
Mixin 作用域隔离
- Mixin 内
@private不泄漏到.box,调用方无法引用------类似函数局部变量。
注意点
- 排查「变量为何是 Unexpected」:多为作用域外引用或循环
@import。 - 线上勿部署
.map文件,避免暴露源码目录结构。
实战场景
- DevTools 显示
.less行号依赖 Source Map;Ant Design 主题编译失败多在 Eval/Expand 阶段变量未定义。
4 LESS 核心语法详解
4.1 注释
LESS 支持两种注释语法,行为存在本质区别:
less
/* 这是 CSS 注释,会原样编译到 CSS 中 */
// 这是 LESS 注释,不会编译到 CSS 中
完整可运行示例:
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 注释演示</title>
<style type="text/less">
/* ======================
CSS 注释:会保留在编译结果中
适合写版权声明、模块说明
====================== */
// LESS 行注释:编译后消失,适合开发调试说明
.demo {
color: red; // 这行注释在 CSS 中不会出现
/* background: blue; */ /* 这行注释会保留 */
}
</style>
<script src="./less.js"></script>
</head>
<body>
<p class="demo">注释演示文字</p>
</body>
</html>
编译后的 CSS:
css
/* ======================
CSS 注释:会保留在编译结果中
适合写版权声明、模块说明
====================== */
.demo {
color: red;
/* background: blue; */
}
【代码注释】
核心逻辑
/* */:走 CSS 规范,编译后保留(压缩工具可再删)。//:LESS 专有,编译后整行消失,不会进入生产 CSS。
对比表
| 语法 | 编译后 | 适用 |
|---|---|---|
/* 模块说明 */ |
保留 | 版权、分区标题 |
// 临时禁用 |
消失 | 调试、TODO |
注意点
- 行内
//注释后的代码仍有效;块注释可包住多行规则。 cssnano等压缩默认会删/* */,与 LESS 无关。
实战场景
- Bootstrap LESS 源码用
//写说明;发布版 CSS 只留简短版权块。
真实场景: Bootstrap 4 的 LESS 源码大量使用 // 行注释做代码说明,编译产物中只保留必要的版权块注释。
4.2 变量
LESS 变量是整个预处理器体系的基石,让"一处修改,全局生效"成为可能。
名词解释
| 术语 | 说明 |
|---|---|
| 变量(Variable) | 以 @ 开头的标识符,存储可复用的值(颜色、尺寸、字体等)。 |
| 懒加载(Lazy Evaluation) | LESS 变量在整个作用域范围内都可以使用,甚至可以在定义之前引用。 |
| 作用域(Scope) | 变量的可见范围,LESS 遵循就近原则(先在当前块查找,再向上冒泡)。 |
| 转义字符串(Escape) | ~"..." 语法,用于包裹含有特殊字符或无法直接写入变量值的字符串。 |
定义与使用变量
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 变量完整演示</title>
<style type="text/less">
/* ── 1. 定义变量 ── */
@primary-color: #1a73e8; /* 主色调 */
@danger-color: #ea4a36; /* 危险/错误色 */
@success-color: #34a853; /* 成功色 */
@base-font-size: 14px; /* 基础字号 */
@container-width: 1200px; /* 容器宽度 */
/* 含有特殊字符的变量:使用转义语法 ~"..." */
@media-md: ~"min-width: 768px";
@media-lg: ~"min-width: 992px";
/* 变量用作属性名 */
@prop: background-position;
/* 变量用作选择器 */
@alert-selector: .alert-box;
/* ── 2. 变量作为属性值(最常见用法) ── */
.button {
background-color: @primary-color;
color: #fff;
font-size: @base-font-size;
padding: 8px 16px;
border: none;
border-radius: 4px;
&:hover {
background-color: darken(@primary-color, 10%);
}
}
.alert-danger {
color: @danger-color;
border: 1px solid @danger-color;
padding: 12px;
}
/* ── 3. 变量作为属性名 ── */
.sprite {
width: 32px;
height: 32px;
@{prop}: -32px 0; /* 等同于 background-position: -32px 0 */
}
/* ── 4. 变量作为选择器 ── */
@{alert-selector} {
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: 4px;
}
/* ── 5. 变量在媒体查询中使用 ── */
.container {
width: 100%;
margin: 0 auto;
@media (@media-md) {
width: 750px;
}
@media (@media-lg) {
width: @container-width;
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="container">
<button class="button">主按钮</button>
<div class="alert-danger">危险提示文字</div>
<div class="alert-box">通用提示框</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
@name: value在编译期全文替换;改一处变量,所有引用同步更新。- 四种插值:
@color值、@{prop}属性名、@{sel}选择器、~"..."转义字符串(媒体查询)。
关键 API
| 写法 | 编译示例 | 用途 |
|---|---|---|
@primary-color |
background: #1a73e8 |
主题色 |
@{prop}: -32px 0 |
background-position: ... |
雪碧图 |
@{alert-selector} { } |
.alert-box { } |
动态类名 |
@media (@media-md) |
@media (min-width: 768px) |
断点变量 |
注意点
- 媒体查询变量含
:必须用~"min-width: 768px",否则解析错误(§10.2)。 darken(@primary-color, 10%)参数是百分比不是 0~1(§10.4)。
实战场景
- Ant Design / Element UI 主题定制:覆盖
@primary-color等变量重新编译即可换肤。
变量作用域演示
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 变量作用域</title>
<style type="text/less">
@color: #333; /* 全局变量 */
.parent {
@color: #900; /* 局部变量,覆盖全局 */
color: @color; /* → #900(就近原则) */
.child {
color: @color; /* → #900(继承父级作用域) */
}
}
.sibling {
color: @color; /* → #333(使用全局变量) */
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="parent">
父元素(#900)
<div class="child">子元素(#900)</div>
</div>
<div class="sibling">兄弟元素(#333)</div>
</body>
</html>
【代码注释】
核心逻辑
- 查找顺序:当前规则块 → 父块 → 全局;就近 + 懒加载(最后赋值有效)。
.parent内@color: #900遮蔽 全局#333;.sibling仍用#333。
与 JS 对比
| LESS 变量 | JS let |
|
|---|---|---|
| 遮蔽 | ✅ 内层覆盖外层 | ✅ 块级遮蔽 |
| 编译期求值 | ✅ | 运行期 |
注意点
- 主题变量写在
.theme-blue { @primary: ... }内,子选择器自动继承该作用域(§7.1)。
实战场景
- Ant Design、Bootstrap 主题:构建时
modifyVars覆盖全局@primary-color。
真实网站场景: Ant Design、Element UI、Bootstrap 均通过 LESS/SCSS 变量系统实现主题定制。用户只需覆盖预定义的变量(如 @primary-color),即可实现整站换肤,无需修改组件样式。
4.3 混合(Mixins)
名词解释
| 术语 | 说明 |
|---|---|
| 混合(Mixin) | 可复用的样式代码块,类似编程语言中的函数。定义一次,多处调用。 |
| 带参数混合 | 调用时可以传入参数值,实现"模板化"的样式生成。 |
| 默认参数 | 混合定义时给参数指定默认值,调用时可省略该参数。 |
| @arguments | LESS 内置变量,代表混合被调用时传入的所有参数列表,按顺序排列。 |
| 命名空间(Namespace) | 将相关混合组织在一个类选择器内,用 #namespace > .mixin() 调用,避免命名污染。 |
① 无参混合
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>无参混合演示</title>
<style type="text/less">
/* 定义无参混合:绝对定位居中 */
.center-box01() {
position: absolute;
width: 400px;
height: 300px;
left: 50%;
top: 50%;
margin-left: -200px; /* 负值为宽度的一半 */
margin-top: -150px; /* 负值为高度的一半 */
}
/* 定义清除浮动混合 */
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
/* 调用混合 */
.dialog {
.center-box01(); /* 复用居中逻辑 */
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,.15);
}
.news-list {
.center-box01(); /* 同样的居中逻辑,无需重复书写 */
background: #f5f5f5;
border: 1px solid #ddd;
}
.layout {
.clearfix(); /* 清除浮动 */
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div style="position:relative;height:400px;background:#eee;">
<div class="dialog">居中弹窗</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.center-box01()定义可复用样式块;.dialog { .center-box01(); }在调用处展开 属性,不单独输出.center-box01规则。.clearfix()内&::after→ 调用者.layout::after清除浮动。
Mixin 有无括号(§10.3)
| 定义 | 是否输出为 CSS 类 | 用途 |
|---|---|---|
.mixin() |
❌ 仅当被调用时展开 | 纯复用 |
.mixin |
✅ 会生成 .mixin 类 |
既可当类又可当 mixin |
注意点
- 无参 mixin 必须 带
(),否则污染 CSS 选择器列表。 - 居中方案也可用 Flex/Grid,本例为经典负 margin 教学写法。
实战场景
- 全局
mixins/clearfix.less;弹窗居中、列表浮动布局在 PC 项目中反复调用。
② 带参数的混合
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>带参数混合演示</title>
<style type="text/less">
/* 定义带参数混合:绝对定位居中(动态尺寸) */
.center-box02(@width, @height) {
position: absolute;
width: @width;
height: @height;
left: 50%;
top: 50%;
margin-left: -(@width / 2); /* 动态计算负边距 */
margin-top: -(@height / 2);
}
/* 调用方式一:按参数顺序传值 */
.modal-sm {
.center-box02(400px, 300px);
background: #fff;
border-radius: 8px;
}
/* 调用方式二:按参数名传值(顺序可以不同) */
.modal-lg {
.center-box02(@height: 600px, @width: 900px);
background: #f0f8ff;
border: 1px solid #1a73e8;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div style="position:relative;height:700px;background:#eee;">
<div class="modal-sm">小弹窗 400×300</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.center-box02(@width, @height)形参在调用时替换;负 margin 用-(@width / 2)动态居中。- 支持位置传参
400px, 300px与按名传参@height: 600px, @width: 900px。
关键 API
- 按名传参:参数多、有默认值时可读性更好。
- 除法:
-(@width / 2)括号强制数学运算。
注意点
- 无括号
margin-left: -@width / 2可能编译异常(§10.1)。
实战场景
- 多尺寸 Modal、Banner 用同一套居中 mixin,只改宽高参数。
③ 带默认值的混合
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>带默认值混合演示</title>
<style type="text/less">
/* 参数带默认值:@width 默认 1200px */
.center-box03(@width: 1200px, @height) {
position: absolute;
width: @width;
height: @height;
left: 50%;
top: 50%;
margin-left: -(@width / 2);
margin-top: -(@height / 2);
}
/* 当有默认值的参数在前时,必须使用按名传参 */
.panel-default {
.center-box03(@height: 300px); /* @width 使用默认值 1200px */
background: #fff;
}
/* 也可以覆盖默认值 */
.panel-narrow {
.center-box03(800px, 500px); /* 覆盖 @width 为 800px */
background: #f5f5f5;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div style="position:relative;height:600px;background:#ccc;">
<div class="panel-default">默认宽度面板</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
@width: 1200px为默认参数;调用.center-box03(@height: 300px)时宽度用默认 1200,高度用 300。- 默认值在前时,后续参数必须用按名传参,否则解析歧义。
注意点
- 与 Python 默认参数规则类似:省略前面的默认参数必须用关键字形式。
- 也可
.center-box03(800px, 500px)按顺序覆盖两个参数。
实战场景
- 栅格容器默认全宽,个别面板传
@height定制高度。
④ @arguments 关键字
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>@arguments 演示</title>
<style type="text/less">
/* @arguments 自动收集所有参数,按传入顺序拼接 */
.box-shadow(@x, @y, @blur, @spread, @color) {
-webkit-box-shadow: @arguments; /* 兼容旧版 Safari/Chrome */
-moz-box-shadow: @arguments; /* 兼容旧版 Firefox */
-o-box-shadow: @arguments; /* 兼容旧版 Opera */
box-shadow: @arguments; /* 标准属性 */
}
/* 定义过渡动画混合 */
.transition(@property, @duration, @timing: ease, @delay: 0s) {
-webkit-transition: @arguments;
transition: @arguments;
}
.card {
width: 300px;
height: 200px;
background: #fff;
/* 调用 box-shadow 混合 */
.box-shadow(3px, 10px, 15px, 0px, rgba(0,0,0,.2));
/* 调用 transition 混合 */
.transition(all, .3s, ease-in-out);
}
.card:hover {
.box-shadow(6px, 15px, 25px, 0px, rgba(0,0,0,.3));
transform: translateY(-4px);
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="card">悬停查看阴影变化</div>
</body>
</html>
【代码注释】
核心逻辑
- 调用
.box-shadow(3px, 10px, 15px, 0px, rgba(...))时,@arguments在 mixin 内展开为完整实参列表。 - 各前缀属性行写
-webkit-box-shadow: @arguments等,改参数只改一处调用。
关键 API
less
.box-shadow(@x, @y, @blur, @spread, @color) {
-webkit-box-shadow: @arguments;
box-shadow: @arguments;
}
注意点
- 参数顺序必须与目标 CSS 属性值顺序一致。
- 现代项目前缀需求减少,但 Ant Design/Bootstrap 3 仍保留此模式。
实战场景
mixins/vendor-prefixes.less;transition/box-shadow工厂函数。
真实网站场景:
- Ant Design 的 LESS 混合库(
mixins.less)使用@arguments封装了box-shadow、transition、transform等跨浏览器兼容属性。 - Bootstrap 3 的
mixins/vendor-prefixes.less同样大量使用此模式处理 CSS3 前缀问题。
4.4 条件判断(Guards)
LESS 通过 when 关键字为混合添加条件守卫(Guards),实现类型级别的条件分支,这是 LESS 中实现"CSS 算法"的核心机制。
名词解释
| 术语 | 说明 |
|---|---|
| Guards(守卫) | 附加在混合定义上的条件表达式。只有条件为真时,该混合的定义才会被匹配和执行。 |
| 模式匹配(Pattern Matching) | 同名混合可以定义多个,LESS 根据参数值匹配对应的定义,类似 Haskell/Erlang 的模式匹配。 |
| 比较运算符 | 可用 >、>=、=、=<、< 进行数值比较;用 = 进行值相等比较。 |
经典用例:CSS 三角形工厂
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 条件判断------三角形生成器</title>
<style type="text/less">
/* 用 when 条件守卫实现方向分支 */
/* 向上三角:底部边有颜色,其余透明 */
.triangle(@border-width, @color, @direction) when (@direction = up) {
width: 0;
height: 0;
border-style: solid;
border-width: @border-width;
border-color: transparent transparent @color transparent;
}
/* 向下三角:顶部边有颜色 */
.triangle(@border-width, @color, @direction) when (@direction = down) {
width: 0;
height: 0;
border-style: solid;
border-width: @border-width;
border-color: @color transparent transparent transparent;
}
/* 向左三角:右侧边有颜色 */
.triangle(@border-width, @color, @direction) when (@direction = left) {
width: 0;
height: 0;
border-style: solid;
border-width: @border-width;
border-color: transparent @color transparent transparent;
}
/* 向右三角:左侧边有颜色 */
.triangle(@border-width, @color, @direction) when (@direction = right) {
width: 0;
height: 0;
border-style: solid;
border-width: @border-width;
border-color: transparent transparent transparent @color;
}
/* 使用三角形混合 */
.arrow-up { .triangle(20px, #1a73e8, up); margin: 20px; display: inline-block; }
.arrow-down { .triangle(20px, #ea4a36, down); margin: 20px; display: inline-block; }
.arrow-left { .triangle(20px, #34a853, left); margin: 20px; display: inline-block; }
.arrow-right { .triangle(20px, #fbbc04, right); margin: 20px; display: inline-block; }
/* 下拉菜单箭头示例 */
.dropdown-arrow {
.triangle(8px, #666, down);
display: inline-block;
vertical-align: middle;
margin-left: 4px;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="arrow-up"></div>
<div class="arrow-down"></div>
<div class="arrow-left"></div>
<div class="arrow-right"></div>
<p>下拉菜单 <span class="dropdown-arrow"></span></p>
</body>
</html>
【代码注释】
核心逻辑
- 同名 mixin
.triangle(...)定义多份,用when (@direction = up)等 Guards 做模式匹配,仅匹配分支展开。 - CSS 三角:
width/height: 0+ 四边border,显示边设色,其余transparent。
关键 API
when (条件):守卫表达式;=为相等比较非赋值。- 下拉箭头:
.triangle(8px, #666, down)即可。
注意点
- Guards 不能替代
if/else做复杂逻辑,仅适合分支样式。 - 数值比较用
when (@cols > 3)等(见下节)。
实战场景
- 气泡尖角、Select 下拉箭头、Tooltip 三角;Bootstrap caret 同源思路。
数值条件守卫
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 数值条件守卫</title>
<style type="text/less">
/* 根据列数动态调整间距 */
.grid-col(@cols) when (@cols <= 3) {
width: percentage(1 / @cols);
padding: 0 20px;
}
.grid-col(@cols) when (@cols > 3) {
width: percentage(1 / @cols);
padding: 0 10px; /* 列多时减小内边距 */
}
/* 根据字号大小动态调整行高 */
.font-size(@size) when (@size < 14px) {
font-size: @size;
line-height: 1.8; /* 小字号用较大行高提升可读性 */
}
.font-size(@size) when (@size >= 14px) {
font-size: @size;
line-height: 1.5;
}
.text-sm { .font-size(12px); }
.text-md { .font-size(16px); }
.text-lg { .font-size(24px); }
</style>
<script src="./less.js"></script>
</head>
<body>
<p class="text-sm">小号文字(12px,行高1.8)</p>
<p class="text-md">中号文字(16px,行高1.5)</p>
<p class="text-lg">大号文字(24px,行高1.5)</p>
</body>
</html>
【代码注释】
核心逻辑
.text-size(@size)用when (@cols <= 3)/when (@cols > 3)分支输出不同字号,等价于 if-else。- 守卫支持
and(与)、逗号,(或),可组合数值、字符串比较。
关键 API
| 写法 | 含义 |
|---|---|
when (@a > 0) and (@b > 0) |
同时满足 |
when (@a > 0), (@b > 0) |
满足其一 |
注意点
- 分支应互斥且覆盖常用场景,否则无匹配时 mixin 不展开。
- 与 §4.4 方向守卫配合,可驱动
.text-sm/.text-lg等修饰类。
实战场景
- 栅格列数、字号档位、按钮尺寸等多档样式生成。
真实网站场景:
- 天猫、京东 商品页的下拉选择器箭头、面包屑导航分隔符均使用 CSS 三角形技术,在 LESS 项目中由三角形混合统一生成。
- Bootstrap 3 的
_dropdowns.less中大量使用 CSS 三角形构造下拉箭头。
4.5 导入(Import)
LESS 的 @import 允许将样式代码分割为多个文件进行模块化管理,这是大型项目样式工程化的基础。
名词解释
| 术语 | 说明 |
|---|---|
| 模块化(Modularization) | 将代码按功能拆分为独立文件(变量文件、重置文件、组件文件等),通过 @import 组合。 |
| @import 关键字 | 导入另一个 LESS 文件的全部内容到当前文件中,类似 C/C++ 的 #include。 |
| 导入关键词 | LESS 扩展了 CSS 的 @import,提供 reference、once、inline 等导入选项。 |
基础导入
variables.less(变量文件):
less
/* 全局颜色变量 */
@master-red: #ea4a36; /* 主品牌红 */
@btn-red: #e1251b; /* 按钮红 */
@sep-color: #b3aeae; /* 分隔线颜色 */
@text-primary: #333; /* 主文字色 */
@text-muted: #999; /* 次要文字色 */
/* 全局尺寸变量 */
@container-width: 1190px;
@gutter: 15px;
reset.less(重置样式):
less
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
text-decoration: none;
color: inherit;
}
ul, ol {
list-style: none;
}
img {
display: block;
max-width: 100%;
}
mixins.less(混合库):
less
/* 清除浮动 */
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
/* Flex 居中 */
.flex-center() {
display: flex;
justify-content: center;
align-items: center;
}
/* 文字截断(单行) */
.text-ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
index.less(入口文件):
less
/* 导入变量(必须最先导入,其他文件依赖变量) */
@import "variables";
/* 导入重置样式 */
@import "reset";
/* 导入混合库 */
@import "mixins";
/* 导入 CSS 文件(原样保留 @import 语句到编译结果) */
@import "normalize.css";
/* 页面级样式 */
.box {
width: 500px;
color: @master-red; /* 使用从 variables.less 导入的变量 */
.clearfix(); /* 使用从 mixins.less 导入的混合 */
}
完整可运行示例(单文件演示导入逻辑):
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 导入演示</title>
<style type="text/less">
/* 在单文件中模拟多文件导入的效果 */
/* ── 等同于 @import "variables" 的内容 ── */
@brand-color: #1a73e8;
@font-size: 14px;
/* ── 等同于 @import "mixins" 的内容 ── */
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
.text-ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* ── 业务样式(使用导入的变量和混合) ── */
.news-list {
width: 400px;
.clearfix();
li {
float: left;
width: 180px;
height: 40px;
line-height: 40px;
color: @brand-color;
font-size: @font-size;
.text-ellipsis();
margin: 0 10px 10px 0;
border-bottom: 1px solid #eee;
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<ul class="news-list">
<li>这是一条很长的新闻标题文字会被截断</li>
<li>另一条新闻标题文字</li>
<li>第三条新闻超长标题内容超过宽度</li>
<li>第四条普通标题</li>
</ul>
</body>
</html>
【代码注释】
核心逻辑
@import "variables"将variables.less内联展开到当前文件(可省略扩展名)。@import "normalize.css"保留为 CSS@import,由浏览器另行请求,不内联。
导入顺序建议
variables → mixins → reset → components → page
LESS 导入关键词对照表:
| 关键词 | 示例 | 行为 |
|---|---|---|
| (无) | @import "file" |
标准导入,文件内容内联展开 |
reference |
@import (reference) "file" |
导入但只引用(不输出),引用混合/变量时才输出 |
once |
@import (once) "file" |
只导入一次(默认行为) |
multiple |
@import (multiple) "file" |
允许多次导入同一文件 |
inline |
@import (inline) "file" |
内联文件内容但不处理(保留原始 CSS) |
真实网站场景:
- Bootstrap 3 的
bootstrap.less入口文件通过@import组织了 40+ 个模块文件(变量、重置、网格、按钮、表单等),所有模块独立维护,通过入口文件统一编译输出。
4.6 嵌套(Nesting)
嵌套是 LESS 最直观、最常用的特性,让样式结构与 HTML 结构保持一致,大幅减少选择器重复书写。
① 基本嵌套(层级选择器)
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 基本嵌套演示</title>
<style type="text/less">
.container {
margin: 0 auto;
width: 1200px;
/* 后代选择器:.container .wrapper */
.wrapper {
height: 200px;
background: #f5f5f5;
/* 后代:.container .wrapper .item */
.item {
background: #ccc;
padding: 10px;
}
/* 子代选择器:.container .wrapper > .active */
> .active {
background: #1a73e8;
color: #fff;
}
/* 紧邻兄弟:.container .wrapper + .content */
+ .content {
background: #aaa;
}
/* 通用兄弟:.container .wrapper ~ div */
~ div {
background: #ddd;
}
}
/* 链接列表 */
.news {
padding: 0;
margin: 10px;
li {
width: 500px;
line-height: 40px;
border-bottom: 1px solid #eee;
/* & 父引用:.news li.active */
&.active {
background: #900;
color: #fff;
}
/* 属性选择器:.news li[name='xiaole'] */
&[data-type='featured'] {
font-weight: bold;
color: #ea4a36;
}
/* 伪类:.news li:hover */
&:hover {
background: #f0f8ff;
cursor: pointer;
}
/* 伪元素:.news li::after */
&::after {
content: "›";
float: right;
color: #999;
}
}
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="container">
<div class="wrapper">
<div class="item">普通子元素</div>
<div class="active">激活子元素</div>
</div>
<ul class="news">
<li class="active">激活新闻</li>
<li data-type="featured">精选新闻</li>
<li>普通新闻</li>
</ul>
</div>
</body>
</html>
编译后等效 CSS 片段:
css
.container { margin: 0 auto; width: 1200px; }
.container .wrapper { height: 200px; background: #f5f5f5; }
.container .wrapper .item { background: #ccc; padding: 10px; }
.container .wrapper > .active { background: #1a73e8; color: #fff; }
.container .wrapper + .content { background: #aaa; }
.container .wrapper ~ div { background: #ddd; }
.container .news li { width: 500px; line-height: 40px; }
.container .news li.active { background: #900; color: #fff; }
.container .news li:hover { background: #f0f8ff; cursor: pointer; }
.container .news li::after { content: "›"; float: right; color: #999; }
【代码注释】
核心逻辑
- 嵌套把子选择器写在父规则内,编译时展开为完整选择器链,减少重复前缀。
&引用当前父选择器,拼接方式决定是交集还是后代。
& 拼接规则
| 写法 | 编译结果 | 含义 |
|---|---|---|
&.active |
.news li.active |
同一元素多类 |
&:hover |
.news li:hover |
伪类 |
& .child |
.news li .child |
后代(有空格) |
> a |
.container > a |
直接子代 |
注意点
&与父选择器之间无空格 才拼接;&::after常用于清除浮动、装饰线。- 嵌套过深(>3 层)可读性下降,组件库常限制 2~3 层。
实战场景
- 列表项 hover/active、新闻模块
.container .news li一类 PC 页结构。
② 媒体查询嵌套
LESS 允许将媒体查询写在选择器内部,使响应式代码与对应选择器在视觉上保持邻近关系,提升可读性。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>媒体查询嵌套演示</title>
<style type="text/less">
/* 变量化断点(可集中管理) */
@bp-sm: 576px;
@bp-md: 768px;
@bp-lg: 992px;
@bp-xl: 1200px;
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding: 0 15px;
/* 媒体查询直接嵌套在选择器内 */
@media (min-width: @bp-md) {
width: 750px;
padding: 0;
}
@media (min-width: @bp-lg) {
width: 970px;
}
@media (min-width: @bp-xl) {
width: 1170px;
}
}
/* 卡片响应式布局 */
.card-grid {
display: flex;
flex-wrap: wrap;
gap: 16px;
.card {
width: 100%; /* 移动端全宽 */
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
@media (min-width: @bp-md) {
width: calc(50% - 8px); /* 平板两列 */
}
@media (min-width: @bp-lg) {
width: calc(33.333% - 11px); /* 桌面三列 */
}
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="container">
<div class="card-grid">
<div class="card">卡片 1</div>
<div class="card">卡片 2</div>
<div class="card">卡片 3</div>
<div class="card">卡片 4</div>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
@media (min-width: @bp-md)写在.container/.card内部,编译为@media (...) { .card-grid .card { ... } },响应式规则与组件同屏维护。@bp-sm~@bp-xl集中管理断点,改一处变量全站媒体查询同步。
关键 API
- 断点变量 + 嵌套媒体查询 = Bootstrap 式容器宽度阶梯(750 / 970 / 1170)。
calc(50% - 8px)可与 gap 布局配合做列宽。
注意点
- 媒体查询变量含
:需~"min-width: 768px"或单独@bp-md: 768px再拼进@media。 - 移动端优先:先写默认样式,再在大屏
@media中覆盖。
实战场景
- 卡片栅格 1→2→3 列、版心
.container随屏宽变化;与 §7.2 栅格变量思路一致。
真实网站场景: Bootstrap 4 将响应式断点定义为 SASS 变量($grid-breakpoints),并通过 mixin 将媒体查询注入各组件,与 LESS 的媒体查询嵌套原理相同。
③ 混合与嵌套结合
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>混合与嵌套结合演示</title>
<style type="text/less">
/* 包含嵌套的混合 */
.center-box() {
width: 400px;
height: 300px;
background: #f0f8ff;
/* 混合内部可以定义嵌套混合 */
.center-box01() {
width: 200px;
height: 150px;
background: #1a73e8;
color: #fff;
}
}
/* 清除浮动(& 引用在混合中的经典用法) */
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
/* 卡片悬停效果混合 */
.card-hover-effect() {
transition: all .3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,.15);
}
}
/* 应用混合 */
.box {
.center-box();
.card-hover-effect();
position: relative;
cursor: pointer;
}
.layout-row {
.clearfix();
width: 100%;
.col {
float: left;
width: 33.333%;
padding: 0 15px;
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="box">悬停查看效果</div>
<div class="layout-row">
<div class="col">列1</div>
<div class="col">列2</div>
<div class="col">列3</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
- Mixin 内
&在调用点 替换为调用者选择器:.layout-row { .clearfix(); }→.layout-row::after。 .card-hover-effect()含&:hover,展开后悬停样式挂在.box:hover,而非孤立伪类。- 混合内可再定义子混合(
.center-box01()),形成局部命名空间。
注意点
- 子混合若未在同次调用链中引用,可能无法按预期展开;大项目更推荐
#utils > .mixin()(§4.9)。 - 浮动三列布局必须配合
.clearfix(),否则父高度塌陷。
实战场景
- 卡片悬停抬升、三列
.layout-row浮动 + 清除浮动,PC 列表页常见组合。
4.7 运算符(Operations)
LESS 支持对数值进行四则运算,让尺寸计算从"硬编码"变为"公式推导"。
名词解释
| 术语 | 说明 |
|---|---|
| 单位推断 | 运算结果的单位取决于操作数,规则:两个操作数单位不同,取第一个操作数的单位;只有一个有单位,取该单位。 |
| 括号强制除法 | LESS 中除法 / 必须用括号包裹,如 (100px / 2),否则被当作 CSS 字体属性分隔符。 |
| calc() | CSS 原生计算函数,LESS 不会对 calc() 内部表达式进行预处理。 |
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 运算符完整演示</title>
<style type="text/less">
@base: 100px;
@gutter: 15px;
@columns: 12;
.ops-demo {
/* 加法:同单位 */
width: @base + 50px; /* → 150px */
/* 加法:不同单位,取第一个操作数单位 */
margin: 10em + 20px; /* → 30em(取 em) */
/* 减法 */
height: @base - 30px; /* → 70px */
/* 乘法 */
padding: @gutter * 2; /* → 30px */
/* 除法:必须用括号 */
font-size: (@base / 10); /* → 10px */
/* 综合运算 */
border-radius: (@base / 4 + 5px); /* → 30px */
}
/* 实际应用:等分列宽计算 */
.grid(@col, @total: @columns) {
width: percentage(@col / @total);
}
.col-4 { .grid(4); } /* 33.3333% */
.col-6 { .grid(6); } /* 50% */
.col-3 { .grid(3); } /* 25% */
/* 响应式容器宽度:减去两侧 gutter */
.inner {
width: (1200px - @gutter * 2); /* → 1170px */
margin: 0 auto;
padding: 0 @gutter;
}
/* 颜色运算 */
@primary: #4a90e2;
.color-ops {
background: @primary + #111; /* 颜色相加(各通道相加,最大255) */
border-color: @primary - #222; /* 颜色相减 */
color: @primary * 2; /* 颜色各通道乘以2 */
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="ops-demo">运算符演示</div>
<div class="inner">
<div class="col-6" style="float:left;background:#f0f8ff;">50% 宽度列</div>
<div class="col-6" style="float:left;background:#fff3cd;">50% 宽度列</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
- LESS 在编译期对数字、长度、颜色做运算;结果写入 CSS 字面量。
- 除法 必须用括号
(100px / 4),否则/可能被当作 CSS 分隔符(§10.1)。 percentage(@col / @total)将比例转为%,栅格核心算法。
易错:混合单位
| 表达式 | 结果 | 说明 |
|---|---|---|
10px + 20px |
30px |
✅ 同单位 |
10em + 20px |
30em |
⚠️ 取第一个单位,px 被丢 |
(100px / 4) |
25px |
✅ 括号除法 |
注意点
- 颜色运算可用但少见;hover 色优先
darken()/lighten()(§4.8)。 100px % 6取余请用mod()函数。
实战场景
- 12 列宽
percentage(4/12)、间距@gutter / 2、负 margin 居中-(@width/2)。
运算规则总结表:
| 场景 | 示例 | 结果 |
|---|---|---|
| 同单位加法 | 10px + 20px |
30px |
| 异单位加法 | 10em + 20px |
30em(取第一个) |
| 只有一个单位 | 10 + 20px |
30px |
| 乘法 | 15px * 2 |
30px |
| 除法(必须括号) | (100px / 4) |
25px |
| 取余(不支持) | 100px % 6 |
不支持 |
4.8 内置函数(Functions)
LESS 提供了丰富的内置函数,涵盖颜色处理、数学运算、字符串操作等。完整列表参见官方文档:https://less.bootcss.com/functions/
常用函数速查表
| 分类 | 函数 | 说明 | 示例 |
|---|---|---|---|
| 数学 | percentage(n) |
小数转百分比 | percentage(0.5) → 50% |
| 数学 | mod(a, b) |
取余 | mod(100px, 6) → 4px |
| 数学 | ceil(n) |
向上取整 | ceil(2.3) → 3 |
| 数学 | floor(n) |
向下取整 | floor(2.9) → 2 |
| 数学 | round(n) |
四舍五入 | round(2.5) → 3 |
| 颜色 | lighten(c, n%) |
提亮颜色 | lighten(#900, 10%) → #cc0000 |
| 颜色 | darken(c, n%) |
加深颜色 | darken(#900, 20%) → #300000 |
| 颜色 | saturate(c, n%) |
增加饱和度 | saturate(#808080, 50%) |
| 颜色 | desaturate(c, n%) |
降低饱和度 | desaturate(#f00, 30%) |
| 颜色 | mix(c1, c2, w%) |
混合两色 | mix(#f00, #00f, 50%) |
| 颜色 | fade(c, n%) |
设置透明度 | fade(#900, 50%) → rgba(153,0,0,0.5) |
| 颜色 | rgba(r,g,b,a) |
创建 rgba 颜色 | rgba(255, 0, 0, 0.5) |
| 字符串 | e(s) |
转义字符串(同 ~"..." ) |
e("min-width:768px") |
完整函数示例
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 内置函数完整演示</title>
<style type="text/less">
@primary: #1a73e8;
@danger: #ea4a36;
/* ── 颜色函数 ── */
.btn-primary {
background: @primary;
color: #fff;
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
display: inline-block;
margin: 8px;
transition: background .2s;
&:hover {
background: darken(@primary, 10%); /* 悬停加深 10% */
}
&:active {
background: darken(@primary, 20%); /* 点击加深 20% */
}
&:disabled {
background: lighten(@primary, 30%); /* 禁用提亮 30% */
cursor: not-allowed;
}
}
.btn-danger {
background: @danger;
color: #fff;
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
display: inline-block;
margin: 8px;
&:hover {
background: darken(@danger, 10%);
}
}
/* 颜色透明度 */
.overlay {
background: fade(#000, 60%); /* rgba(0,0,0,0.6) */
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
}
/* 颜色混合:生成渐变系列 */
@base-color: #1a73e8;
.color-50 { background: mix(@base-color, #fff, 50%); } /* 50% 浅化 */
.color-100 { background: mix(@base-color, #fff, 30%); } /* 更浅 */
.color-200 { background: @base-color; } /* 原色 */
.color-300 { background: mix(@base-color, #000, 80%); } /* 稍深 */
/* ── 数学函数 ── */
.grid-item {
width: percentage(1 / 6); /* 等分六列 → 16.6667% */
height: percentage(0.7); /* → 70% */
float: left;
padding: mod(100px, 6) 0; /* 取余 → 4px */
}
/* ── 实际主题生成示例 ── */
@theme-hue: #4a90e2;
.theme-light {
background: lighten(@theme-hue, 40%);
color: darken(@theme-hue, 30%);
border: 1px solid lighten(@theme-hue, 20%);
}
.theme-dark {
background: darken(@theme-hue, 20%);
color: lighten(@theme-hue, 40%);
border: 1px solid darken(@theme-hue, 30%);
}
</style>
<script src="./less.js"></script>
</head>
<body>
<button class="btn-primary">主要按钮</button>
<button class="btn-primary" disabled>禁用按钮</button>
<button class="btn-danger">危险按钮</button>
<div style="display:flex;gap:8px;margin:16px 0;">
<div class="color-50" style="width:80px;height:40px;"></div>
<div class="color-100" style="width:80px;height:40px;"></div>
<div class="color-200" style="width:80px;height:40px;"></div>
<div class="color-300" style="width:80px;height:40px;"></div>
</div>
<div class="theme-light" style="padding:16px;margin:8px 0;">Light 主题</div>
<div class="theme-dark" style="padding:16px;margin:8px 0;">Dark 主题</div>
</body>
</html>
【代码注释】
核心逻辑
- 颜色函数在 HSL 空间 运算:
darken/lighten调亮度,saturate/desaturate调饱和度,fade调透明度。 mix(c1, c2, w%)按权重混合两色,生成 50/100/200 色阶。- 数学函数
percentage、mod、ceil等与 §4.7 运算互补。
常用函数速记
| 函数 | 典型用途 |
|---|---|
darken(@primary, 10%) |
按钮 hover |
fade(#000, 60%) |
遮罩层 |
mix(@base, #fff, 50%) |
浅色背景系列 |
percentage(1/6) |
六等分列宽 |
注意点
darken第二参数是 百分比(10%),不是 0.1(§10.4)。- 禁用态用
lighten比手写灰色更易随主题色联动。
实战场景
- 主色一套变量 + 函数推导 hover/active/disabled;主题 light/dark 块(
.theme-light)。
4.9 命名空间混合与混合嵌套
当混合数量增多时,可用 命名空间 把工具类混合收拢到同一选择器下,避免全局类名污染;混合内部也可以再定义子混合,并在调用处组合使用。
名词解释
| 术语 | 说明 |
|---|---|
| 命名空间(Namespace) | 形如 #utils { .clearfix() {} },通过 #utils > .clearfix() 调用,编译后不输出 #utils 空规则(仅展开调用处)。 |
| 混合嵌套 | 在一个混合定义块内再定义子混合,适合按组件维度组织工具方法。 |
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>命名空间混合与混合嵌套</title>
<style type="text/less">
@master-red: #c81623;
/* 外层混合内可定义子混合 */
.center-box() {
width: 400px;
height: 300px;
background: fade(@master-red, 15%);
.center-box-lg() {
width: 500px;
height: 400px;
background: fade(@master-red, 25%);
}
}
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
#layout-utils {
.clearfix() {
&::after { content: ""; display: block; clear: both; }
}
.ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.box {
.center-box();
color: @master-red;
.center-box-lg();
}
.item {
.center-box();
margin-top: 12px;
}
.wrapper {
#layout-utils > .clearfix();
width: 900px;
border: 1px dashed #ddd;
padding: 8px;
.col {
float: left;
width: 200px;
#layout-utils > .ellipsis();
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="box">主容器(嵌套混合尺寸)</div>
<div class="wrapper">
<div class="col">很长很长很长很长很长很长很长很长的栏目名称</div>
<div class="col">第二列</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
#layout-utils { .clearfix() {} }定义命名空间 ,调用#layout-utils > .clearfix()只展开 mixin,不输出空#layout-utils规则块。- 混合内嵌套子混合(
.center-box-lg())形成局部作用域,适合按组件打包工具方法。
对比
| 方式 | 检索性 | 冲突风险 |
|---|---|---|
全局 .clearfix() |
高 | 易重名 |
#utils > .clearfix() |
中 | 低,需记命名空间 |
注意点
- 带
()的 mixin 默认不进 CSS;无()的类会同时生成选择器(§10.3)。 - 生产项目更推荐
mixins/clearfix.less+@import,命名空间适合大型组件库内部。
实战场景
- Ant Design 早期 LESS 版:
typography、ellipsis等工具 mixin 集中管理。
真实网站场景: 组件库(如 Ant Design 早期 LESS 版)将 typography、clearfix、text-ellipsis 等工具混合集中在内部模块,业务侧通过命名空间或统一入口 @import 引用,减少全局类名冲突。
4.10 图片路径与资源引用
样式中的 url() 在 LESS 里可按普通 CSS 书写;路径相对于 当前 LESS 文件所在目录 (编译后则相对于输出 CSS 位置)。建议将位图、图标放在项目下的 images/ 目录,便于统一维护。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 图片路径演示</title>
<style type="text/less">
@logo-width: 190px;
@logo-height: 56px;
@banner-height: 120px;
.header-logo {
width: @logo-width;
height: @logo-height;
/* 相对当前页面:images 与 html 同级 */
background: url("images/logo.png") no-repeat left center;
background-size: contain;
text-indent: -9999px;
overflow: hidden;
}
.promo-banner {
height: @banner-height;
background: url("images/banner.jpg") no-repeat center;
background-size: cover;
border-radius: 4px;
}
.sprite-icon-cart {
width: 24px;
height: 24px;
background: url("images/icons.png") no-repeat -48px 0;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<header>
<h1 class="header-logo">站点 Logo</h1>
<span class="sprite-icon-cart" title="购物车"></span>
</header>
<section class="promo-banner" aria-label="促销横幅区域"></section>
<p style="font-size:12px;color:#666;margin-top:8px;">
将 logo.png、banner.jpg、icons.png 置于与页面同级的 images/ 目录即可看到背景图效果。
</p>
</body>
</html>
【代码注释】
核心逻辑
url()在 LESS 中多数原样输出 到 CSS;路径解析以当前 .less 文件目录为基准,非 HTML 位置。- Logo/Banner 用
background-size: contain/cover;雪碧图用background-position负偏移切图标。
关键 API
| 场景 | 写法 |
|---|---|
| 同目录 images | url("images/logo.png") |
| less 在 css/ 子目录 | url("../images/logo.png") |
| 雪碧图 | no-repeat -48px 0 + 变量偏移 |
注意点
- LESS 不会自动 base64 内联,需 webpack
url-loader等插件。 text-indent: -9999px常用于 Logo 文字隐藏(可访问性上更推荐真实 alt/aria)。
实战场景
- 电商 PC 头图、购物车图标;
@logo-width/@sprite-path换肤只改变量。
真实网站场景: 电商 PC 站头部 Logo、轮播 Banner、雪碧图图标(购物车、消息)普遍用背景图 + 精确偏移;LESS 变量统一管理尺寸与偏移,换肤时只改 @sprite-path 或主题目录。
4.11 高级特性:$prop、:extend()、属性合并与递归循环
本节聚合官方文档中四个常被忽视却极为实用的高级特性,深度掌握它们才能写出"工业级"LESS 代码。
4.11.1 $prop ------ 将属性值当变量引用(v3.0.0)
名词解释
| 术语 | 说明 |
|---|---|
$prop 语法 |
LESS v3.0.0 新增的属性引用语法,允许在同一规则块内将某个属性的值当作变量直接复用,无需额外定义变量。 |
| 懒加载(Lazy Evaluation) | LESS 的变量/属性值求值策略:取当前作用域内最后一次赋值的结果,而非声明顺序。 |
用 $color、$background 等形式直接引用同一规则块内的 CSS 属性值,避免重复书写:
less
/* 变量定义 → 属性引用双重减少 */
.widget {
color: #1a73e8;
background-color: $color; /* 直接复用 color 属性的值 */
border-color: $color;
}
编译结果:
css
.widget {
color: #1a73e8;
background-color: #1a73e8;
border-color: #1a73e8;
}
注意:懒加载规则同样适用。 同一作用域中若 color 被多次赋值,$color 取最后一次赋值:
less
.block {
color: red;
.inner {
background-color: $color; /* 取外层 color 的最终值:blue */
}
color: blue; /* 此行是最终值 */
}
编译为:
css
.block { color: red; color: blue; }
.block .inner { background-color: blue; }
【代码注释】
核心逻辑
$color、$background引用同一规则块内已声明属性的值,编译期替换,零运行时开销。- 遵循 LESS 懒加载 :取作用域内该属性最后一次赋值,非书写顺序快照。
注意点
- 子块
.inner { background: $color }取外层color的最终值(见懒加载示例)。 - 与
@变量不同:$prop无需额外@border-color: @accent,减少 Token 重复。
实战场景
- Badge 边框/背景与文字同色;Icon 描边与填充联动。
经典场景: Ant Design 5.x 通过这种方式在组件内减少 CSS Token 变量声明数量。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>$prop 属性引用演示</title>
<style type="text/less">
@accent: #6200ea;
.badge {
color: @accent;
border: 1px solid $color; /* 复用 color 值 */
background: fade($color, 10%); /* 复用 color 值并降低透明度 */
padding: 2px 8px;
border-radius: 999px;
font-size: 12px;
display: inline-block;
}
.tag {
background: @accent;
color: #fff;
border-radius: 4px;
padding: 4px 10px;
&:hover {
background: darken($background, 10%); /* 复用 background 值 */
}
}
</style>
<script src="./less.js"></script>
</head>
<body style="padding:20px;font-family:sans-serif">
<span class="badge">NEW</span>
<span class="badge" style="--color:#e91e63">HOT</span>
<br><br>
<span class="tag">前端</span>
<span class="tag">LESS</span>
</body>
</html>
【代码注释】
核心逻辑
.badge中border: 1px solid $color→ 边框与color: @accent一致;fade($color, 10%)生成浅色背景。.tag:hover用darken($background, 10%),$background指向当前background属性值。
关键 API
$color+fade():一套主色衍生边框、背景、悬停。- 编译后均为普通 CSS 颜色字面量。
注意点
$background仅在已声明background的规则内有效。- 动态主题(运行时换肤)应配合 CSS Variables(§9),
$prop仅编译期。
实战场景
- 标签、徽章、角标组件;减少
@accent-border、@accent-bg等重复变量。
4.11.2 @@variable ------ 变量的变量
LESS 支持用另一个变量的值作为变量名,实现动态变量选择:
less
@primary: #1a73e8;
@secondary: #ea4a36;
@success: #34a853;
.section {
@theme: primary; /* 用字符串指定主题名 */
color: @@theme; /* 等价于 @primary → #1a73e8 */
}
编译结果:
css
.section { color: #1a73e8; }
经典场景: 通过 @@ 实现"主题名 → 颜色值"的映射,在守卫(Guards)中组合使用可产生类似"主题切换"的效果:
less
@themes: primary, secondary, success;
@primary: #1a73e8;
@secondary: #ea4a36;
@success: #34a853;
.generate-btn(@name) {
.btn-@{name} {
background: @@name;
color: #fff;
padding: 6px 14px;
border-radius: 4px;
}
}
.generate-btn(primary);
.generate-btn(secondary);
.generate-btn(success);
编译结果:
css
.btn-primary { background: #1a73e8; color: #fff; padding: 6px 14px; border-radius: 4px; }
.btn-secondary { background: #ea4a36; color: #fff; padding: 6px 14px; border-radius: 4px; }
.btn-success { background: #34a853; color: #fff; padding: 6px 14px; border-radius: 4px; }
【代码注释】
核心逻辑
@@theme:先求@theme→"primary",再求@primary→#1a73e8(变量的变量)。.generate-btn(@name)内@@name+.btn-@{name}批量生成多色按钮类。
两步求值
@name = primary → @@name → @primary → #1a73e8
注意点
@theme存的是变量名 (无@前缀的标识符),不是颜色字面量。- 可与递归
.gen-*((@i - 1))组合批量生成主题/尺寸类(§4.11.5)。
实战场景
- 多主题色表、状态色映射;减少
.btn-primary/.btn-danger手写重复。
4.11.3 :extend() ------ 选择器继承(v1.4.0)
名词解释
| 术语 | 说明 |
|---|---|
:extend() |
LESS 的选择器合并机制,将当前选择器"追加"到被扩展选择器的定义处,共享同一段 CSS 规则,从而减少编译后 CSS 的重复属性。 |
| Mixin 与 Extend 的区别 | Mixin 是复制属性 (每处调用都产生一份 CSS),Extend 是合并选择器(只有一份 CSS,多个选择器共享)。 |
基础语法:
less
/* 公共基础类 */
.base-text {
font-family: "Inter", sans-serif;
line-height: 1.6;
color: #333;
}
/* 标题扩展基础类 */
.heading {
&:extend(.base-text);
font-size: 24px;
font-weight: 700;
}
/* 正文扩展基础类 */
.body-text {
&:extend(.base-text);
font-size: 16px;
}
编译结果:
css
/* 三个选择器共享同一条规则------CSS 体积最小 */
.base-text,
.heading,
.body-text {
font-family: "Inter", sans-serif;
line-height: 1.6;
color: #333;
}
.heading { font-size: 24px; font-weight: 700; }
.body-text { font-size: 16px; }
对比使用 Mixin:
less
/* Mixin 方式------属性被复制 3 次,CSS 体积更大 */
.base-text() { font-family: "Inter", sans-serif; line-height: 1.6; color: #333; }
.heading { .base-text(); font-size: 24px; }
.body-text { .base-text(); font-size: 16px; }
Extend 的 all 关键字:扩展目标选择器的所有变体(包括伪类、组合选择器):
less
.error {
color: red;
&:hover { color: darkred; }
&.active { font-weight: bold; }
}
/* 不带 all:只扩展 .error 本身 */
.critical { &:extend(.error); }
/* 带 all:扩展 .error 的所有变体(含 :hover、.active 等) */
.critical-all { &:extend(.error all); }
完整可运行示例:
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>:extend() 选择器继承演示</title>
<style type="text/less">
/* ---- 公共基础样式 ---- */
.card-base {
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0,0,0,.12);
background: #fff;
}
/* ---- 通过 extend 继承,避免重复属性 ---- */
.card-info { &:extend(.card-base); border-left: 4px solid #1a73e8; }
.card-success { &:extend(.card-base); border-left: 4px solid #34a853; }
.card-danger { &:extend(.card-base); border-left: 4px solid #ea4a36; }
.card-warning { &:extend(.card-base); border-left: 4px solid #fbbc04; }
body { padding: 20px; background: #f5f5f5; font-family: sans-serif; }
.card-base + .card-base,
.card-info + .card-info,
[class^="card-"] + [class^="card-"] { margin-top: 12px; }
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="card-info"> 信息卡片:extend 让四种卡片共享 .card-base 的圆角/阴影/内边距</div>
<div class="card-success"> 成功卡片:每种卡片只需额外声明自身的左边框颜色</div>
<div class="card-danger"> 危险卡片:编译后 CSS 中 .card-base 的属性只出现一次</div>
<div class="card-warning"> 警告卡片:对比 Mixin,Extend 可显著减小样式文件体积</div>
</body>
</html>
【代码注释】
核心逻辑
&:extend(.card-base)把当前选择器并入.card-base规则组,共享一份圆角/阴影/内边距。- Mixin 是复制属性 到每个调用处;Extend 是合并选择器,体积更小。
Extend vs Mixin
| Extend | Mixin | |
|---|---|---|
| 输出 | 选择器列表合并 | 每处一份属性 |
| 参数 | ❌ | ✅ |
| hover 变体 | 需 all 关键字 |
更灵活 |
注意点
&:extend(.error all)可连带扩展:hover、.active等变体。- 静态基础类(卡片、排版)用 Extend;动态颜色/尺寸用 Mixin。
实战场景
- 信息/成功/危险/警告四类卡片共享
.card-base;Bootstrap 按钮基础类思路类似。
何时用 Extend,何时用 Mixin:
| 场景 | 推荐 |
|---|---|
| 纯样式继承、减少 CSS 体积 | :extend() |
| 需要传入参数、动态生成样式 | Mixin |
| 状态/变体样式(hover、active) | Mixin(更灵活) |
| 框架级共享基础类(按钮基础等) | :extend() + Mixin 混合 |
4.11.4 属性合并(Merge)------ + 与 +_(v1.5.0 / v1.7.0)
名词解释
| 符号 | 说明 | 适用场景 |
|---|---|---|
property+: |
将多个值以逗号拼接为一个属性 | background、box-shadow、transition |
property+_: |
将多个值以空格拼接为一个属性 | transform、background-position |
逗号合并(+):
less
/* 为元素叠加多个 box-shadow */
.shadow-base() {
box-shadow+: inset 0 1px 3px rgba(0,0,0,.2);
}
.shadow-outer() {
box-shadow+: 0 4px 12px rgba(0,0,0,.15);
}
.card {
.shadow-base();
.shadow-outer();
}
编译结果:
css
.card {
box-shadow: inset 0 1px 3px rgba(0,0,0,.2), 0 4px 12px rgba(0,0,0,.15);
}
空格合并(+_):
less
/* 组合多个 transform 变换 */
.scale-mixin() { transform+_: scale(1.05); }
.rotate-mixin() { transform+_: rotate(5deg); }
.animated {
.scale-mixin();
.rotate-mixin();
}
编译结果:
css
.animated {
transform: scale(1.05) rotate(5deg);
}
完整可运行示例:
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>属性合并 Merge 演示</title>
<style type="text/less">
/* 阴影混合库 */
.shadow-inset() { box-shadow+: inset 0 2px 4px rgba(0,0,0,.15); }
.shadow-drop() { box-shadow+: 0 6px 18px rgba(0,0,0,.12); }
.shadow-glow(@c) { box-shadow+: 0 0 0 3px fade(@c, 30%); }
/* 变换混合库 */
.scale(@s: 1.05) { transform+_: scale(@s); }
.rotate(@deg: 3deg) { transform+_: rotate(@deg); }
.btn {
padding: 10px 22px;
border-radius: 6px;
background: #1a73e8;
color: #fff;
border: none;
cursor: pointer;
font-size: 15px;
transition: all .2s;
/* 叠加三层阴影 */
.shadow-inset();
.shadow-drop();
.shadow-glow(#1a73e8);
&:hover {
.scale(1.03);
.shadow-drop();
.shadow-glow(#1a73e8);
}
&:active {
.scale(0.97);
.rotate(1deg);
}
}
body { padding: 40px; background: #f0f4f8; font-family: sans-serif; }
</style>
<script src="./less.js"></script>
</head>
<body>
<button class="btn">点击我</button>
</body>
</html>
【代码注释】
核心逻辑
box-shadow+::多个 mixin 的阴影值用逗号拼接,实现多层阴影叠加。transform+_::多个变换用空格 拼接为transform: scale(...) rotate(...),避免后者覆盖前者。
合并符号
| 写法 | 拼接符 | 典型属性 |
|---|---|---|
property+: |
, |
box-shadow, background, transition |
property+_: |
空格 | transform, background-position |
注意点
- 参与合并的每条声明都必须带
+或+_,否则后写的 mixin 覆盖而非合并。 - v1.5.0+ 特性;与
@arguments前缀工厂是不同维度的复用。
实战场景
- 按钮同时内阴影 + 外阴影 + 发光;hover 叠加
scale+rotate。
4.11.5 递归循环(Recursive Loop)------ 批量生成工具类
名词解释
| 术语 | 说明 |
|---|---|
| 递归 Mixin | LESS 中没有原生 for 循环,通过"Mixin 调用自身 + Guards 控制终止条件"实现循环逻辑。 |
each() 函数 |
v3.7.0 引入的列表迭代函数,语法更简洁,推荐在支持 v3.7+ 的项目中使用。 |
方式一:Guards 递归(兼容所有版本):
less
/* 生成 .mt-1 ~ .mt-10 的 margin-top 工具类 */
@base-spacing: 4px;
@max-steps: 10;
.generate-spacing(@n) when (@n > 0) {
.generate-spacing((@n - 1)); /* 先递归到底 */
.mt-@{n} { margin-top: (@n * @base-spacing); }
.mb-@{n} { margin-bottom:(@n * @base-spacing); }
.pt-@{n} { padding-top: (@n * @base-spacing); }
.pb-@{n} { padding-bottom:(@n * @base-spacing); }
}
.generate-spacing(@max-steps);
编译结果(节选):
css
.mt-1 { margin-top: 4px; } .mb-1 { margin-bottom: 4px; }
.mt-2 { margin-top: 8px; } .mb-2 { margin-bottom: 8px; }
/* ... */
.mt-10 { margin-top: 40px; } .mb-10 { margin-bottom: 40px; }
方式二:each() 函数(v3.7.0+,推荐):
less
/* 使用 each 遍历颜色主题列表 */
@themes: {
primary: #1a73e8;
success: #34a853;
danger: #ea4a36;
warning: #fbbc04;
};
each(@themes, {
.text-@{key} { color: @value; }
.bg-@{key} { background-color: @value; }
.border-@{key} { border-color: @value; }
});
完整可运行示例(栅格 + 间距批量生成):
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>递归循环 ------ 批量生成工具类</title>
<style type="text/less">
/* ---- 递归生成 12 列栅格 ---- */
@columns: 12;
.gen-col(@i) when (@i > 0) {
.gen-col((@i - 1));
.col-@{i} {
width: percentage(@i / @columns);
float: left;
box-sizing: border-box;
padding: 0 6px;
}
}
.gen-col(@columns);
/* ---- 递归生成间距工具类(0-8,步长 4px) ---- */
.gen-spacing(@n) when (@n >= 0) {
.gen-spacing((@n - 1));
.m-@{n} { margin: (@n * 4px); }
.mt-@{n} { margin-top: (@n * 4px); }
.mb-@{n} { margin-bottom: (@n * 4px); }
.p-@{n} { padding: (@n * 4px); }
}
.gen-spacing(8);
/* ---- 布局辅助 ---- */
.row::after { content:""; display:block; clear:both; }
.demo { padding: 20px; font-family: sans-serif; }
[class^="col-"] {
background: #e8f0fe; border: 1px solid #1a73e8;
text-align: center; padding: 8px 0; font-size: 13px;
margin-bottom: 8px;
}
</style>
<script src="./less.js"></script>
</head>
<body class="demo">
<h3>递归生成 12 列栅格 + 间距工具类</h3>
<div class="row">
<div class="col-3">col-3</div>
<div class="col-3">col-3</div>
<div class="col-6">col-6</div>
</div>
<div class="row mt-4">
<div class="col-4">col-4</div>
<div class="col-8">col-8</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.gen-col(@i) when (@i > 0):Guard 为终止条件,@i到 0 时不再匹配,递归结束。- 体内先
.gen-col((@i - 1))再输出.col-@{i},保证.col-1....col-12顺序稳定。 each(@themes, { ... })(v3.7+)可替代手写递归,遍历 map 生成.text-primary等。
关键 API
| 方式 | 适用 |
|---|---|
| Guards 递归 | 全版本;栅格、间距工具类 |
each() |
v3.7+;主题色表批量生成 |
注意点
- 递归深度过大增加编译时间;生产常用预生成工具类或 PostCSS。
percentage(@i / @columns)括号除法,避免/解析歧义。
实战场景
- Tailwind 式
mt-1~mt-10、Bootstrap 12 列.col-*源码生成。
5 LESS 知识脉络总结
5.1 核心知识体系对照表
| 特性 | LESS 语法 | 类比概念 | 核心价值 |
|---|---|---|---|
| 变量 | @var: value |
常量 / 配置项 | 主题统一修改 |
| 混合 | .mixin() |
函数 | 样式代码复用 |
| 嵌套 | { child {} } |
树形结构 | 减少选择器重复 |
| 运算 | + - * (/) |
算术表达式 | 动态尺寸计算 |
| 条件 | when (@x = y) |
if/switch | CSS 算法逻辑 |
| 导入 | @import "file" |
模块化 | 代码组织拆分 |
| 函数 | darken() 等 |
标准库 | 颜色/数学工具 |
5.2 选择器嵌套规则总结
#mermaid-svg-5HRGEICoOGJ3SBHd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5HRGEICoOGJ3SBHd .error-icon{fill:#552222;}#mermaid-svg-5HRGEICoOGJ3SBHd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5HRGEICoOGJ3SBHd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5HRGEICoOGJ3SBHd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5HRGEICoOGJ3SBHd .marker.cross{stroke:#333333;}#mermaid-svg-5HRGEICoOGJ3SBHd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5HRGEICoOGJ3SBHd p{margin:0;}#mermaid-svg-5HRGEICoOGJ3SBHd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd .cluster-label text{fill:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd .cluster-label span{color:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd .cluster-label span p{background-color:transparent;}#mermaid-svg-5HRGEICoOGJ3SBHd .label text,#mermaid-svg-5HRGEICoOGJ3SBHd span{fill:#333;color:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd .node rect,#mermaid-svg-5HRGEICoOGJ3SBHd .node circle,#mermaid-svg-5HRGEICoOGJ3SBHd .node ellipse,#mermaid-svg-5HRGEICoOGJ3SBHd .node polygon,#mermaid-svg-5HRGEICoOGJ3SBHd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5HRGEICoOGJ3SBHd .rough-node .label text,#mermaid-svg-5HRGEICoOGJ3SBHd .node .label text,#mermaid-svg-5HRGEICoOGJ3SBHd .image-shape .label,#mermaid-svg-5HRGEICoOGJ3SBHd .icon-shape .label{text-anchor:middle;}#mermaid-svg-5HRGEICoOGJ3SBHd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5HRGEICoOGJ3SBHd .rough-node .label,#mermaid-svg-5HRGEICoOGJ3SBHd .node .label,#mermaid-svg-5HRGEICoOGJ3SBHd .image-shape .label,#mermaid-svg-5HRGEICoOGJ3SBHd .icon-shape .label{text-align:center;}#mermaid-svg-5HRGEICoOGJ3SBHd .node.clickable{cursor:pointer;}#mermaid-svg-5HRGEICoOGJ3SBHd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5HRGEICoOGJ3SBHd .arrowheadPath{fill:#333333;}#mermaid-svg-5HRGEICoOGJ3SBHd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5HRGEICoOGJ3SBHd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5HRGEICoOGJ3SBHd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5HRGEICoOGJ3SBHd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5HRGEICoOGJ3SBHd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5HRGEICoOGJ3SBHd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5HRGEICoOGJ3SBHd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5HRGEICoOGJ3SBHd .cluster text{fill:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd .cluster span{color:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5HRGEICoOGJ3SBHd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5HRGEICoOGJ3SBHd rect.text{fill:none;stroke-width:0;}#mermaid-svg-5HRGEICoOGJ3SBHd .icon-shape,#mermaid-svg-5HRGEICoOGJ3SBHd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5HRGEICoOGJ3SBHd .icon-shape p,#mermaid-svg-5HRGEICoOGJ3SBHd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5HRGEICoOGJ3SBHd .icon-shape .label rect,#mermaid-svg-5HRGEICoOGJ3SBHd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5HRGEICoOGJ3SBHd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5HRGEICoOGJ3SBHd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5HRGEICoOGJ3SBHd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 父选择器 .parent
后代 .child
直接子代 > .child
紧邻兄弟 + .sibling
通用兄弟 ~ .sibling
& 父引用
交集 &.active
伪类 &:hover
伪元素 &::after
属性选择器 &attr
5.3 混合参数传递规则
#mermaid-svg-bCknfS98Fb3lzDfE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bCknfS98Fb3lzDfE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bCknfS98Fb3lzDfE .error-icon{fill:#552222;}#mermaid-svg-bCknfS98Fb3lzDfE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bCknfS98Fb3lzDfE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bCknfS98Fb3lzDfE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bCknfS98Fb3lzDfE .marker.cross{stroke:#333333;}#mermaid-svg-bCknfS98Fb3lzDfE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bCknfS98Fb3lzDfE p{margin:0;}#mermaid-svg-bCknfS98Fb3lzDfE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bCknfS98Fb3lzDfE .cluster-label text{fill:#333;}#mermaid-svg-bCknfS98Fb3lzDfE .cluster-label span{color:#333;}#mermaid-svg-bCknfS98Fb3lzDfE .cluster-label span p{background-color:transparent;}#mermaid-svg-bCknfS98Fb3lzDfE .label text,#mermaid-svg-bCknfS98Fb3lzDfE span{fill:#333;color:#333;}#mermaid-svg-bCknfS98Fb3lzDfE .node rect,#mermaid-svg-bCknfS98Fb3lzDfE .node circle,#mermaid-svg-bCknfS98Fb3lzDfE .node ellipse,#mermaid-svg-bCknfS98Fb3lzDfE .node polygon,#mermaid-svg-bCknfS98Fb3lzDfE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bCknfS98Fb3lzDfE .rough-node .label text,#mermaid-svg-bCknfS98Fb3lzDfE .node .label text,#mermaid-svg-bCknfS98Fb3lzDfE .image-shape .label,#mermaid-svg-bCknfS98Fb3lzDfE .icon-shape .label{text-anchor:middle;}#mermaid-svg-bCknfS98Fb3lzDfE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bCknfS98Fb3lzDfE .rough-node .label,#mermaid-svg-bCknfS98Fb3lzDfE .node .label,#mermaid-svg-bCknfS98Fb3lzDfE .image-shape .label,#mermaid-svg-bCknfS98Fb3lzDfE .icon-shape .label{text-align:center;}#mermaid-svg-bCknfS98Fb3lzDfE .node.clickable{cursor:pointer;}#mermaid-svg-bCknfS98Fb3lzDfE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bCknfS98Fb3lzDfE .arrowheadPath{fill:#333333;}#mermaid-svg-bCknfS98Fb3lzDfE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bCknfS98Fb3lzDfE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bCknfS98Fb3lzDfE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bCknfS98Fb3lzDfE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bCknfS98Fb3lzDfE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bCknfS98Fb3lzDfE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bCknfS98Fb3lzDfE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bCknfS98Fb3lzDfE .cluster text{fill:#333;}#mermaid-svg-bCknfS98Fb3lzDfE .cluster span{color:#333;}#mermaid-svg-bCknfS98Fb3lzDfE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bCknfS98Fb3lzDfE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bCknfS98Fb3lzDfE rect.text{fill:none;stroke-width:0;}#mermaid-svg-bCknfS98Fb3lzDfE .icon-shape,#mermaid-svg-bCknfS98Fb3lzDfE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bCknfS98Fb3lzDfE .icon-shape p,#mermaid-svg-bCknfS98Fb3lzDfE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bCknfS98Fb3lzDfE .icon-shape .label rect,#mermaid-svg-bCknfS98Fb3lzDfE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bCknfS98Fb3lzDfE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bCknfS98Fb3lzDfE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bCknfS98Fb3lzDfE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用混合
参数模式
顺序传参
.mixin 600px 400px
按名传参
.mixin @h:600px @w:400px
使用默认值
省略有默认值的参数
LESS 按位置匹配形参
顺序可以不同
默认值生效
5.4 LESS 变量 vs CSS 自定义属性(CSS Variables)
| 维度 | LESS 变量 (@var) |
CSS 自定义属性 (--var) |
|---|---|---|
| 处理阶段 | 编译时(静态) | 运行时(动态) |
| 浏览器支持 | 100%(编译为纯 CSS) | IE 不支持 |
| JavaScript 访问 | 无法直接访问 | getPropertyValue('--var') |
| 动态修改 | 不可以 | 可以(适合暗色模式切换) |
| 作用域 | LESS 词法作用域 | CSS 级联作用域 |
| 适用场景 | 构建时主题、工程化 | 运行时换肤、JS 交互 |
5.5 特性 × 真实网站应用矩阵
| LESS 特性 | 典型 CSS 能力 | 业界使用举例 | 归纳要点 |
|---|---|---|---|
变量 @ |
主题色、间距、字号 | Ant Design 3、Bootstrap 3 主题定制 | 编译期替换,改一处全局生效 |
| 混合 | 可复用声明块 | Bootstrap .button-variant()、清除浮动 |
带 () 不输出多余类 |
Guards when |
条件化规则 | 多方向箭头、响应式间距分支 | 无 @if,靠模式匹配 |
嵌套 & |
缩短选择器 | 导航 li.active、:hover |
控制深度 ≤3 层 |
| 运算 | calc 类计算 |
12 栅格 percentage(1/12) |
除法必须加括号 |
| 函数 | 颜色变换 | 按钮 hover 自动 darken() |
参数是百分比不是 0--1 |
@import |
模块化 | Bootstrap 入口聚合数十模块 | 变量文件最先导入 |
| 命名空间 | 工具库隔离 | 内部 UI 库 utils 包 | #ns > .mixin() 调用 |
#mermaid-svg-qANZfozJx4s0Xo6k{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qANZfozJx4s0Xo6k .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qANZfozJx4s0Xo6k .error-icon{fill:#552222;}#mermaid-svg-qANZfozJx4s0Xo6k .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qANZfozJx4s0Xo6k .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qANZfozJx4s0Xo6k .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qANZfozJx4s0Xo6k .marker.cross{stroke:#333333;}#mermaid-svg-qANZfozJx4s0Xo6k svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qANZfozJx4s0Xo6k p{margin:0;}#mermaid-svg-qANZfozJx4s0Xo6k .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-qANZfozJx4s0Xo6k .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-qANZfozJx4s0Xo6k .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-qANZfozJx4s0Xo6k .slice{font-family:"trebuchet ms",verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-qANZfozJx4s0Xo6k .legend text{fill:#000000;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-qANZfozJx4s0Xo6k :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 45% 20% 20% 15% 大型站点 LESS 工程文件占比(示意) variables mixins components layout/pages
【代码注释】
核心逻辑
- 大型 LESS 工程 ≈ variables + mixins + components + pages 四层;组件样式占主导(饼图 ~45%)。
- §5.1~5.5 对照表把语法点映射到 Ant Design、Bootstrap 等真实用法,便于复习闭环。
工程占比启示
| 目录 | 职责 |
|---|---|
| variables | 主题 Token |
| mixins | 清除浮动、按钮变体 |
| components | 按钮/表单/导航 skin |
| layout/pages | Topbar、版心、频道页 |
注意点
- Bootstrap 4+ 默认 SCSS,但 Guards、嵌套、
@import组织方式与 LESS 互通。 - 运行时换肤见 §5.4:LESS 编译期 + CSS Variables 运行期可组合(§9)。
6 PC 项目实战:用 LESS 构建完整页面
以电商网站为背景,展示 LESS 在完整项目中的工程化实践。以下模块按 准备 → Topbar → Header → Nav 递进,与目录中的语法练习一一对应,最终合并为可运行的单页结构。
6.0 工程准备(container 与公共混合)
在写页面模块前,先约定 版心宽度 、清除浮动 等公共能力,避免各区块重复写布局代码。
less
/* 版心容器:水平居中 */
.container {
margin-left: auto;
margin-right: auto;
width: 1200px;
}
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>工程准备 - 版心演示</title>
<style type="text/less">
.clearfix() {
&::after { content: ""; display: block; clear: both; }
}
.container {
margin: 0 auto;
width: 1200px;
background: #f5f5f5;
.clearfix();
}
.container .block {
float: left;
width: 380px;
height: 60px;
margin: 10px;
background: #fff;
border: 1px solid #e0e0e0;
line-height: 60px;
text-align: center;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="container">
<div class="block">版心内模块 A</div>
<div class="block">版心内模块 B</div>
<div class="block">版心内模块 C</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.container:margin: 0 auto+width: 1200px实现 PC 版心居中,与后续 Topbar/Header 共用。.clearfix()mixin 在浮动子元素父级调用,用&::after撑开高度。
注意点
- 版心宽度建议抽为
@container-width,响应式时改媒体查询而非散落 magic number。 - 现代布局可用 Flex/Grid;本课保留 float 以对应经典电商 PC 结构。
实战场景
- §6.1 文件树中
variables.less→main.less聚合;本节为页面模块写代码前的公共底座。
6.1 项目文件结构
css/
├── variables.less ← 全局变量(颜色、字号、间距)
├── reset.less ← 重置样式
├── mixins.less ← 混合库
└── index.less ← 入口文件(@import 其他文件 + 页面样式)
6.2 variables.less(全局变量)
less
/* ── 品牌色系 ── */
@form-red: #ea4a36; /* 表单强调色(搜索框描边) */
@btn-red: #e1251b; /* 主按钮色 */
@sep-color: #b3aeae; /* 分隔线 */
@text-dark: #333;
@text-gray: #666;
@text-light: #999;
@bg-gray: #f5f5f5;
/* ── 布局变量 ── */
@container-width: 1190px;
@gutter: 15px;
@header-height: 100px;
@topbar-height: 30px;
/* ── 字体变量 ── */
@font-size-base: 14px;
@font-size-sm: 12px;
@font-size-lg: 16px;
/* ── 断点变量 ── */
@bp-sm: 576px;
@bp-md: 768px;
@bp-lg: 992px;
@bp-xl: 1200px;
6.3 reset.less(重置样式)
less
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
font-size: @font-size-base;
color: @text-dark;
background: #fff;
}
a {
text-decoration: none;
color: inherit;
&:hover { color: @btn-red; }
}
ul, ol { list-style: none; }
img { display: block; max-width: 100%; }
6.4 mixins.less(混合库)
less
/* 清除浮动 */
.clearfix() {
&::after {
content: "";
display: block;
clear: both;
}
}
/* 文字截断(单行省略号) */
.ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 绝对定位居中 */
.abs-center(@w, @h) {
position: absolute;
width: @w;
height: @h;
left: 50%;
top: 50%;
margin-left: -(@w / 2);
margin-top: -(@h / 2);
}
/* CSS 三角形(用于下拉箭头等) */
.triangle(@size, @color, @dir) when (@dir = up) {
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: transparent transparent @color transparent;
}
.triangle(@size, @color, @dir) when (@dir = down) {
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: @color transparent transparent transparent;
}
/* 多行文字截断 */
.clamp(@lines) {
display: -webkit-box;
-webkit-line-clamp: @lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
6.5 index.less(页面样式)
less
@import "variables";
@import "reset";
@import "mixins";
/* ────────────────────────────────────
顶部工具栏(Topbar)
──────────────────────────────────── */
.topbar {
height: @topbar-height;
background: @bg-gray;
border-bottom: 1px solid #e0e0e0;
line-height: @topbar-height;
.topbar-inner {
width: @container-width;
margin: 0 auto;
.clearfix();
.topbar-left {
float: left;
font-size: @font-size-sm;
color: @text-gray;
}
.topbar-right {
float: right;
a {
font-size: @font-size-sm;
color: @text-gray;
padding: 0 8px;
border-right: 1px solid @sep-color;
&:last-child { border-right: none; }
&:hover { color: @btn-red; }
}
}
}
}
/* ────────────────────────────────────
Logo 与搜索框
──────────────────────────────────── */
.header {
height: @header-height;
background: #fff;
.header-inner {
width: @container-width;
margin: 0 auto;
.clearfix();
}
.logo {
float: left;
width: 200px;
height: @header-height;
line-height: @header-height;
}
.search-bar {
float: left;
margin-top: 25px;
margin-left: 80px;
.search-input {
width: 500px;
height: 36px;
padding: 0 12px;
border: 2px solid @form-red;
border-right: none;
outline: none;
font-size: @font-size-base;
}
.search-btn {
width: 80px;
height: 36px;
background: @btn-red;
color: #fff;
border: none;
cursor: pointer;
font-size: @font-size-base;
vertical-align: top;
&:hover {
background: darken(@btn-red, 10%);
}
}
}
}
/* ────────────────────────────────────
页面导航(Nav)
──────────────────────────────────── */
.nav {
height: 40px;
background: @btn-red;
.nav-inner {
width: @container-width;
margin: 0 auto;
.clearfix();
}
.nav-list {
li {
float: left;
height: 40px;
line-height: 40px;
padding: 0 20px;
font-size: @font-size-base;
a {
color: #fff;
&:hover {
background: darken(@btn-red, 15%);
}
}
&.active > a {
background: darken(@btn-red, 20%);
font-weight: bold;
}
}
}
}
完整可运行 HTML 示例(单文件演示页面结构):
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>PC 项目实战演示</title>
<style type="text/less">
/* ── 变量 ── */
@btn-red: #e1251b;
@form-red: #ea4a36;
@sep-color: #b3aeae;
@text-gray: #666;
@bg-gray: #f5f5f5;
@font-sm: 12px;
@font-base: 14px;
@container: 1190px;
/* ── 混合 ── */
.clearfix() {
&::after { content: ""; display: block; clear: both; }
}
/* ── 全局重置 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; font-size: @font-base; }
a { text-decoration: none; color: inherit; }
ul { list-style: none; }
/* ── Topbar ── */
.topbar {
height: 30px;
background: @bg-gray;
border-bottom: 1px solid #e0e0e0;
line-height: 30px;
.inner {
width: @container;
margin: 0 auto;
.clearfix();
}
.left {
float: left;
font-size: @font-sm;
color: @text-gray;
}
.right {
float: right;
a {
font-size: @font-sm;
color: @text-gray;
padding: 0 8px;
border-right: 1px solid @sep-color;
&:last-child { border-right: none; }
&:hover { color: @btn-red; }
}
}
}
/* ── Header ── */
.header {
height: 80px;
background: #fff;
border-bottom: 2px solid @btn-red;
.inner {
width: @container;
margin: 0 auto;
.clearfix();
}
.logo {
float: left;
height: 80px;
line-height: 80px;
font-size: 28px;
font-weight: bold;
color: @btn-red;
letter-spacing: 2px;
}
.search {
float: left;
margin: 20px 0 0 60px;
input {
width: 400px;
height: 36px;
padding: 0 12px;
border: 2px solid @form-red;
border-right: none;
outline: none;
vertical-align: top;
}
button {
width: 70px;
height: 36px;
background: @btn-red;
color: #fff;
border: none;
cursor: pointer;
vertical-align: top;
&:hover { background: darken(@btn-red, 10%); }
}
}
}
/* ── Nav ── */
.nav {
height: 40px;
background: @btn-red;
.inner {
width: @container;
margin: 0 auto;
}
li {
display: inline-block;
height: 40px;
line-height: 40px;
a {
display: block;
padding: 0 18px;
color: #fff;
font-size: @font-base;
&:hover {
background: darken(@btn-red, 15%);
}
}
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<!-- Topbar -->
<div class="topbar">
<div class="inner">
<div class="left">欢迎来到前端学习商城!</div>
<div class="right">
<a href="#">请登录</a>
<a href="#">免费注册</a>
<a href="#">我的订单</a>
<a href="#">客户服务</a>
<a href="#">网站导航</a>
</div>
</div>
</div>
<!-- Header -->
<div class="header">
<div class="inner">
<div class="logo">SHOP</div>
<div class="search">
<input type="text" placeholder="搜索商品、品牌...">
<button>搜 索</button>
</div>
</div>
</div>
<!-- Nav -->
<div class="nav">
<div class="inner">
<ul>
<li><a href="#">全部商品分类</a></li>
<li><a href="#">服装城</a></li>
<li><a href="#">美妆馆</a></li>
<li><a href="#">电器城</a></li>
<li><a href="#">手机</a></li>
<li><a href="#">家居</a></li>
<li><a href="#">超市</a></li>
</ul>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
- 变量层 :
@btn-red、@container、@font-base集中品牌色与排版;改一处全站联动。 - 结构层 :Topbar(浮动左右)→ Header(Logo + 搜索)→ Nav(
li内链 hover),均.inner限宽 +.clearfix()。 - 交互层 :搜索按钮、导航
a:hover用darken(@btn-red, 10%/15%)自动派生深色。
模块对照
| 区块 | LESS 要点 |
|---|---|
| Topbar | 浮动 + clearfix + 链接色变量 |
| Header | 嵌套 .search input/button |
| Nav | display:inline-block + hover darken |
实战场景
- 京东/天猫式 PC 顶栏三段式;与 §4 变量、嵌套、混合、函数综合演练。
7 经典应用场景与真实网站案例
7.1 主题换肤系统
场景: 同一套 UI 组件需要支持多套配色主题(默认/深色/品牌色)。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>主题换肤示例</title>
<style type="text/less">
/* ── 主题变量定义 ── */
/* 默认主题(蓝色)*/
.theme-blue {
@primary: #1a73e8;
@primary-hover: darken(@primary, 10%);
@primary-light: lighten(@primary, 45%);
.btn {
background: @primary;
color: #fff;
&:hover { background: @primary-hover; }
}
.badge {
background: @primary-light;
color: @primary;
}
}
/* 红色主题 */
.theme-red {
@primary: #ea4a36;
@primary-hover: darken(@primary, 10%);
@primary-light: lighten(@primary, 40%);
.btn {
background: @primary;
color: #fff;
&:hover { background: @primary-hover; }
}
.badge {
background: @primary-light;
color: @primary;
}
}
/* 公共样式 */
.btn {
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: inline-block;
margin: 8px;
}
.badge {
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
display: inline-block;
margin: 8px;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="theme-blue">
<button class="btn">蓝色主题按钮</button>
<span class="badge">蓝色徽章</span>
</div>
<div class="theme-red">
<button class="btn">红色主题按钮</button>
<span class="badge">红色徽章</span>
</div>
</body>
</html>
【代码注释】
核心逻辑
.theme-blue/.theme-red父块内定义@primary等变量,子.btn、.badge词法作用域自动取对应主题色。darken/lighten在同一作用域内推导 hover、浅色背景,无需为每主题手写色值表。
构建时 vs 运行时
| 方式 | 机制 |
|---|---|
| 本例(父类作用域) | 编译出多套 CSS 规则 |
Ant Design modifyVars |
构建覆盖全局 @primary-color |
| CSS Variables(§9) | 运行时 data-theme 切换 |
实战场景
- 同一 DOM 挂不同主题 class;B 端后台多品牌皮肤。
真实网站: Ant Design、Element Plus(早期版本)、WeUI 均采用此方案。
7.2 响应式栅格系统
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 栅格系统</title>
<style type="text/less">
@columns: 12;
@gutter: 30px;
@container-sm: 750px;
@container-md: 970px;
@container-lg: 1170px;
/* 生成栅格列的混合 */
.make-col(@n) {
float: left;
width: percentage(@n / @columns);
padding: 0 (@gutter / 2);
min-height: 1px;
}
.row {
margin: 0 -(@gutter / 2);
&::after { content: ""; display: block; clear: both; }
}
/* 手动展开常用列宽(演示)*/
.col-1 { .make-col(1); }
.col-2 { .make-col(2); }
.col-3 { .make-col(3); }
.col-4 { .make-col(4); }
.col-6 { .make-col(6); }
.col-8 { .make-col(8); }
.col-9 { .make-col(9); }
.col-12 { .make-col(12); }
/* 容器 */
.container {
width: 100%;
margin: 0 auto;
padding: 0 15px;
@media (min-width: 768px) { width: @container-sm; }
@media (min-width: 992px) { width: @container-md; }
@media (min-width: 1200px) { width: @container-lg; }
}
/* 辅助样式 */
[class*="col-"] {
background: #e8f4fd;
border: 1px solid #1a73e8;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 12px;
color: #1a73e8;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-3">col-3 (25%)</div>
<div class="col-3">col-3 (25%)</div>
<div class="col-3">col-3 (25%)</div>
<div class="col-3">col-3 (25%)</div>
</div>
<div class="row" style="margin-top:10px">
<div class="col-8">col-8 (66.67%)</div>
<div class="col-4">col-4 (33.33%)</div>
</div>
<div class="row" style="margin-top:10px">
<div class="col-6">col-6 (50%)</div>
<div class="col-6">col-6 (50%)</div>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.make-col(@n):width: percentage(@n / @columns)将 1~12 列转为百分比宽。.row { margin: 0 -(@gutter/2) }+ 列padding: 0 (@gutter/2):gutter 由负 margin 抵消,列间留白一致。
关键公式
列宽 = percentage(n / 12)
半 gutter = @gutter / 2
注意点
- 浮动栅格需
.row::after { clear: both };容器@media切换@container-sm/md/lg。 - 与 §4.11.5 递归
.gen-col等价,本例手写.col-1~.col-12便于阅读。
实战场景
- Bootstrap 3 LESS 源码、后台管理系统多栏布局。
真实网站: Bootstrap 3/4 的栅格系统正是基于这套逻辑,Bootstrap 3 使用 LESS 编写,代码结构与此示例高度相似。
7.3 按钮组件库(Mixin 模式)
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 按钮组件库</title>
<style type="text/less">
/* 按钮基础 mixin */
.btn-base() {
display: inline-block;
padding: 8px 16px;
border: 1px solid transparent;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
cursor: pointer;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
transition: all .15s ease-in-out;
text-decoration: none;
&:disabled {
opacity: .65;
cursor: not-allowed;
}
}
/* 颜色变体 mixin */
.btn-variant(@bg, @border, @color: #fff) {
background: @bg;
border-color: @border;
color: @color;
&:hover, &:focus {
background: darken(@bg, 8%);
border-color: darken(@border, 12%);
}
&:active {
background: darken(@bg, 15%);
border-color: darken(@border, 20%);
}
}
/* 尺寸变体 */
.btn-size(@padding-y, @padding-x, @font-size, @border-radius) {
padding: @padding-y @padding-x;
font-size: @font-size;
border-radius: @border-radius;
}
/* 生成按钮类 */
.btn { .btn-base(); }
.btn-primary { .btn-base(); .btn-variant(#1a73e8, #1a73e8); }
.btn-success { .btn-base(); .btn-variant(#34a853, #2e9147); }
.btn-danger { .btn-base(); .btn-variant(#ea4a36, #d43b27); }
.btn-warning { .btn-base(); .btn-variant(#fbbc04, #e8ab00, #333); }
.btn-secondary { .btn-base(); .btn-variant(#6c757d, #6c757d); }
.btn-outline { .btn-base(); .btn-variant(transparent, #1a73e8, #1a73e8); }
/* 尺寸修饰 */
.btn-lg { .btn-size(10px, 20px, 16px, 6px); }
.btn-sm { .btn-size(5px, 10px, 12px, 3px); }
/* 布局 */
body { padding: 20px; }
.btn-row { margin-bottom: 12px; }
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="btn-row">
<button class="btn-primary">Primary</button>
<button class="btn-success">Success</button>
<button class="btn-danger">Danger</button>
<button class="btn-warning">Warning</button>
<button class="btn-secondary">Secondary</button>
<button class="btn-outline">Outline</button>
</div>
<div class="btn-row">
<button class="btn-primary btn-lg">大号按钮</button>
<button class="btn-primary">默认按钮</button>
<button class="btn-primary btn-sm">小号按钮</button>
</div>
<div class="btn-row">
<button class="btn-primary" disabled>禁用状态</button>
</div>
</body>
</html>
【代码注释】
核心逻辑
.btn-base()提供盒模型与过渡;.btn-variant(@bg, @border, @color)用darken生成 hover/active/focus 整套状态色。.btn-size(@padding, @font-size)+.btn-lg/.btn-sm修饰符:基础类 + 尺寸类叠加(类 BEM)。
Mixin 分层
| Mixin | 职责 |
|---|---|
btn-base |
布局、圆角、cursor |
btn-variant |
颜色与交互态 |
btn-size |
内边距、字号 |
注意点
- Warning 等浅色底需传
@color: #333,默认#fff在浅黄底上对比度不足。 - 与 Bootstrap
button-variant()同源思路,适合组件库统一出口。
实战场景
- 后台 Primary/Danger/Warning 按钮族;改
@brand-primary即可换肤。
真实网站: Bootstrap 4/5 的按钮系统正是这种混合模式------button-variant() 混合接收颜色参数,自动生成完整的按钮状态样式。
7.4 表单组件系统
场景: 电商、后台管理等项目中,表单是最高频的 UI 元素。通过 LESS 混合统一定义输入框、选择框、单选/复选框的焦点色、错误色、禁用色,修改一个变量即可全局同步。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 表单组件系统</title>
<style type="text/less">
/* ── 表单主题变量 ── */
@input-border: #d9d9d9;
@input-focus-border: #1a73e8;
@input-error-border: #ea4a36;
@input-success-border:#34a853;
@input-disabled-bg: #f5f5f5;
@input-disabled-color:#bbb;
@input-height: 36px;
@input-radius: 4px;
@label-color: #333;
@helper-color: #999;
@error-color: #ea4a36;
/* ── 基础输入框混合 ── */
.input-base() {
display: block;
width: 100%;
height: @input-height;
padding: 0 12px;
font-size: 14px;
line-height: @input-height;
color: #333;
background: #fff;
border: 1px solid @input-border;
border-radius: @input-radius;
outline: none;
transition: border-color .2s, box-shadow .2s;
box-sizing: border-box;
&::placeholder { color: #bbb; }
&:focus {
border-color: @input-focus-border;
box-shadow: 0 0 0 3px fade(@input-focus-border, 15%);
}
&:disabled {
background: @input-disabled-bg;
color: @input-disabled-color;
cursor: not-allowed;
}
}
/* ── 状态变体混合 ── */
.input-state(@border-color, @shadow-color) {
border-color: @border-color;
&:focus {
border-color: @border-color;
box-shadow: 0 0 0 3px fade(@shadow-color, 15%);
}
}
/* ── 表单组容器 ── */
.form-group {
margin-bottom: 20px;
label {
display: block;
font-size: 14px;
color: @label-color;
font-weight: 500;
margin-bottom: 6px;
.required {
color: @error-color;
margin-left: 2px;
}
}
.helper-text {
font-size: 12px;
color: @helper-color;
margin-top: 4px;
}
.error-text {
font-size: 12px;
color: @error-color;
margin-top: 4px;
&::before {
content: "⚠ ";
}
}
}
/* ── 各种输入控件 ── */
.form-input { .input-base(); }
.form-textarea {
.input-base();
height: auto;
min-height: 100px;
padding: 10px 12px;
line-height: 1.6;
resize: vertical;
}
.form-select {
.input-base();
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M6 8L0 0h12z' fill='%23999'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 32px;
}
/* ── 状态类 ── */
.form-input.is-error { .input-state(@input-error-border, @input-error-border); }
.form-input.is-success { .input-state(@input-success-border, @input-success-border); }
/* ── 自定义复选框 ── */
.form-checkbox {
display: flex;
align-items: center;
cursor: pointer;
gap: 8px;
font-size: 14px;
color: @label-color;
user-select: none;
input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: @input-focus-border;
cursor: pointer;
flex-shrink: 0;
}
}
/* ── 表单布局 ── */
.form-row {
display: flex;
gap: 16px;
.form-group { flex: 1; }
}
/* ── 提交按钮区 ── */
.form-actions {
display: flex;
gap: 12px;
margin-top: 8px;
button {
padding: 9px 24px;
border-radius: @input-radius;
font-size: 14px;
cursor: pointer;
border: 1px solid transparent;
transition: all .2s;
}
.btn-submit {
background: @input-focus-border;
color: #fff;
&:hover { background: darken(@input-focus-border, 10%); }
}
.btn-reset {
background: #fff;
color: #666;
border-color: @input-border;
&:hover {
border-color: @input-focus-border;
color: @input-focus-border;
}
}
}
/* ── 整体页面 ── */
body { padding: 32px; font-family: "Microsoft YaHei", sans-serif; background: #f0f2f5; }
.form-card {
max-width: 560px;
background: #fff;
border-radius: 8px;
padding: 32px;
box-shadow: 0 2px 12px rgba(0,0,0,.08);
h2 {
font-size: 18px;
color: #333;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="form-card">
<h2>用户注册</h2>
<div class="form-row">
<div class="form-group">
<label>姓名 <span class="required">*</span></label>
<input class="form-input" type="text" placeholder="请输入真实姓名">
</div>
<div class="form-group">
<label>手机号 <span class="required">*</span></label>
<input class="form-input is-error" type="tel" placeholder="请输入手机号" value="138abc">
<div class="error-text">手机号格式不正确</div>
</div>
</div>
<div class="form-group">
<label>邮箱</label>
<input class="form-input is-success" type="email" value="user@example.com">
<div class="helper-text">注册成功后将发送验证邮件</div>
</div>
<div class="form-group">
<label>城市</label>
<select class="form-select">
<option>请选择所在城市</option>
<option>北京</option>
<option>上海</option>
<option>广州</option>
</select>
</div>
<div class="form-group">
<label>个人简介</label>
<textarea class="form-textarea" placeholder="请简单介绍自己..."></textarea>
</div>
<div class="form-group">
<label class="form-checkbox">
<input type="checkbox" checked>
已阅读并同意用户协议
</label>
</div>
<div class="form-actions">
<button class="btn-submit">注 册</button>
<button class="btn-reset">重 置</button>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.input-base()统一边框、圆角、过渡;:focus用fade(@input-focus-border, 15%)做与边框同源的 focus ring。.input-state(@border, @shadow)驱动.is-error/.is-success,错误/成功态复用同一套焦点逻辑。.form-textarea调用.input-base()后只覆盖height、resize等差异项。
变量驱动
| 变量 | 影响范围 |
|---|---|
@input-focus-border |
所有输入框焦点环 |
@input-error-color |
.is-error 边框与阴影 |
注意点
fade保证光晕色相与边框一致,避免手写rgba色差。- 禁用态、只读态可再扩展 mixin 参数或修饰类。
实战场景
- B 端表单、登录页;品牌色变更只改变量文件。
真实网站场景: 阿里云控制台、飞书的表单系统均使用类似的变量驱动方案,焦点色、错误色与品牌主题色保持一致。
7.5 导航菜单与下拉菜单
场景: 水平主导航 + 二级下拉菜单是门户网站、电商网站最核心的交互组件,用 LESS 混合封装后可以复用于多个页面。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 导航菜单系统</title>
<style type="text/less">
/* ── 导航变量 ── */
@nav-bg: #2c3e50;
@nav-hover-bg: darken(@nav-bg, 8%);
@nav-active-bg: #1a73e8;
@nav-text: rgba(255,255,255,.85);
@nav-text-hover: #fff;
@nav-height: 56px;
@dropdown-bg: #fff;
@dropdown-border: #e8e8e8;
@dropdown-shadow: 0 4px 16px rgba(0,0,0,.12);
@dropdown-hover: #f0f8ff;
/* ── CSS 三角形混合(课堂案例复用) ── */
.triangle(@size, @color, @dir) when (@dir = down) {
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: @color transparent transparent transparent;
}
.triangle(@size, @color, @dir) when (@dir = up) {
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: transparent transparent @color transparent;
}
/* ── 全局重置 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; font-size: 14px; }
ul { list-style: none; }
a { text-decoration: none; color: inherit; }
/* ── 主导航容器 ── */
.main-nav {
background: @nav-bg;
height: @nav-height;
position: relative;
z-index: 1000;
.nav-inner {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
height: 100%;
}
/* 一级菜单项 */
> .nav-inner > ul {
display: flex;
height: 100%;
> li {
position: relative;
height: 100%;
display: flex;
align-items: center;
> a {
display: flex;
align-items: center;
gap: 6px;
height: 100%;
padding: 0 20px;
color: @nav-text;
font-size: 14px;
transition: background .2s, color .2s;
white-space: nowrap;
&:hover {
background: @nav-hover-bg;
color: @nav-text-hover;
}
/* 下拉箭头(使用三角形混合) */
.arrow {
.triangle(4px, @nav-text, down);
display: inline-block;
transition: transform .2s;
margin-top: 2px;
}
}
/* 激活状态 */
&.active > a {
background: @nav-active-bg;
color: #fff;
}
/* 悬停时展开下拉,箭头翻转 */
&:hover > a .arrow {
.triangle(4px, @nav-text-hover, up);
}
/* 下拉菜单 */
.dropdown {
position: absolute;
top: 100%;
left: 0;
min-width: 180px;
background: @dropdown-bg;
border: 1px solid @dropdown-border;
border-top: 2px solid @nav-active-bg;
border-radius: 0 0 4px 4px;
box-shadow: @dropdown-shadow;
opacity: 0;
visibility: hidden;
transform: translateY(-6px);
transition: opacity .2s, transform .2s, visibility .2s;
li a {
display: block;
padding: 10px 16px;
color: #333;
font-size: 13px;
transition: background .15s;
border-bottom: 1px solid @dropdown-border;
&:hover {
background: @dropdown-hover;
color: @nav-active-bg;
padding-left: 20px;
}
&:last-child { border-bottom: none; }
}
}
/* 悬停展开下拉 */
&:hover .dropdown {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
}
}
}
/* ── 演示内容区 ── */
.demo-content {
padding: 40px;
text-align: center;
color: #999;
font-size: 16px;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<nav class="main-nav">
<div class="nav-inner">
<ul>
<li class="active"><a href="#">首页</a></li>
<li>
<a href="#">商品分类 <span class="arrow"></span></a>
<ul class="dropdown">
<li><a href="#">数码电器</a></li>
<li><a href="#">服装鞋包</a></li>
<li><a href="#">食品生鲜</a></li>
<li><a href="#">家居家装</a></li>
<li><a href="#">运动户外</a></li>
</ul>
</li>
<li>
<a href="#">品牌专区 <span class="arrow"></span></a>
<ul class="dropdown">
<li><a href="#">国内品牌</a></li>
<li><a href="#">进口品牌</a></li>
<li><a href="#">新锐品牌</a></li>
</ul>
</li>
<li><a href="#">限时特卖</a></li>
<li><a href="#">积分商城</a></li>
<li><a href="#">企业采购</a></li>
</ul>
</div>
</nav>
<div class="demo-content">悬停菜单项查看下拉效果</div>
</body>
</html>
【代码注释】
核心逻辑
- 下拉显隐:
opacity+visibility+transform联动过渡,避免display切换无法动画的问题。 - 父级
li:hover展开.dropdown-panel;.triangle(down/up)Guards 控制箭头方向翻转。 translateY(-6px) → 0微位移增强「落下」感;顶栏border-top用@nav-active-bg强调品牌色。
关键 API
- §4.4
.triangle()复用;嵌套&绑定li:hover .panel。 - 主题变量统一
@nav-bg、@nav-active-bg。
注意点
- 需处理键盘焦点与移动端点击(本示例为 PC hover 教学)。
- 多级导航注意
z-index与面板position: absolute。
实战场景
- 京东/天猫顶栏「全部商品分类」、频道 mega menu。
真实网站场景: 京东、天猫、苏宁易购的顶部导航菜单均采用此悬停下拉模式,导航背景色、下拉高亮色均通过主题变量统一管理。
7.6 卡片组件与骨架屏
场景: 商品列表、文章列表等场景下,卡片组件是最核心的展示单元;骨架屏则在数据加载时替代卡片,提升用户感知性能。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 卡片组件与骨架屏</title>
<style type="text/less">
/* ── 卡片变量 ── */
@card-radius: 8px;
@card-shadow: 0 2px 12px rgba(0,0,0,.08);
@card-shadow-hover: 0 8px 24px rgba(0,0,0,.15);
@card-bg: #fff;
@tag-radius: 3px;
@skeleton-color: #e8e8e8;
@skeleton-highlight: #f5f5f5;
/* ── 混合库 ── */
.ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.clamp(@lines) {
display: -webkit-box;
-webkit-line-clamp: @lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-base() {
background: @card-bg;
border-radius: @card-radius;
box-shadow: @card-shadow;
overflow: hidden;
transition: box-shadow .3s, transform .3s;
&:hover {
box-shadow: @card-shadow-hover;
transform: translateY(-4px);
}
}
/* ── 全局 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; font-family: "Microsoft YaHei", sans-serif; padding: 24px; }
a { text-decoration: none; color: inherit; }
/* ── 商品卡片 ── */
.product-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
max-width: 1000px;
}
.product-card {
.card-base();
cursor: pointer;
.card-img {
width: 100%;
height: 200px;
object-fit: cover;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
font-size: 40px;
position: relative;
.badge-tag {
position: absolute;
top: 10px;
left: 10px;
background: #ea4a36;
color: #fff;
font-size: 11px;
padding: 2px 6px;
border-radius: @tag-radius;
}
}
.card-body {
padding: 12px;
.card-title {
font-size: 14px;
color: #333;
line-height: 1.5;
margin-bottom: 8px;
.clamp(2);
}
.card-price {
display: flex;
align-items: baseline;
gap: 6px;
margin-bottom: 8px;
.price-now {
font-size: 18px;
font-weight: bold;
color: #ea4a36;
&::before { content: "¥"; font-size: 13px; }
}
.price-origin {
font-size: 12px;
color: #999;
text-decoration: line-through;
&::before { content: "¥"; }
}
}
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: #999;
}
}
}
/* ── 骨架屏动画 ── */
@keyframes skeleton-shimmer {
0% { background-position: -400px 0; }
100% { background-position: 400px 0; }
}
.skeleton-block() {
background: linear-gradient(
90deg,
@skeleton-color 25%,
@skeleton-highlight 50%,
@skeleton-color 75% );
background-size: 800px 100%;
animation: skeleton-shimmer 1.5s infinite linear;
border-radius: 4px;
}
.skeleton-card {
.card-base();
.skeleton-img {
.skeleton-block();
width: 100%;
height: 200px;
border-radius: 0;
}
.skeleton-body {
padding: 12px;
.skeleton-line {
.skeleton-block();
height: 14px;
margin-bottom: 8px;
&.line-full { width: 100%; }
&.line-3-4 { width: 75%; }
&.line-half { width: 50%; }
&.line-1-3 { width: 33%; }
}
.skeleton-price {
.skeleton-block();
width: 60px;
height: 20px;
margin-bottom: 8px;
}
}
}
/* ── 切换演示 ── */
.section-title {
font-size: 16px;
color: #333;
font-weight: 500;
margin: 0 0 16px;
}
.section { margin-bottom: 32px; }
</style>
<script src="./less.js"></script>
</head>
<body>
<!-- 骨架屏(加载中状态) -->
<div class="section">
<p class="section-title">骨架屏(数据加载中...)</p>
<div class="product-grid">
<div class="skeleton-card">
<div class="skeleton-img"></div>
<div class="skeleton-body">
<div class="skeleton-line line-full"></div>
<div class="skeleton-line line-3-4"></div>
<div class="skeleton-price"></div>
<div class="skeleton-line line-half"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-img"></div>
<div class="skeleton-body">
<div class="skeleton-line line-full"></div>
<div class="skeleton-line line-3-4"></div>
<div class="skeleton-price"></div>
<div class="skeleton-line line-half"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-img"></div>
<div class="skeleton-body">
<div class="skeleton-line line-full"></div>
<div class="skeleton-line line-3-4"></div>
<div class="skeleton-price"></div>
<div class="skeleton-line line-half"></div>
</div>
</div>
<div class="skeleton-card">
<div class="skeleton-img"></div>
<div class="skeleton-body">
<div class="skeleton-line line-full"></div>
<div class="skeleton-line line-3-4"></div>
<div class="skeleton-price"></div>
<div class="skeleton-line line-half"></div>
</div>
</div>
</div>
</div>
<!-- 实际内容(数据加载完成) -->
<div class="section">
<p class="section-title">商品列表(数据加载完成)</p>
<div class="product-grid">
<div class="product-card">
<div class="card-img">
🎧
<span class="badge-tag">热卖</span>
</div>
<div class="card-body">
<div class="card-title">头戴式无线蓝牙降噪耳机,旗舰级音质,超长续航</div>
<div class="card-price">
<span class="price-now">299</span>
<span class="price-origin">599</span>
</div>
<div class="card-meta">
<span>已售 12,680 件</span>
<span>⭐ 4.9</span>
</div>
</div>
</div>
<div class="product-card">
<div class="card-img">📱</div>
<div class="card-body">
<div class="card-title">智能手机 6.7英寸大屏 5000mAh电池</div>
<div class="card-price">
<span class="price-now">1,299</span>
<span class="price-origin">1,999</span>
</div>
<div class="card-meta">
<span>已售 5,430 件</span>
<span>⭐ 4.8</span>
</div>
</div>
</div>
<div class="product-card">
<div class="card-img">
💻
<span class="badge-tag">NEW</span>
</div>
<div class="card-body">
<div class="card-title">轻薄笔记本电脑 16GB内存 512GB固态</div>
<div class="card-price">
<span class="price-now">4,299</span>
<span class="price-origin">5,499</span>
</div>
<div class="card-meta">
<span>已售 2,100 件</span>
<span>⭐ 4.7</span>
</div>
</div>
</div>
<div class="product-card">
<div class="card-img">⌨️</div>
<div class="card-body">
<div class="card-title">机械键盘 87键 青轴 RGB背光</div>
<div class="card-price">
<span class="price-now">359</span>
<span class="price-origin">499</span>
</div>
<div class="card-meta">
<span>已售 8,900 件</span>
<span>⭐ 4.9</span>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.skeleton-block():线性渐变底色 +@keyframes skeleton-shine流光扫描,图片/标题/价格区统一调用。.clamp(@lines):-webkit-line-clamp参数化多行省略,标题 2 行、描述 3 行各传参即可。@skeleton-color/@skeleton-highlight控制明暗主题骨架色。
注意点
- 骨架块尺寸应尽量与真实卡片 DOM 结构一致,减少布局跳动(CLS)。
line-clamp需配合display: -webkit-box、-webkit-box-orient: vertical。
实战场景
- 列表首屏加载、商品卡片占位;暗色模式改两变量即可。
真实网站场景: 淘宝、抖音、知乎在列表页均使用骨架屏替代 loading 动画,配合 LESS/SCSS 变量驱动,保证骨架屏样式与实际卡片布局严格对应。
7.7 提示工具(Tooltip)与气泡确认框
场景: 图标按钮说明、表格操作列提示、表单字段帮助文字,Tooltip 是高频交互组件,通过 LESS 混合生成四方向版本。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS Tooltip 工具提示</title>
<style type="text/less">
/* ── Tooltip 变量 ── */
@tip-bg: #333;
@tip-color: #fff;
@tip-radius: 4px;
@tip-size: 5px; /* 三角箭头尺寸 */
@tip-font: 12px;
@tip-padding: 6px 10px;
@tip-delay: .1s;
/* ── 三角形混合(课堂案例扩展) ── */
.arrow-down(@size, @color) {
.triangle(@size, @color, down);
}
.arrow-up(@size, @color) {
.triangle(@size, @color, up);
}
.triangle(@size, @color, @dir) when (@dir = down) {
content: "";
position: absolute;
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: @color transparent transparent transparent;
}
.triangle(@size, @color, @dir) when (@dir = up) {
content: "";
position: absolute;
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: transparent transparent @color transparent;
}
.triangle(@size, @color, @dir) when (@dir = left) {
content: "";
position: absolute;
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: transparent @color transparent transparent;
}
.triangle(@size, @color, @dir) when (@dir = right) {
content: "";
position: absolute;
width: 0; height: 0;
border-style: solid;
border-width: @size;
border-color: transparent transparent transparent @color;
}
/* ── Tooltip 核心混合 ── */
.tooltip-base() {
position: absolute;
background: @tip-bg;
color: @tip-color;
font-size: @tip-font;
padding: @tip-padding;
border-radius: @tip-radius;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity @tip-delay, transform @tip-delay;
z-index: 9999;
}
/* ── 四方向 Tooltip ── */
[data-tip] {
position: relative;
display: inline-block;
/* Tooltip 文字气泡 */
&::before {
content: attr(data-tip); /* 直接读取 data-tip 属性值 */
.tooltip-base();
}
/* 箭头 */
&::after { position: absolute; }
/* 悬停显示 */
&:hover::before,
&:hover::after {
opacity: 1;
}
}
/* 上方 Tooltip(默认) */
[data-tip] {
&::before {
bottom: calc(100% + @tip-size + 4px);
left: 50%;
transform: translateX(-50%) translateY(4px);
}
&::after {
.triangle(@tip-size, @tip-bg, down);
bottom: calc(100% + 4px);
left: 50%;
transform: translateX(-50%) translateY(4px);
}
&:hover::before { transform: translateX(-50%) translateY(0); }
&:hover::after { transform: translateX(-50%) translateY(0); }
}
/* 下方 Tooltip */
[data-tip-dir="bottom"] {
&::before {
top: calc(100% + @tip-size + 4px);
bottom: auto;
left: 50%;
transform: translateX(-50%) translateY(-4px);
}
&::after {
.triangle(@tip-size, @tip-bg, up);
top: calc(100% + 4px);
bottom: auto;
left: 50%;
transform: translateX(-50%) translateY(-4px);
}
&:hover::before { transform: translateX(-50%) translateY(0); }
&:hover::after { transform: translateX(-50%) translateY(0); }
}
/* 右方 Tooltip */
[data-tip-dir="right"] {
&::before {
left: calc(100% + @tip-size + 4px);
top: 50%;
bottom: auto;
transform: translateY(-50%) translateX(-4px);
}
&::after {
.triangle(@tip-size, @tip-bg, right);
left: calc(100% + 4px);
top: 50%;
bottom: auto;
transform: translateY(-50%) translateX(-4px);
}
&:hover::before { transform: translateY(-50%) translateX(0); }
&:hover::after { transform: translateY(-50%) translateX(0); }
}
/* ── 演示布局 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; padding: 80px 40px; background: #f0f2f5; }
.demo-row {
display: flex;
gap: 48px;
align-items: center;
flex-wrap: wrap;
}
.tip-btn {
padding: 10px 20px;
background: #1a73e8;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.icon-btn {
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid #ddd;
background: #fff;
cursor: pointer;
font-size: 18px;
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="demo-row">
<!-- 上方提示(默认) -->
<button class="tip-btn" data-tip="上方提示气泡">悬停我(上)</button>
<!-- 下方提示 -->
<button class="tip-btn" data-tip="下方提示气泡" data-tip-dir="bottom">悬停我(下)</button>
<!-- 右方提示 -->
<button class="tip-btn" data-tip="右侧提示气泡" data-tip-dir="right">悬停我(右)</button>
<!-- 图标按钮(常见场景) -->
<span class="icon-btn" data-tip="删除该记录">🗑️</span>
<span class="icon-btn" data-tip="编辑信息">✏️</span>
<span class="icon-btn" data-tip="分享链接" data-tip-dir="bottom">🔗</span>
</div>
</body>
</html>
【代码注释】
核心逻辑
[data-tip]+::before/::after:content: attr(data-tip)纯 CSS 显示提示文案,无 JS。data-tip-dir属性选择器区分上下左右;.triangle()按方向生成箭头。opacity+transform过渡实现悬停「弹出」微交互。
属性驱动
| 属性 | 作用 |
|---|---|
data-tip |
提示文案 |
data-tip-dir |
top/right/bottom/left |
注意点
- 纯 CSS Tooltip 无焦点陷阱与 ARIA,生产复杂场景仍用组件库。
- 箭头颜色需与气泡背景
@tip-bg一致。
实战场景
- 图标按钮说明、表格操作列;GitHub / Ant Design Table 图标提示。
真实网站场景: GitHub 仓库页面的按钮图标(Star、Fork、Watch)、Ant Design Table 组件的操作列图标,均大量使用 Tooltip 提供文字说明。
7.8 加载状态与进度条
场景: 文件上传、页面跳转、数据请求等场景需要视觉反馈,用 LESS 混合封装各类加载指示器。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 加载状态与进度条</title>
<style type="text/less">
/* ── 加载变量 ── */
@primary: #1a73e8;
@success: #34a853;
@warning: #fbbc04;
@danger: #ea4a36;
@spinner-size: 40px;
@bar-height: 8px;
@bar-radius: 100px;
/* ── 加载动画关键帧 ── */
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: .4; transform: scale(.8); }
}
@keyframes bar-fill {
0% { background-position: 40px 0; }
100% { background-position: 0 0; }
}
@keyframes bar-slide {
0% { left: -40%; width: 40%; }
60% { left: 100%; width: 50%; }
100% { left: 100%; width: 50%; }
}
/* ── Spinner 混合 ── */
.spinner-base(@size: @spinner-size, @border-width: 3px) {
display: inline-block;
width: @size;
height: @size;
border-radius: 50%;
border: @border-width solid rgba(0,0,0,.1);
animation: spin .8s linear infinite;
}
/* ── 进度条混合 ── */
.progress-bar(@color, @percent) {
width: 100%;
height: @bar-height;
background: lighten(@color, 38%);
border-radius: @bar-radius;
overflow: hidden;
.bar-inner {
width: @percent;
height: 100%;
background: @color;
border-radius: @bar-radius;
transition: width .4s ease;
position: relative;
overflow: hidden;
/* 条纹流光效果 */
&.animated::after {
content: "";
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background: repeating-linear-gradient(
45deg,
rgba(255,255,255,.15) 0px,
rgba(255,255,255,.15) 10px,
transparent 10px,
transparent 20px
);
background-size: 40px 100%;
animation: bar-fill .8s linear infinite;
}
}
}
/* ── 全局 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; padding: 40px; background: #f0f2f5; }
h3 {
font-size: 15px;
color: #333;
margin: 24px 0 16px;
font-weight: 500;
}
/* ── Spinner 实例 ── */
.spinner-wrap {
display: flex;
align-items: center;
gap: 24px;
}
.spinner-primary {
.spinner-base(40px, 3px);
border-top-color: @primary;
}
.spinner-success {
.spinner-base(40px, 3px);
border-top-color: @success;
}
.spinner-danger {
.spinner-base(40px, 3px);
border-top-color: @danger;
}
.spinner-sm {
.spinner-base(20px, 2px);
border-top-color: @primary;
}
.spinner-lg {
.spinner-base(60px, 4px);
border-top-color: @primary;
}
/* ── 点状加载(三点跳动) ── */
.dots-loader {
display: flex;
gap: 6px;
align-items: center;
span {
width: 8px;
height: 8px;
border-radius: 50%;
background: @primary;
animation: pulse 1.4s ease-in-out infinite;
&:nth-child(2) { animation-delay: .2s; }
&:nth-child(3) { animation-delay: .4s; }
}
}
/* ── 进度条实例 ── */
.progress-list {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 500px;
.progress-item {
display: flex;
flex-direction: column;
gap: 4px;
.progress-label {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #666;
}
}
}
.progress-primary { .progress-bar(@primary, 72%); }
.progress-success { .progress-bar(@success, 95%); }
.progress-warning { .progress-bar(@warning, 45%); }
.progress-danger { .progress-bar(@danger, 20%); }
/* ── 顶部进度条(页面跳转进度)── */
.page-progress {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: @primary;
box-shadow: 0 0 8px @primary;
z-index: 10000;
animation: none;
/* 不确定进度(indeterminate) */
&.indeterminate {
width: 100%;
overflow: hidden;
background: lighten(@primary, 35%);
&::after {
content: "";
position: absolute;
top: 0;
height: 100%;
background: @primary;
animation: bar-slide 2s ease-in-out infinite;
}
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<!-- 顶部进度条 -->
<div class="page-progress indeterminate"></div>
<h3>圆形加载器(Spinner)</h3>
<div class="spinner-wrap">
<div class="spinner-sm"></div>
<div class="spinner-primary"></div>
<div class="spinner-success"></div>
<div class="spinner-danger"></div>
<div class="spinner-lg"></div>
</div>
<h3>点状加载动画</h3>
<div class="dots-loader">
<span></span><span></span><span></span>
</div>
<h3>进度条</h3>
<div class="progress-list">
<div class="progress-item">
<div class="progress-label"><span>编程能力</span><span>72%</span></div>
<div class="progress-primary"><div class="bar-inner animated"></div></div>
</div>
<div class="progress-item">
<div class="progress-label"><span>项目完成</span><span>95%</span></div>
<div class="progress-success"><div class="bar-inner animated"></div></div>
</div>
<div class="progress-item">
<div class="progress-label"><span>文件上传</span><span>45%</span></div>
<div class="progress-warning"><div class="bar-inner animated"></div></div>
</div>
<div class="progress-item">
<div class="progress-label"><span>磁盘使用</span><span>20%</span></div>
<div class="progress-danger"><div class="bar-inner"></div></div>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
.spinner-base(@size, @border-width):旋转圆环尺寸参数化,.spinner-sm/lg一行调用。.progress-bar(@color, @percent):lighten自动生成轨道色,填充宽用@percent控制。- 条纹进度:
repeating-linear-gradient+background-position动画;顶栏不确定进度仿 NProgress 伪元素滑动。
Mixin 职责
| Mixin | 输出 |
|---|---|
spinner-base |
加载旋转器 |
progress-bar |
确定进度 + 条纹动画 |
page-progress |
全宽顶部 indeterminate |
实战场景
- 上传/提交等待、路由切换顶栏;YouTube、Ant Design Progress 同类实现。
真实网站场景: YouTube 顶部红色进度条(页面跳转时)、微信网页版文件发送进度条、Ant Design Progress 组件均采用类似方案。
7.9 模态框(Modal)弹窗系统
场景: 登录框、确认删除、图片预览,模态框是任何 Web 应用都不可缺少的组件,通过 LESS 变量统一尺寸、圆角、遮罩透明度。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS 模态框系统</title>
<style type="text/less">
/* ── 模态框变量 ── */
@modal-backdrop: rgba(0,0,0,.5);
@modal-bg: #fff;
@modal-radius: 8px;
@modal-shadow: 0 8px 40px rgba(0,0,0,.2);
@modal-sm-width: 400px;
@modal-md-width: 560px;
@modal-lg-width: 800px;
@modal-padding: 24px;
@modal-header-border: #f0f0f0;
@modal-footer-border: #f0f0f0;
@close-color: #999;
@close-hover: #333;
/* ── 动画 ── */
@keyframes modal-in {
from { opacity: 0; transform: scale(.92) translateY(-10px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes backdrop-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* ── 混合库 ── */
.flex-between() {
display: flex;
justify-content: space-between;
align-items: center;
}
.clearfix() {
&::after { content: ""; display: block; clear: both; }
}
/* ── 全局 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; font-size: 14px; background: #f0f2f5; }
/* ── 遮罩层 ── */
.modal-backdrop {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: @modal-backdrop;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
animation: backdrop-in .2s ease;
}
/* ── 模态框容器基础 ── */
.modal {
background: @modal-bg;
border-radius: @modal-radius;
box-shadow: @modal-shadow;
width: @modal-md-width;
max-width: calc(100vw - 32px);
max-height: calc(100vh - 64px);
display: flex;
flex-direction: column;
animation: modal-in .2s ease;
/* 尺寸变体 */
&.modal-sm { width: @modal-sm-width; }
&.modal-lg { width: @modal-lg-width; }
/* ── 头部 ── */
.modal-header {
.flex-between();
padding: @modal-padding;
border-bottom: 1px solid @modal-header-border;
flex-shrink: 0;
.modal-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.modal-close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
color: @close-color;
font-size: 18px;
transition: color .15s, background .15s;
border: none;
background: transparent;
&:hover {
color: @close-hover;
background: #f5f5f5;
}
}
}
/* ── 内容区 ── */
.modal-body {
padding: @modal-padding;
overflow-y: auto;
flex: 1;
line-height: 1.7;
color: #555;
font-size: 14px;
}
/* ── 底部操作区 ── */
.modal-footer {
.flex-between();
padding: @modal-padding;
border-top: 1px solid @modal-footer-border;
flex-shrink: 0;
gap: 12px;
/* 默认右对齐 */
justify-content: flex-end;
button {
padding: 8px 20px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
border: 1px solid transparent;
transition: all .15s;
&.btn-primary {
background: #1a73e8;
color: #fff;
&:hover { background: darken(#1a73e8, 10%); }
}
&.btn-default {
background: #fff;
color: #666;
border-color: #d9d9d9;
&:hover {
border-color: #1a73e8;
color: #1a73e8;
}
}
&.btn-danger {
background: #ea4a36;
color: #fff;
&:hover { background: darken(#ea4a36, 10%); }
}
}
}
}
/* ── 确认框变体 ── */
.modal-confirm {
.modal();
width: @modal-sm-width;
.modal-body {
display: flex;
gap: 16px;
align-items: flex-start;
padding: @modal-padding;
.confirm-icon {
font-size: 24px;
flex-shrink: 0;
line-height: 1;
}
.confirm-content {
.confirm-title {
font-size: 15px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.confirm-desc { color: #666; font-size: 13px; }
}
}
}
/* ── 触发按钮 ── */
.demo {
padding: 48px;
display: flex;
gap: 16px;
flex-wrap: wrap;
align-items: center;
button {
padding: 10px 20px;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
border: 1px solid transparent;
&.open-btn {
background: #1a73e8;
color: #fff;
&:hover { background: darken(#1a73e8, 8%); }
}
&.confirm-btn {
background: #ea4a36;
color: #fff;
&:hover { background: darken(#ea4a36, 8%); }
}
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="demo">
<button class="open-btn" onclick="document.getElementById('modal1').style.display='flex'">
打开普通弹窗
</button>
<button class="confirm-btn" onclick="document.getElementById('modal2').style.display='flex'">
打开确认框
</button>
</div>
<!-- 普通模态框 -->
<div class="modal-backdrop" id="modal1" style="display:none"
onclick="if(event.target===this)this.style.display='none'">
<div class="modal">
<div class="modal-header">
<span class="modal-title">用户信息编辑</span>
<button class="modal-close" onclick="document.getElementById('modal1').style.display='none'">×</button>
</div>
<div class="modal-body">
<p>这里是模态框的内容区域,可以放置任意内容,包括表单、图片、列表等。</p>
<br>
<p>点击遮罩层或右上角 × 按钮关闭弹窗。模态框支持 sm / md / lg 三种宽度规格,通过添加 `.modal-sm` 或 `.modal-lg` 类切换。</p>
</div>
<div class="modal-footer">
<button class="btn-default" onclick="document.getElementById('modal1').style.display='none'">取 消</button>
<button class="btn-primary" onclick="document.getElementById('modal1').style.display='none'">确 定</button>
</div>
</div>
</div>
<!-- 确认框 -->
<div class="modal-backdrop" id="modal2" style="display:none"
onclick="if(event.target===this)this.style.display='none'">
<div class="modal modal-sm">
<div class="modal-header">
<span class="modal-title">删除确认</span>
<button class="modal-close" onclick="document.getElementById('modal2').style.display='none'">×</button>
</div>
<div class="modal-body" style="display:flex;gap:16px;align-items:flex-start">
<span style="font-size:24px;line-height:1">⚠️</span>
<div>
<div style="font-size:15px;font-weight:600;color:#333;margin-bottom:8px">确认删除该商品?</div>
<div style="color:#666;font-size:13px">删除后数据无法恢复,请谨慎操作。</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-default" onclick="document.getElementById('modal2').style.display='none'">取 消</button>
<button class="btn-danger" onclick="document.getElementById('modal2').style.display='none'">确认删除</button>
</div>
</div>
</div>
</body>
</html>
【代码注释】
核心逻辑
- 尺寸变量
@modal-sm/md/lg-width+ 修饰类嵌套覆盖width,单.modal骨架复用。 modal-in关键帧:scale + translateY 弹入;遮罩fade与内容区overflow-y: auto处理长表单。.flex-between()复用于 header/footer 左右分布(标题↔关闭、取消↔确认)。
结构分层
| 区域 | LESS 要点 |
|---|---|
.modal-mask |
全屏遮罩 + 居中 flex |
.modal-body |
max-height: calc(100vh - 64px) 可滚动 |
.modal-footer |
flex-between 按钮组 |
实战场景
- 删除确认、编辑弹窗;Ant Design Modal / Arco Modal 同类交互。
真实网站场景: 各类后台管理系统(若依、AntD Pro)、电商网站的商品操作确认、评论删除确认均采用此模态框模式。
7.10 标签页(Tabs)与手风琴(Accordion)
场景: 商品详情页的参数/评论/推荐切换、FAQ 帮助页的折叠问答,是内容组织的两种经典模式。
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS Tabs 与 Accordion</title>
<style type="text/less">
/* ── 变量 ── */
@tab-active-color: #1a73e8;
@tab-border: #e8e8e8;
@tab-bg-hover: #f5f5f5;
@acc-border: #e8e8e8;
@acc-header-bg: #fafafa;
@acc-active-bg: #f0f8ff;
@radius: 6px;
/* ── 混合 ── */
.transition(@prop: all, @dur: .2s, @fn: ease) {
transition: @prop @dur @fn;
}
/* ── 全局 ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: "Microsoft YaHei", sans-serif; padding: 32px; background: #f0f2f5; }
h3 { font-size: 15px; color: #333; font-weight: 500; margin: 0 0 16px; }
.section { margin-bottom: 40px; }
/* ════════════════════════════
标签页(Tabs)
════════════════════════════ */
.tabs {
background: #fff;
border-radius: @radius;
box-shadow: 0 2px 8px rgba(0,0,0,.06);
overflow: hidden;
/* ── Tab 头部导航 ── */
.tab-nav {
display: flex;
border-bottom: 2px solid @tab-border;
background: #fff;
overflow-x: auto;
&::-webkit-scrollbar { display: none; }
.tab-item {
padding: 14px 24px;
font-size: 14px;
color: #666;
cursor: pointer;
white-space: nowrap;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
.transition(color);
user-select: none;
&:hover { color: @tab-active-color; }
&.active {
color: @tab-active-color;
border-bottom-color: @tab-active-color;
font-weight: 500;
}
}
}
/* ── Tab 内容面板 ── */
.tab-panels {
.tab-panel {
display: none;
padding: 24px;
line-height: 1.8;
color: #555;
font-size: 14px;
animation: fadeIn .2s ease;
&.active { display: block; }
}
}
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── 卡片式 Tab(变体) ── */
.tabs-card {
.tabs();
.tab-nav {
background: #f5f5f5;
border-bottom: none;
padding: 8px 8px 0;
gap: 4px;
.tab-item {
border-radius: @radius @radius 0 0;
border-bottom: none;
margin-bottom: 0;
background: #e8e8e8;
&.active {
background: #fff;
color: @tab-active-color;
border-bottom-color: transparent;
}
}
}
}
/* ════════════════════════════
手风琴(Accordion)
════════════════════════════ */
.accordion {
background: #fff;
border: 1px solid @acc-border;
border-radius: @radius;
overflow: hidden;
.acc-item {
border-bottom: 1px solid @acc-border;
&:last-child { border-bottom: none; }
/* 头部(点击展开/收起) */
.acc-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: @acc-header-bg;
cursor: pointer;
font-size: 14px;
color: #333;
font-weight: 500;
.transition(background);
user-select: none;
.acc-icon {
font-size: 14px;
color: #999;
.transition(transform);
}
&:hover { background: darken(@acc-header-bg, 3%); }
}
/* 内容区 */
.acc-body {
max-height: 0;
overflow: hidden;
.transition(max-height, .3s, ease-in-out);
.acc-content {
padding: 16px 20px;
font-size: 14px;
color: #555;
line-height: 1.8;
border-top: 1px solid @acc-border;
}
}
/* 展开状态 */
&.active {
.acc-header {
background: @acc-active-bg;
color: @tab-active-color;
.acc-icon { transform: rotate(180deg); }
}
.acc-body { max-height: 500px; }
}
}
}
</style>
<script src="./less.js"></script>
</head>
<body>
<!-- 标准线条 Tab -->
<div class="section">
<h3>标准线条标签页</h3>
<div class="tabs" id="tabs1">
<div class="tab-nav">
<div class="tab-item active" onclick="switchTab('tabs1',0)">商品详情</div>
<div class="tab-item" onclick="switchTab('tabs1',1)">规格参数</div>
<div class="tab-item" onclick="switchTab('tabs1',2)">用户评价 (128)</div>
<div class="tab-item" onclick="switchTab('tabs1',3)">售后政策</div>
</div>
<div class="tab-panels">
<div class="tab-panel active">这是商品详情内容区域,展示商品的图文详细描述信息。</div>
<div class="tab-panel">这是规格参数内容,包含重量、尺寸、颜色、材质等技术参数。</div>
<div class="tab-panel">这是用户评价区域,展示购买后用户的真实使用反馈。</div>
<div class="tab-panel">7天无理由退换,30天质量问题包退,1年整机保修。</div>
</div>
</div>
</div>
<!-- 手风琴 -->
<div class="section">
<h3>手风琴折叠面板</h3>
<div class="accordion">
<div class="acc-item active">
<div class="acc-header" onclick="toggleAcc(this)">
如何使用 LESS 变量实现主题切换?
<span class="acc-icon">▼</span>
</div>
<div class="acc-body">
<div class="acc-content">
通过在根级别定义颜色变量(如 @primary-color),所有引用该变量的组件都会在重编译时自动更新颜色。Ant Design 3.x 正是通过覆盖 LESS 变量并在构建时重新编译来实现主题定制功能。
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-header" onclick="toggleAcc(this)">
LESS 混合(Mixin)和 CSS 类有什么区别?
<span class="acc-icon">▼</span>
</div>
<div class="acc-body">
<div class="acc-content">
带括号的 Mixin(如 .clearfix())不会输出为独立的 CSS 类,只在被调用处展开代码。不带括号的(如 .clearfix)既是 CSS 类也可被调用。带括号的 Mixin 更适合作为纯复用工具,避免输出无用的 CSS 规则。
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-header" onclick="toggleAcc(this)">
为什么 LESS 的除法运算必须加括号?
<span class="acc-icon">▼</span>
</div>
<div class="acc-body">
<div class="acc-content">
CSS 中斜杠 / 在 font 属性(如 font: 12px/1.5 sans-serif)中有特殊含义,表示行高分隔。为避免歧义,LESS 规定除法运算必须用括号包裹,如 (100px / 2),才会被编译器识别为数学运算。
</div>
</div>
</div>
<div class="acc-item">
<div class="acc-header" onclick="toggleAcc(this)">
LESS 中如何实现循环生成多个类?
<span class="acc-icon">▼</span>
</div>
<div class="acc-body">
<div class="acc-content">
LESS 没有原生循环语法,但可以通过带递归的混合模拟。定义一个调用自身的 Mixin,每次递减计数器,结合 Guards 条件在计数器为 0 时终止递归,从而批量生成 .col-1 ~ .col-12 等系列类。
</div>
</div>
</div>
</div>
</div>
<script>
function switchTab(tabsId, index) {
const tabs = document.getElementById(tabsId);
tabs.querySelectorAll('.tab-item').forEach((el, i) => {
el.classList.toggle('active', i === index);
});
tabs.querySelectorAll('.tab-panel').forEach((el, i) => {
el.classList.toggle('active', i === index);
});
}
function toggleAcc(header) {
const item = header.parentElement;
const isActive = item.classList.contains('active');
document.querySelectorAll('.acc-item.active').forEach(el => el.classList.remove('active'));
if (!isActive) item.classList.add('active');
}
</script>
</body>
</html>
【代码注释】
核心逻辑
- 线条 Tab:激活态靠
border-bottom-color+color绑定@tab-active-color,改变量全局换色。 - 手风琴:
max-height: 0 → 500px纯 CSS 高度过渡(足够大的上限值);.transition(@prop, @dur, @fn)统一动效参数。 .tabs-card()在基础 Tab mixin 上只覆盖圆角/边框,演示差异覆盖而非复制全文。
模式对照
| 组件 | 动画技巧 |
|---|---|
| Tab | 底边高亮 + 可选 card 皮肤 |
| Accordion | max-height 展开 |
| 共用 | .transition() mixin |
实战场景
- 商品详情多 Tab、帮助中心 FAQ;淘宝详情页、京东帮助中心典型形态。
真实网站场景: 淘宝商品详情页的"宝贝详情/规格参数/评价(xxxx)"切换、京东的帮助中心 FAQ 折叠,均是这两种模式的典型应用。
8 LESS vs SASS/SCSS 对比
渲染错误: Mermaid 渲染失败: Parse error on line 3: ...bgraph LESS A1变量:@var ----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'subgraph', 'end', 'acc_title', 'acc_descr', 'acc_descr_multiline_value', 'AMP', 'COLON', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', 'direction_tb', 'direction_bt', 'direction_rl', 'direction_lr', 'direction_td', got 'LINK_ID'
| 特性 | LESS | SASS/SCSS |
|---|---|---|
| 变量前缀 | @ |
$ |
| Mixin 定义 | .mixin() |
@mixin name |
| Mixin 调用 | .mixin() |
@include name |
| 条件语句 | when (Guards) |
@if / @else if / @else |
| 循环 | 递归 Mixin / each()(v3.7+) |
@for @each @while |
| 选择器继承 | :extend(.class)(v1.4+) |
@extend .class |
| 属性合并 | + / +_ 标记(v1.5+) |
无原生支持 |
| 属性引用 | $prop 语法(v3.0+) |
无 |
| 自定义函数 | 无(只有内置函数) | @function |
| 模块系统 | @import + reference/inline/once 等关键字 |
@use / @forward(Dart Sass) |
| 学习曲线 | 较低,CSS 友好 | 较高,需学 @mixin/@include 等 |
| Node.js 生态 | 原生支持(less 包) |
需要安装 sass / node-sass 包 |
| 典型使用方 | Bootstrap 3、Ant Design 早期 | Bootstrap 4/5、Material UI |
9 工程最佳实践与文件组织
9.1 推荐的项目文件结构
styles/
├── base/
│ ├── _reset.less ← 重置样式
│ ├── _typography.less ← 排版基础
│ └── _variables.less ← 全局变量
├── components/
│ ├── _buttons.less ← 按钮组件
│ ├── _forms.less ← 表单组件
│ ├── _cards.less ← 卡片组件
│ └── _nav.less ← 导航组件
├── layout/
│ ├── _grid.less ← 栅格系统
│ ├── _header.less ← 页头
│ └── _footer.less ← 页脚
├── pages/
│ ├── _home.less ← 首页专属样式
│ └── _product.less ← 商品页专属样式
└── main.less ← 入口:@import 所有模块
9.2 变量命名规范
less
/* 语义化命名(推荐) */
@color-primary: #1a73e8;
@color-danger: #ea4a36;
@spacing-md: 16px;
@border-radius: 4px;
/* 不推荐:值型命名 */
@blue: #1a73e8; /* 语义不明确,换色时变量名就过时了 */
@16px: 16px; /* 无意义 */
9.3 Mixin 最佳实践
less
/* 推荐:带命名空间,避免污染 */
#util {
.ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.clearfix() {
&::after { content: ""; display: block; clear: both; }
}
}
/* 调用 */
.title { #util.ellipsis(); }
.row { #util.clearfix(); }
9.4 嵌套深度控制
less
/* 不推荐:超过三层嵌套 */
.page {
.section {
.container {
.list {
.item { /* 嵌套太深 */
.text { } /* 编译为极长的选择器 */
}
}
}
}
}
/* 推荐:最多三层,超出用 BEM 拆分 */
.section { }
.section__list { }
.section__item { }
.section__item-text { }
9.5 Design Token 命名体系
来自 2026 工程实践:用语义化命名代替"值型命名",让变量经得起需求变更。
变量按"类别 → 状态 → 尺寸"三层命名,形成可预测的 Token 体系:
less
/* ====== 颜色 Token ====== */
/* 品牌色 */
@color-brand-primary: #1a73e8;
@color-brand-secondary: #ea4a36;
/* 语义色 */
@color-success: #34a853;
@color-warning: #fbbc04;
@color-danger: #ea4a36;
@color-info: #4285f4;
/* 文本色 */
@color-text-primary: #111;
@color-text-secondary: #555;
@color-text-muted: #999;
@color-text-disabled: #ccc;
/* 背景色 */
@color-bg-base: #fff;
@color-bg-secondary: #f5f5f5;
@color-bg-elevated: #fafafa;
/* ====== 间距 Token(8px 基准网格) ====== */
@space-1: 4px; /* 极紧凑 */
@space-2: 8px; /* 紧凑 */
@space-3: 12px; /* 默认 */
@space-4: 16px; /* 宽松 */
@space-6: 24px; /* 大间距 */
@space-8: 32px; /* 区域分隔 */
@space-12: 48px; /* 模块分隔 */
/* ====== 圆角 Token ====== */
@radius-sm: 4px;
@radius-md: 8px;
@radius-lg: 12px;
@radius-full: 9999px;
/* ====== 字体 Token ====== */
@font-size-xs: 12px;
@font-size-sm: 13px;
@font-size-base: 14px;
@font-size-md: 16px;
@font-size-lg: 20px;
@font-size-xl: 24px;
@font-size-2xl: 32px;
/* ====== Z-index 层级 Token ====== */
@z-dropdown: 100;
@z-sticky: 200;
@z-modal: 300;
@z-toast: 400;
@z-tooltip: 500;
/* ====== 阴影 Token ====== */
@shadow-sm: 0 1px 3px rgba(0,0,0,.08);
@shadow-md: 0 4px 12px rgba(0,0,0,.12);
@shadow-lg: 0 8px 24px rgba(0,0,0,.16);
@shadow-xl: 0 16px 48px rgba(0,0,0,.20);
命名原则:
@color-text-primary而非@black------语义稳定,调色不改名@space-4对应4 × 4px = 16px,数字即倍数,规律可记@z-modal而非@z-300------意图清晰,排查层叠问题无需查表
9.6 LESS 与 CSS 自定义属性混合策略
CSS 自定义属性(CSS Variables)在运行时 可被 JS 动态修改,而 LESS 变量在编译时被解析为字面量。两者有明确的职责分工,混合使用可兼得二者优势:
less
/* LESS Token(编译时定值)------做设计系统的"单一真相源" */
@brand-blue: #1a73e8;
@brand-green: #34a853;
@space-4: 16px;
@radius-md: 8px;
/* 将 LESS Token 写入 CSS Variables(供运行时使用) */
:root {
--color-primary: @brand-blue;
--color-success: @brand-green;
--space-4: @space-4;
--radius-md: @radius-md;
/* 暗色主题下通过 JS 覆盖这些变量即可实现主题切换 */
}
/* 组件层:使用 CSS Variables 实现"主题可切换" */
.button {
background: var(--color-primary);
border-radius: var(--radius-md);
padding: calc(var(--space-4) * 0.5) var(--space-4);
color: #fff;
}
/* 以下代码在 JS 中执行,即可切换到暗色主题------无需重编译 */
/* document.documentElement.style.setProperty('--color-primary', '#0d47a1'); */
分层策略总结:
| 层级 | 工具 | 特点 | 典型用途 |
|---|---|---|---|
| 设计 Token | LESS @var |
编译期常量 | 颜色、间距、字体的唯一来源 |
| 主题变量 | CSS --var |
运行时可修改 | 暗色模式、用户个性化主题 |
| 组件样式 | var(--var) |
消费 CSS 变量 | 按钮、卡片等具体组件 |
| 动态覆盖 | JS setProperty |
实时生效 | 主题切换、动态配色 |
完整可运行示例(暗色主题切换):
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>LESS + CSS Variables 主题切换</title>
<style type="text/less">
/* LESS Token → CSS Variable */
@light-bg: #ffffff;
@light-text: #111111;
@dark-bg: #1c1c1e;
@dark-text: #ebebeb;
@brand: #1a73e8;
@radius: 8px;
:root {
--bg: @light-bg;
--text: @light-text;
--brand: @brand;
--radius: @radius;
}
/* 暗色主题:通过 data-theme 属性切换 */
[data-theme="dark"] {
--bg: @dark-bg;
--text: @dark-text;
}
body {
background: var(--bg);
color: var(--text);
font-family: sans-serif;
padding: 32px;
transition: background .3s, color .3s;
}
.card {
background: var(--bg);
color: var(--text);
border: 1px solid rgba(128,128,128,.2);
border-radius: var(--radius);
padding: 20px;
max-width: 360px;
box-shadow: 0 2px 8px rgba(0,0,0,.08);
}
.btn-theme {
background: var(--brand);
color: #fff;
border: none;
border-radius: var(--radius);
padding: 8px 18px;
cursor: pointer;
margin-top: 12px;
}
</style>
<script src="./less.js"></script>
</head>
<body>
<div class="card">
<h3>LESS + CSS Variables 主题切换</h3>
<p>LESS Token 提供设计基准,CSS Variable 在运行时动态切换</p>
<button class="btn-theme" onclick="toggleTheme()">切换暗色 / 亮色</button>
</div>
<script>
function toggleTheme() {
const root = document.documentElement;
root.dataset.theme = root.dataset.theme === 'dark' ? '' : 'dark';
}
</script>
</body>
</html>
【代码注释】
核心逻辑
- LESS 变量在编译期 算出
@light-bg等,写入:root { --bg: ... }与[data-theme="dark"] { --bg: ... }。 - 业务样式用
var(--bg);运行时document.documentElement.dataset.theme = 'dark'即可换肤,无需重新 lessc。
双层 Token
| 层 | 时机 | 示例 |
|---|---|---|
@primary (LESS) |
构建 | 生成 --primary |
var(--primary) (CSS) |
运行 | 组件背景色 |
注意点
- IE 不支持 CSS Variables;仅需现代浏览器时可采用此混合策略(§5.4)。
transition: background .3s避免主题切换闪烁。
实战场景
- 暗色模式、跟随系统
prefers-color-scheme;与 §7.1 编译期主题互补。
9.7 Vite 项目集成 LESS
Vite 原生支持 LESS,无需额外安装 less-loader,只需安装 less 包本身:
bash
# 安装依赖(Vite 本身已集成解析能力)
npm install -D less
vite.config.js 配置:
javascript
import { defineConfig } from 'vite';
export default defineConfig({
css: {
preprocessorOptions: {
less: {
// 全局注入变量/混合文件(无需在每个 .less 文件中手动 @import)
additionalData: `@import "@/styles/variables.less";
@import "@/styles/mixins.less";`,
// 开启 Source Map(开发环境调试用)
sourceMap: true,
// LESS 全局配置项
globalVars: {
'brand-color': '#1a73e8',
},
},
},
// 生成模块化 CSS(防止类名冲突)
modules: {
scopeBehaviour: 'local',
},
},
});
在 Vue 单文件组件(SFC)中使用:
vue
<template>
<div class="card">
<h2 class="title">Vite + LESS + Vue</h2>
<p class="desc">变量与 Mixin 由 vite.config 全局注入</p>
</div>
</template>
<style lang="less">
/* 无需手动 @import variables.less,vite.config 已全局注入 */
.card {
background: @color-bg-base;
border-radius: @radius-md;
padding: @space-4;
.title {
color: @color-brand-primary;
font-size: @font-size-lg;
}
.desc {
color: @color-text-secondary;
font-size: @font-size-base;
}
}
</style>
package.json 构建脚本(可选):
json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"styles:watch": "lessc src/styles/main.less dist/styles.css --watch"
}
}
【代码注释】
核心逻辑
- Vite
css.preprocessorOptions.less.additionalData:每个.less文件编译前自动注入@import "@/styles/variables.less",全局变量/mixin 免重复 import。 globalVarsvsmodifyVars:前者优先级高、不易被文件内覆盖;后者追加在末尾,适合 Ant Design 主题覆盖。
配置要点
| 选项 | 用途 |
|---|---|
additionalData |
全局 variables/mixins |
modifyVars |
覆盖 UI 库 @primary-color |
javascriptEnabled: true |
兼容部分旧库内联 JS |
注意点
- LESS 与 CSS Modules 可并存:LESS 管 Token/混合,Modules 管类名作用域。
- 路径别名
@需在resolve.alias与additionalData中一致。
实战场景
- Vue3 + Vite + Element/Ant Design 自定义主题;Monorepo 共享
packages/styles/variables.less。
10 常见问题与避坑指南
10.1 除法必须加括号
less
/* ❌ 错误:/ 被解释为 CSS font 属性的分隔符 */
height: 100px / 2;
/* ✓ 正确:括号强制 LESS 进行数学运算 */
height: (100px / 2);
10.2 媒体查询变量转义
less
/* ❌ 错误:冒号在 LESS 中是变量赋值符,语法歧义 */
@query: min-width: 768px;
/* ✓ 正确:使用转义字符串 */
@query: ~"min-width: 768px";
/* 使用 */
@media (@query) { .container { width: 750px; } }
10.3 Mixin 有无括号的区别
less
/* 带括号:只能作为 mixin,不输出为 CSS 类 */
.hidden-mixin() { display: none; }
/* 不带括号:既是 CSS 类,也可以被 mixin 调用(会输出到 CSS 中) */
.visible-class { display: block; }
/* 调用 */
.item {
.hidden-mixin(); /* 安全,.hidden-mixin 本身不出现在 CSS 中 */
.visible-class(); /* .visible-class 的规则会出现在 CSS 中 */
}
10.4 颜色函数参数是百分比非比例
less
/* ❌ 错误:参数不是 0-1 的比例 */
background: darken(#900, 0.1); /* 几乎没有变化 */
/* ✓ 正确:参数是百分比 */
background: darken(#900, 10%); /* 亮度降低 10% */
10.5 变量懒加载导致的"先用后定义"
less
/* LESS 允许这样写------变量会在整个作用域内查找 */
.box {
color: @color; /* 虽然在下面定义,但 LESS 懒加载机制允许 */
}
@color: #900; /* 在使用之后定义 */
10.6 :extend() 无法跨媒体查询生效
:extend() 的作用域被严格限制在同一 @media 块内,不能跨媒体查询进行选择器合并:
less
@media screen {
.base { color: blue; }
/* ✓ 正确:.inner 与 .base 在同一 @media 块内,extend 有效 */
.inner { &:extend(.base); }
}
/* ❌ 错误:.outside 在 @media 外部,无法扩展 @media 内的 .base */
.outside { &:extend(.base); }
/* 编译后 .outside 不会继承 .base 的样式 */
解决方案: 将公共基础样式提取到媒体查询外部,或改用 Mixin:
less
/* ✓ 将基础样式提取到全局层,再在 @media 内覆盖 */
.base { color: blue; }
@media screen {
.inner { &:extend(.base); } /* 继承全局 .base ✓ */
.base { font-size: 16px; } /* 媒体内额外扩展 */
}
10.7 属性合并(+)未加标记导致属性被覆盖
使用 merge 特性时,所有参与合并的声明 都必须携带 + 或 +_ 标记,否则后者直接覆盖前者:
less
.shadow-a() { box-shadow: inset 0 1px 3px rgba(0,0,0,.2); } /* ❌ 没有 + */
.shadow-b() { box-shadow: 0 4px 12px rgba(0,0,0,.15); } /* ❌ 没有 + */
.card {
.shadow-a();
.shadow-b(); /* ❌ 结果:只有 shadow-b,shadow-a 被覆盖 */
}
less
/* ✓ 正确:所有参与合并的属性都加 + */
.shadow-a() { box-shadow+: inset 0 1px 3px rgba(0,0,0,.2); }
.shadow-b() { box-shadow+: 0 4px 12px rgba(0,0,0,.15); }
.card {
.shadow-a();
.shadow-b(); /* ✓ 结果:两个 shadow 以逗号合并 */
}
10.8 @import (reference) 导入的样式不直接输出
@import (reference) 是 LESS 的高级导入模式,用于从第三方库(如 Bootstrap)中"按需提取"样式,但其行为经常被误解:
less
/* ❌ 常见误解:以为导入后样式自动生效 */
@import (reference) "bootstrap/less/bootstrap.less";
/* 此时 Bootstrap 的所有样式都不会出现在输出 CSS 中! */
/* 必须通过 extend 或 mixin 主动"激活"才会输出 */
/* ✓ 正确:通过 :extend 按需引入 Bootstrap 的 .navbar 样式 */
.my-nav {
&:extend(.navbar all);
}
适用场景: 大型框架(Bootstrap/Semantic UI)引入时,只需其中少数组件的样式,可通过 (reference) + :extend 仅提取需要的部分,大幅减小最终 CSS 体积。
总结 {#总结}
LESS 通过变量 、混合 、嵌套 、运算 、函数 和条件 六大核心特性,将编程范式引入 CSS,从根本上解决了原生 CSS 可维护性差、复用性低的问题。在此基础上,:extend()、属性合并(+/+_)、$prop 引用、递归循环等高级特性,进一步提升了 LESS 在大型工程中的表达力。
渲染错误: Mermaid 渲染失败: Parse error on line 8: ... A --> K:extend():选择器继承,减少 CSS 体积 ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
核心特性速查手册:
| 特性 | 语法 | 核心价值 | 适用版本 |
|---|---|---|---|
| 变量 | @name: value |
单一真相源,全局一处改 | all |
| 混合 | .mixin(@arg: default) |
样式模板化复用 | all |
| 嵌套 | { & } |
结构清晰,减少选择器重复 | all |
| 运算 | (100px / 2) |
动态计算尺寸 | all |
| 条件 Guards | when (@n > 0) |
分支逻辑 | all |
| 导入 | @import (reference/inline) |
模块化 + 按需提取 | v1.4+ |
| 选择器继承 | &:extend(.class all) |
共享规则,减少 CSS 体积 | v1.4+ |
| 属性合并 | + / +_ |
多值叠加(阴影、变换) | v1.5+ |
| 属性引用 | $prop |
属性值就地复用 | v3.0+ |
| 列表迭代 | each(@list, {...}) |
批量生成工具类 | v3.7+ |
| 变量变量 | @@name |
动态变量选择 | all |
在工程实践中,LESS 的最大价值在于与团队约定结合:
- Design Token 命名体系 :语义化命名(
@color-text-primary而非@black),Token 不随颜色值变而失效 - 三层架构 :
variables.less→mixins.less→components.less,通过@import链组装 - 嵌套深度控制:严格不超过三层,超出部分用 BEM 命名拆分
- LESS + CSS Variables 分层:LESS Token 作编译期快照,CSS Variable 作运行时主题切换
- Vite/Webpack 全局注入 :通过
additionalData全局注入变量和 Mixin,组件无需手动@import
配合现代构建工具(Webpack/Vite),LESS 能够构建出高度可维护的大型样式代码库------无论是 Bootstrap 3 的 6000 行组件库,还是 Ant Design 的完整设计系统,其背后都遵循着相同的工程哲学:用编程范式管理 CSS,让每一个设计决策只出现一次。
参考文档与延伸阅读:
- LESS 官方文档:https://lesscss.org/features/
- LESS 中文文档:https://less.bootcss.com
- LESS 函数参考:https://lesscss.org/functions/
- LESS GitHub 源码:https://github.com/less/less.js
- Bootstrap 3 LESS 源码:https://github.com/twbs/bootstrap/tree/v3-dev/less
- Ant Design LESS Token 规范:https://ant.design/docs/react/customize-theme-cn
- LESS in 2026 工程实践:https://thelinuxcode.com/less-in-2026-a-practical-scalable-way-to-keep-css-sane/