Less 常用语法
大白话解释: Less 和 Sass 一样,都是"增强版 CSS"。给 CSS 加了变量、嵌套、混合等功能,让样式代码更易维护。
Less vs Sass 怎么选?
- Less:学习曲线低,语法更接近 CSS,老项目常见
- Sass:功能更强大,社区生态更好,新项目推荐
什么时候用 Less?
- 老项目维护(历史技术栈使用 Less)
- 团队成员不熟悉 Sass
- 项目不太复杂,只需要变量和嵌套
Less 是 CSS 预处理器,扩展了 CSS 语言,增加了变量、混合、函数等功能。
安装
bash
# 全局安装(用于命令行编译)
npm install -g less
# 项目安装(推荐)
npm install -D less
# 编译单个文件
lessc styles.less styles.css
# 压缩编译(需先安装 less-plugin-clean-css)
npm install -g less-plugin-clean-css
lessc --clean-css styles.less styles.min.css
# 监听变化(需安装 less-watch-compiler)
npm install -g less-watch-compiler
less-watch-compiler src dist style.less变量
基本用法
less
// 定义变量:用 @ 符号声明,编译时会被替换为实际值
@primary: #1890ff; // 主色:按钮、链接等核心颜色
@success: #52c41a; // 成功色:成功状态提示
@danger: #ff4d4f; // 危险色:错误、删除操作
@warning: #faad14; // 警告色:警告状态提示
// 间距:统一管理组件内外边距,方便全局调整
@spacing-xs: 4px;
@spacing-sm: 8px;
@spacing-md: 16px;
@spacing-lg: 24px;
@spacing-xl: 32px;
// 字体:集中定义字号,保持排版一致性
@font-size-sm: 12px;
@font-size-base: 14px;
@font-size-lg: 16px;
@font-size-xl: 20px;
@font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto;
// 圆角:按钮、卡片等组件的圆角统一管理
@border-radius-sm: 2px;
@border-radius-base: 4px;
@border-radius-lg: 8px;
@border-radius-round: 50%;
// 阴影:不同层级的投影效果
@box-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.09);
@box-shadow-base: 0 4px 16px rgba(0, 0, 0, 0.12);
@box-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.15);
// z-index 层级:统一管理层叠顺序,避免 z-index 混乱
@z-index-dropdown: 1000;
@z-index-modal: 2000;
@z-index-tooltip: 3000;
// 使用变量:直接用 @变量名 引用
.button {
background: @primary;
padding: @spacing-sm @spacing-md;
font-size: @font-size-base;
border-radius: @border-radius-base;
box-shadow: @box-shadow-sm;
}变量计算
less
@base: 16px;
@double: @base * 2; // 32px
@half: (@base / 2); // 8px(Less 4+ 中 / 需要括号才计算)
@percentage: (100% / 3); // 33.33333%
.container {
width: 100% - 20%; // 80%
padding: @half;
font-size: @base + 2px; // 18px
}css
/* 编译后:变量计算在编译期完成。注意:Less 4+ 中 / 需要括号才作为除法运算 */
.container {
width: 80%;
padding: 8px;
font-size: 18px;
}变量作用域
less
@color: red;
.header {
@color: blue; // 局部变量
color: @color; // blue
}
.footer {
color: @color; // red(使用全局变量)
}
// 延迟加载(Less 特性)
.lazy {
color: @var;
}
@var: red; // 变量定义在使用之后也可以css
/* 编译后:变量被替换为实际值,局部变量只在对应选择器内生效 */
.header {
color: blue;
}
.footer {
color: red;
}
.lazy {
color: red; /* 延迟加载:变量定义在使用之后也能正常解析 */
}嵌套
基本嵌套
less
.nav {
background: #fff; // 导航栏背景色
height: 60px; // 导航栏高度
.nav-item {
display: inline-block; // 水平排列
padding: 0 16px; // 左右内边距
line-height: 60px; // 行高 = 容器高度,实现垂直居中
&:hover { // & 代表父选择器 .nav-item,编译后为 .nav-item:hover
background: #f5f5f5;
}
&.active { // 编译后为 .nav-item.active
color: @primary;
border-bottom: 2px solid @primary; // 底部指示条
}
}
.nav-link {
color: #333;
text-decoration: none; // 去掉下划线
&:hover {
color: @primary;
}
&::after { // 伪元素:hover 时从下方滑出的动画线
content: '';
display: block;
width: 100%;
height: 2px;
background: @primary;
transform: scaleX(0); // 初始状态:水平缩放为 0(不可见)
transition: transform 0.3s; // 过渡动画
}
&:hover::after {
transform: scaleX(1); // hover 时:水平缩放为 1(完全展开)
}
}
}css
/* 编译后:嵌套规则被展开为后代选择器,& 替换为父选择器 */
.nav {
background: #fff;
height: 60px;
}
.nav .nav-item {
display: inline-block;
padding: 0 16px;
line-height: 60px;
}
.nav .nav-item:hover {
background: #f5f5f5;
}
.nav .nav-item.active {
color: #1890ff;
border-bottom: 2px solid #1890ff;
}
.nav .nav-link {
color: #333;
text-decoration: none;
}
.nav .nav-link:hover {
color: #1890ff;
}
.nav .nav-link::after {
content: '';
display: block;
width: 100%;
height: 2px;
background: #1890ff;
transform: scaleX(0);
transition: transform 0.3s;
}
.nav .nav-link:hover::after {
transform: scaleX(1);
}媒体查询嵌套
less
.container {
width: 100%;
padding: 0 @spacing-md;
@media (min-width: 768px) {
max-width: 720px;
margin: 0 auto;
}
@media (min-width: 1024px) {
max-width: 960px;
}
@media (min-width: 1280px) {
max-width: 1200px;
}
}css
/* 编译后:媒体查询被提升到外层,不会嵌套 */
.container {
width: 100%;
padding: 0 16px;
}
@media (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
}
}
@media (min-width: 1024px) {
.container {
max-width: 960px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1200px;
}
}属性嵌套
less
.box {
border: {
top: 1px solid #ddd;
right: 1px solid #ddd;
bottom: 1px solid #ddd;
left: 1px solid #ddd;
radius: 4px;
}
margin: {
top: 16px;
bottom: 16px;
}
background: {
color: #fff;
image: url('bg.png');
repeat: no-repeat;
position: center;
}
}css
/* 编译后:属性嵌套展开为完整的属性名 */
.box {
border-top: 1px solid #ddd;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
border-left: 1px solid #ddd;
border-radius: 4px;
margin-top: 16px;
margin-bottom: 16px;
background-color: #fff;
background-image: url('bg.png');
background-repeat: no-repeat;
background-position: center;
}Mixin(混合)
基本 Mixin
less
// 定义 mixin
.bordered {
border: 1px solid #ddd;
border-radius: 4px;
}
.box-shadow {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// 使用 mixin
.card {
.bordered();
.box-shadow();
padding: 16px;
.title {
.text-ellipsis();
font-size: 16px;
}
}css
/* 编译后:不带括号的 mixin 定义本身也会输出为普通类,使用处展开 mixin 样式 */
.bordered {
border: 1px solid #ddd;
border-radius: 4px;
}
.box-shadow {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card {
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
padding: 16px;
}
.card .title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 16px;
}带参数的 Mixin
less
// 带参数
.size(@width, @height) {
width: @width;
height: @height;
}
.square(@size) {
.size(@size, @size);
}
.rectangle(@width, @height: @width * 0.618) {
.size(@width, @height);
}
// 使用
.avatar {
.square(48px);
}
.banner {
.rectangle(100%);
}css
/* 编译后:参数被替换为实际传入的值 */
.avatar {
width: 48px;
height: 48px;
}
.banner {
width: 100%;
height: 61.8%; /* 默认值:@width * 0.618 */
}默认参数
less
.padding(@vertical: 8px, @horizontal: 16px) {
padding: @vertical @horizontal;
}
.button-small {
.padding(4px, 8px);
}
.button-default {
.padding(); // 使用默认值 8px 16px
}
.button-large {
.padding(12px, 24px);
}css
/* 编译后:未传参时使用默认值 */
.button-small {
padding: 4px 8px;
}
.button-default {
padding: 8px 16px; /* 使用默认值 */
}
.button-large {
padding: 12px 24px;
}可变参数
less
.box-shadow(@shadows...) {
box-shadow: @shadows;
}
.card {
.box-shadow(0 2px 4px rgba(0,0,0,0.1));
}
.card-hover {
.box-shadow(0 2px 4px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.15));
}css
/* 编译后:@shadows... 可变参数原样展开 */
.card {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 12px rgba(0, 0, 0, 0.15);
}匹配模式
less
// 三角形
.triangle(@direction, @size, @color) {
width: 0;
height: 0;
& when (@direction = top) {
border-left: @size solid transparent;
border-right: @size solid transparent;
border-bottom: @size solid @color;
}
& when (@direction = bottom) {
border-left: @size solid transparent;
border-right: @size solid transparent;
border-top: @size solid @color;
}
& when (@direction = left) {
border-top: @size solid transparent;
border-bottom: @size solid transparent;
border-right: @size solid @color;
}
& when (@direction = right) {
border-top: @size solid transparent;
border-bottom: @size solid transparent;
border-left: @size solid @color;
}
}
.arrow-top {
.triangle(top, 10px, #333);
}
.arrow-bottom {
.triangle(bottom, 10px, #333);
}css
/* 编译后:根据 @direction 参数只保留匹配的分支 */
.arrow-top {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 10px solid #333;
}
.arrow-bottom {
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #333;
}条件判断
less
// when 条件
.generate-padding(@size) when (@size = sm) {
padding: 4px 8px;
}
.generate-padding(@size) when (@size = md) {
padding: 8px 16px;
}
.generate-padding(@size) when (@size = lg) {
padding: 12px 24px;
}
.btn-sm { .generate-padding(sm); }
.btn-md { .generate-padding(md); }
.btn-lg { .generate-padding(lg); }
// 类型检查
.check(@value) when (isnumber(@value)) {
width: @value;
}
.check(@value) when (iscolor(@value)) {
color: @value;
}css
/* 编译后:when 条件判断只输出匹配的分支 */
.btn-sm {
padding: 4px 8px;
}
.btn-md {
padding: 8px 16px;
}
.btn-lg {
padding: 12px 24px;
}命名空间
less
// 定义命名空间
#theme {
.primary() {
color: @primary;
background: fade(@primary, 10%);
}
.success() {
color: @success;
background: fade(@success, 10%);
}
.danger() {
color: @danger;
background: fade(@danger, 10%);
}
}
#utils {
.flex-center() {
display: flex;
justify-content: center;
align-items: center;
}
.flex-between() {
display: flex;
justify-content: space-between;
align-items: center;
}
.ellipsis(@lines: 1) when (@lines = 1) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ellipsis(@lines) when (@lines > 1) {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: @lines;
overflow: hidden;
}
}
// 使用命名空间
.alert-primary {
#theme > .primary();
}
.center-box {
#utils > .flex-center();
}
.title {
#utils > .ellipsis(2);
}css
/* 编译后:命名空间只是组织方式,使用时展开为普通 CSS */
.alert-primary {
color: #1890ff;
background: rgba(24, 144, 255, 0.1);
}
.center-box {
display: flex;
justify-content: center;
align-items: center;
}
.title {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}函数
颜色函数
less
@base: #1890ff;
// 亮度调整
.lighter { color: lighten(@base, 20%); }
.darker { color: darken(@base, 20%); }
// 饱和度调整
.saturated { color: saturate(@base, 20%); }
.desaturated { color: desaturate(@base, 20%); }
// 色相调整
.hue-rotate { color: spin(@base, 60); }
// 混合颜色
.mixed { color: mix(@base, #ff0000, 50%); }
// 透明度
.fade { color: fade(@base, 50%); }
.fade-in { color: fadein(@base, 20%); }
.fade-out { color: fadeout(@base, 20%); }
// 灰度
.grayscale { color: greyscale(@base); }
// 对比色
.contrast { color: contrast(@base, #000, #fff); }css
/* 编译后:颜色函数在编译期计算为具体颜色值 */
.lighter { color: #7ec1ff; } /* lighten 20% */
.darker { color: #005cb1; } /* darken 20% */
.saturated { color: #1890ff; } /* saturate 20%(已饱和,无变化) */
.desaturated { color: #2f8fe8; } /* desaturate 20% */
.hue-rotate { color: #8718ff; } /* spin 60° */
.mixed { color: #8c4880; } /* mix 50% */
.fade { color: rgba(24, 144, 255, 0.5); }
.fade-in { color: #1890ff; } /* fadein 20% */
.fade-out { color: rgba(24, 144, 255, 0.8); }
.grayscale { color: #8c8c8c; }
.contrast { color: #fff; } /* 对比色 */数学函数
less
.ceil { width: ceil(1.2px); } // 2px
.floor { width: floor(1.8px); } // 1px
.round { width: round(1.5px); } // 2px
.abs { width: abs(-10px); } // 10px
.max { width: max(10px, 20px); } // 20px
.min { width: min(10px, 20px); } // 10px
.percentage { width: percentage(0.5); } // 50%
.sqrt { width: sqrt(9px); } // 3px字符串函数
less
@str: "hello";
.replace { content: replace(@str, "hello", "world"); }
.escape { content: escape(@str); }
.len { content: length(red, green, blue); } // 3(返回列表元素个数)自定义函数
less
// 自定义函数
.calc-rem(@px, @base: 16px) {
@result: (@px / @base) * 1rem;
}
.title {
.calc-rem(24px);
font-size: @result; // 1.5rem
}
// 列表操作
@colors: red, green, blue;
each(@colors, {
.text-@{index} {
color: @value;
}
});css
/* 编译后:自定义函数结果在编译期计算 */
.title {
font-size: 1.5rem;
}
/* each 循环展开 */
.text-1 {
color: red;
}
.text-2 {
color: green;
}
.text-3 {
color: blue;
}循环
递归循环
less
// 生成栅格(使用 Flexbox 替代 float,更现代)
.generate-columns(@n, @i: 1) when (@i =< @n) {
.col-@{i} {
width: (@i / @n) * 100%; // 计算每列宽度百分比
box-sizing: border-box; // 包含 padding 和 border
padding: 0 15px; // 列内边距
}
.generate-columns(@n, (@i + 1)); // 递归调用,i 递增
}
.generate-columns(12); // 调用:生成 .col-1 到 .col-12
// 生成间距工具类
.generate-spacing(@n, @i: 0) when (@i =< @n) {
.mt-@{i} { margin-top: (@i * 8px); } // .mt-0, .mt-1 ... .mt-5
.mb-@{i} { margin-bottom: (@i * 8px); }
.ml-@{i} { margin-left: (@i * 8px); }
.mr-@{i} { margin-right: (@i * 8px); }
.pt-@{i} { padding-top: (@i * 8px); }
.pb-@{i} { padding-bottom: (@i * 8px); }
.pl-@{i} { padding-left: (@i * 8px); }
.pr-@{i} { padding-right: (@i * 8px); }
.generate-spacing(@n, (@i + 1));
}
.generate-spacing(5); // 调用:生成 mt-0 到 mt-5 等css
/* 编译后:循环展开为所有生成的类(这里展示部分) */
.col-1 {
width: 8.33333333%;
box-sizing: border-box;
padding: 0 15px;
}
.col-2 {
width: 16.66666667%;
box-sizing: border-box;
padding: 0 15px;
}
/* ... col-3 到 col-11 省略 ... */
.col-12 {
width: 100%;
box-sizing: border-box;
padding: 0 15px;
}
.mt-1 { margin-top: 8px; }
.mb-1 { margin-bottom: 8px; }
/* ... mt-2 到 mt-4 省略 ... */
.mt-5 { margin-top: 40px; }
.mb-5 { margin-bottom: 40px; }each 循环
less
// 遍历数组
@breakpoints: {
sm: 576px;
md: 768px;
lg: 1024px;
xl: 1280px;
}
each(@breakpoints, {
.container-@{key} {
max-width: @value;
margin: 0 auto;
}
});
// 遍历颜色
@colors: {
primary: #1890ff;
success: #52c41a;
danger: #ff4d4f;
warning: #faad14;
}
each(@colors, {
.btn-@{key} {
background: @value;
color: #fff;
&:hover {
background: lighten(@value, 10%);
}
}
.text-@{key} {
color: @value;
}
.bg-@{key} {
background-color: fade(@value, 10%);
border-color: fade(@value, 30%);
}
});css
/* 编译后:each 循环遍历 map 生成所有类(这里展示 primary 部分) */
.container-sm {
max-width: 576px;
margin: 0 auto;
}
.container-md {
max-width: 768px;
margin: 0 auto;
}
/* ... 其他断点省略 ... */
.btn-primary {
background: #1890ff;
color: #fff;
}
.btn-primary:hover {
background: #40a9ff; /* lighten(#1890ff, 10%) */
}
.text-primary {
color: #1890ff;
}
.bg-primary {
background-color: rgba(24, 144, 255, 0.1);
border-color: rgba(24, 144, 255, 0.3);
}
/* success、danger、warning 同理省略 */导入
文件组织
src/
styles/
_variables.less // 变量
_mixins.less // mixin
_reset.less // 重置样式
_utilities.less // 工具类
components/
_button.less
_card.less
_form.less
themes/
_light.less
_dark.less
main.less // 主入口导入语法
less
// main.less
@import '_variables';
@import '_mixins';
@import '_reset';
@import '_utilities';
// 导入组件
@import 'components/_button';
@import 'components/_card';
@import 'components/_form';
// 导入主题
@import 'themes/_light';
// 导入选项
@import (reference) '_mixins'; // 只引用,不输出
@import (once) '_variables'; // 只导入一次(默认)
@import (multiple) '_mixins'; // 允许重复导入
@import (inline) '_legacy.css'; // 只包含,不处理
@import (less) '_style.css'; // 当作 less 处理
@import (css) '_style.css'; // 当作 css 输出 @import实际应用场景
主题切换
less
// 定义主题变量(使用 CSS 变量 + Less 变量结合)
@light-bg: #fff;
@light-text: #333;
@light-border: #ddd;
@light-primary: #1890ff;
@dark-bg: #141414;
@dark-text: #fff;
@dark-border: #444;
@dark-primary: #177ddc;
// 生成主题类
[data-theme="light"] {
--bg: @light-bg;
--text: @light-text;
--border: @light-border;
--primary: @light-primary;
}
[data-theme="dark"] {
--bg: @dark-bg;
--text: @dark-text;
--border: @dark-border;
--primary: @dark-primary;
}
// 使用 CSS 变量
body {
background: var(--bg);
color: var(--text);
}
.card {
border: 1px solid var(--border);
background: var(--bg);
}响应式工具类
less
// 断点定义
@breakpoints: {
sm: 576px;
md: 768px;
lg: 1024px;
xl: 1280px;
xxl: 1600px;
}
// 生成显示/隐藏类
each(@breakpoints, {
@media (min-width: @value) {
.d-@{key}-none { display: none !important; }
.d-@{key}-block { display: block !important; }
.d-@{key}-flex { display: flex !important; }
.d-@{key}-inline { display: inline !important; }
.d-@{key}-inline-block { display: inline-block !important; }
}
});
// 生成列宽类
each(range(12), {
@media (min-width: 768px) {
.col-md-@{value} {
width: (@value / 12) * 100%;
box-sizing: border-box;
}
}
});Vue 中使用 Less
基础配置
vue
<template>
<div class="container">
<button class="btn btn-primary">按钮</button>
<div class="card">
<h3 class="card__title">标题</h3>
<p class="card__desc">描述</p>
</div>
</div>
</template>
<script setup>
// scoped 模式下不需要额外导入样式,class 直接使用即可
</script>
<style lang="less" scoped>
@import '@/styles/variables.less';
.container {
padding: @spacing-lg;
}
.btn {
padding: @spacing-sm @spacing-md;
border: none;
border-radius: @border-radius-base;
cursor: pointer;
&-primary {
background: @primary;
color: #fff;
&:hover {
background: lighten(@primary, 10%);
}
}
&-danger {
background: @danger;
color: #fff;
}
}
.card {
border: 1px solid #eee;
border-radius: @border-radius-lg;
padding: @spacing-lg;
&__title {
font-size: @font-size-lg;
margin-bottom: @spacing-sm;
}
&__desc {
color: #666;
font-size: @font-size-base;
}
}
</style>CSS Modules 方式
vue
<template>
<div :class="styles.container">
<button :class="[styles.btn, styles.btnPrimary]">按钮</button>
</div>
</template>
<script setup>
import styles from './index.module.less'
</script>
<style lang="less" module>
@primary: #1890ff;
.container {
padding: 24px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
}
.btnPrimary {
background: @primary;
color: #fff;
}
</style>全局变量方案
js
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
less: {
// 全局注入变量(不需要每个文件 @import)
additionalData: `@import "@/styles/variables.less";`
}
}
}
})常见问题
1. 变量名冲突
less
// ❌ 避免使用过于通用的变量名
@color: red;
@size: 10px;
// ✅ 使用有意义的前缀
@btn-color: red;
@icon-size: 10px;2. mixin 输出多余代码
less
// ❌ 普通 mixin 会输出到 CSS
.bordered {
border: 1px solid #ddd;
}
// ✅ 使用 () 使其成为函数,不输出
.bordered() {
border: 1px solid #ddd;
}css
/* 编译后对比:
普通 mixin(无括号)会同时输出定义和使用处:
.bordered {
border: 1px solid #ddd;
}
.card {
border: 1px solid #ddd; // 重复
}
函数式 mixin(有括号)只输出使用处:
.card {
border: 1px solid #ddd;
}
*/3. 导入路径问题
less
// ❌ 相对路径容易出错
@import '../../../styles/variables.less';
// ✅ 使用别名(需配置 vite.config.ts)
@import '@/styles/variables.less';4. 浏览器兼容
less
// Less 本身不处理浏览器兼容
// 需要配合 PostCSS 的 autoprefixer 使用Less vs Sass 对比
| 特性 | Less | Sass/SCSS |
|---|---|---|
| 变量符号 | @var | $var |
| 嵌套 | ✅ | ✅ |
| Mixin | .mixin() | @mixin + @include |
| 继承 | :extend() | @extend |
| 函数 | 有限 | 丰富 |
| 控制指令 | when + each | @if、@for、@each、@while |
| 学习曲线 | 较低 | 较高 |
| 社区生态 | 较小 | 更大 |
| 推荐场景 | 简单项目 | 复杂项目 |