less深入指南
pid: 100605548
前言
现今前端流行的css预编译器无非less、scss。无论这两种编译器如何,相信大部分人使用它们只为了嵌套功能,而没有去了解其他实用功能。这篇文章主要结合自己的使用情况,聊聊less的强大功能!
下面以vue为例,提出几个问题:
正文
变量
在书写样式时,可能会出现同一个属性使用多次的情况,这时候变量就不可或缺了, 因为大量重复代码必定会带来维护上的不便。
变量基本使用
less中变量均以@艾特符开头, 你可以像定义普通css属性那样定义less变量。 有一个变量的特例情况,变量会以$金额符开头。
@color: red;
@num: 20;
@unit:px;
a {
@color: green;
color: @color; //直接复制
margin: ~"@{num}@{unit}"; //类似于模板字符串
}
link {
color: @color;
width: 200px;
height:$width; //特例用法
}
//翻译为
a {
color: green;
margin:20px;
}
link {
color: red;
width: 200px;
height: 200px;
}
其中有一个变量作用域的问题,less中的变量作用域和js中的类似。你可以吧a {}
这样的代码视为一个块,拥有块级作用域。
在代码的a
中,由于块中同样声明了@color:green
,因此在使用时以green为主。
而在link
中并未声明@color
因此使用时顺着作用域链找到全局的red。
你会发现在a
中写出了height:$width;
这样的代码, 这就是变量的特例情况,以$开头的变量仅会在当前作用域寻找对应的css属性,代码中可以找到width为:200px;
由于只会在当前作用域寻找,以下代码是不合法的:
div{
width: 200px;
& div{
height: &width;
}
}
变量的类型
以下赋值均合理:
@color: red;
@num: 123;
@list: banana, apple, watermelon;
@set: {
small: 小小的也很可爱🧡;
normal: just so so;
large: suki;
}
.mixinA(){
width: 10px;
height:20px;
}
.mixinB(){
width:20px;
height:20px;
}
.mixinC(){
width:20px;
height:10px;
}
@rules: {
ruleA: .mixinB();
ruleB: .mixinB();
ruleC: .mixinC();
}
变量作用域引发的问题
当我们在写组件时, 一般是样式文件less和逻辑文件vue分离,所有的less文件统一存放并由索引文件导出。
假设我在a.less文件中定义的@num:10
变量, 在b.less文件中也同样定义了@num:20
变量。又假设我们在index.less中先引入a.less, 再引入b.less, 那么@num变量最终值由b.less文件中的值决定(最后定义)。
艹蛋的来了,此时a.less文件中的@num
会变成20(最后定义的值),可以理解为less中的变量先提升再组合在一起。(ps: 不愧是less! 和js一脉相承了属于是)
变量提升的同时也会导致以下代码是合法的:
div{
margin:@num;
}
@num: 20px;
//翻译为:
div {
margin: 20px;
}
为了解决这些反直觉的问题,提供以下解决方案:
注意点
当某个属性,比如height: 20px;这段代码再多次被定义时, 应该吧20px抽离为css变量而不是less变量, css变量在运行时可以修改,而less变量只能在编译前使用,也就是less变量是静态的。相对于css变量,less变量灵活性更差。
混合
less中的混合是一个非常强大的功能!可以解决合并很多重复代码或者类似代码.
基本使用
.mixin(){
width: 10px;
height: 10px;
//other css
}
div {
.mixin();
}
//翻译为:
div {
width: 10px;
height: 10px;
}
混合中定义参数
混合是支持定义参数和默认值的, 支持的参数可以是上文提到所有参数,egs:
.setSize(@num, @unit:px){ //定义元素宽高
width: ~"@{num}@{unit}";
height: $width;
}
div{
.setSize(20);
}
//翻译为:
div {
width: 20px;
height: 20px;
}
别忘了less变量可以是一个规则集, 因此以下代码是合法的:
.m(@modifier, @rules:{}){ //定义修饰符的样式
&--@{modifier} {
@rules();
}
}
.s-input{
.m(icon,{
display: flex;
//other css
})
}
//翻译为
.s-input--icon {
display: flex;
}
混合守卫
有时候需要根据混合的参数是否符合条件来决定使用不同的规则集
.senbai(@num) when(@num = 114514) {
margin: @num;
}
.s-input{
width: auto;
.senbai(15);
}
.s-button{
width: auto;
.senbai(114514);
}
//翻译为:
.s-input {
width: auto;
}
.s-button {
margin: 114514;
}
tips 使用混合守卫完成递归混合:
.mg(@num) when(@num <= 5) {
.m-@{num} {
margin: ~"@{num}px";
}
.mg(@num+1);
}
.mg(1);
//翻译为:
.m-1 {
margin: 1px;
}
.m-2 {
margin: 2px;
}
.m-3 {
margin: 3px;
}
.m-4 {
margin: 4px;
}
.m-5 {
margin: 5px;
}
混合守卫不仅仅能在混合中调用,以下代码均是合法的;
.senbai(@num) {
& when (@num = padding) {
margin: 114514px;
}
& when (@num = margin) {
padding: 114514px;
}
}
.s-input {
.senbai(padding);
}
.s-button {
.senbai(margin);
& when(true) {
width: 100px;
}
}
//翻译为:
.s-input {
margin: 114514px;
}
.s-button {
padding: 114514px;
width: 100px;
}
相对于叫作混合守卫,我觉得更像是逻辑函数。
混合重载
当一个混合接受不定个数参数时,重载是个很好的选择:
.define(@b, @r:{}) { //定义一个block
.@{b} {
@r();
}
}
.m(@b, @m, @r:{}) { //定义block--modifier
.@{b}--@{m} {
@r();
}
}
//定义block--modifier, 此处没有传入block,将选择父元素作为block
.m(@m, @r:{}) {
&--@{m} {
@r();
}
}
//namespace
@ns: s-input;
.define(@ns, {
height: 32px;
width: 40vw;
.m(icon, {
height: 16px;
width: 16px;
});
});
.m(@ns, slot, {
width: 100px;
height: 100%;
});
//翻译为:
.s-input {
height: 32px;
width: 40vw;
}
.s-input--icon {
height: 16px;
width: 16px;
}
.s-input--slot {
width: 100px;
height: 100%;
}
上述混合完成了经过翻译最终生成了遵循bem规范的css
注意点
你会看到上述定义的混合无论有没有参数, 都携带了小括号,但实际上,若是没有参数则小括号可以省略不写,但随之而来的问题是,不携带小括号的混合将会被翻译最终在生成的css文件中, 也就是less将其看成了一个类
.mixin{
height: 10px;
width: 10px;
}
div{
.mixin();
}
//翻译为:
.mixin {
height: 10px;
width: 10px;
}
div {
height: 10px;
width: 10px;
}
这意味着你可以将任何类当成一个混合来使用! 当然一般不会这么做。
建议混合都带上小括号区分于类, 不仅仅是为了那打包后的一点点体积,更多是的是为了规范性。
函数
请不要把函数和混合看成同一种东西!
基本使用
less中内置了许许多多的函数提供开发者使用,但许多内置函数都有一个共同点,只用作为某个属性/变量的右值, 最常见的比如颜色函数。
@color:red;
@color2: darken(@color, -20%);
div {
color: darken(@color, 20%);
}
each函数
我认为less中,最不行的就是流程控制函数, 貌似只有each、when, 当然还有一个if,但是它用起来反人类。
each可以用来遍历变量, 常见用来遍历列表、集合:
@list:primary, success;
each(@list, .(@value,@key, @index){
@{value} {
key:@key;
index:@index;
}
})
//翻译为:
primary {
key: 1;
index: 1;
}
success {
key: 2;
index: 2;
}
当each的第二个参数不是一个混合而是规则集时, 默认具有@value, @key, @index三个参数。因此以下写法也是合法的:
@list:primary, success;
each(@list, {
@{value} {
key:@key;
index:@index;
}
})
each(primary success, {
@{value} {
key:@key;
index:@index;
}
})
上述代码翻译的结果一样,但是强烈不建议这么写!, 假设是双重混合时,会出现意料之外的结果:
each(primary success, {
@type:@value;
each(small large, {
@size:@value;
@{type}{
size:@size;
}
})
})
//翻译为:
small {
size: small;
}
large {
size: large;
}
small {
size: small;
}
large {
size: large;
}
上述代码愿意是,匹配不同的type类型和size大小,生成不同的样式, 就如果介绍全局变量时那样,似乎出现的变量提升合并的问题,最终value使用了最后一次赋值。
遍历set:
@import "../mixn/layout";
@sizeSet: {
small: .useSizeSmall();
normal: .useSizeDft();
large: .useSizeLarge();
}
each(@sizeSet, .(@rules, @key){
@{key} {
@rules();
}
})
//翻译为:
small {
min-height: var(--sss-component-height-small);
font-size: var(--sss-font-size-small);
padding: 6px 9.5px;
}
normal {
min-height: var(--sss-component-height);
font-size: 15px;
padding: 9px 14.5px;
}
large {
height: var(--sss-component-height-large);
font-size: 16px;
padding: 12px 19.5px;
}
集合的遍历和list类似,但一般集合用于储存混合, 因此使用@rule()
调用混合。
自定义函数
在less 2.5.0以上我们可以通过@plugin引入自定义函数
@plugin "myPlugin"
一个最基本的less plugin为:
//获取css变量
const getClrVar = function (...args){
const strList = Array.from(args.map(item => item.value));
const str = strList.join('-');
return `var(--sss-color-${str})`;
}
module.exports = {
install: function(less, pluginManager, functions) {
functions.add('getClrVar', getClrVar);
}
};
使用:
@plugin "../plugin/index";
div {
color: getClrVar(primary);
background-color: getClrVar(primary, light);
}
//翻译为:
div {
color: var(--sss-color-primary);
background-color: var(--sss-color-primary-light);
}
自定义函数的返回值不仅仅是字符串,你也可以返回less中抽象的AST的Node,这点很重要,你可以通过返回Node使得函数不仅仅作为属性的右值
奈何自己技术不行,不会😭
最佳实践
让我们回到最初提到的三个问题:
对于不同的type,假设定义type为: primary, success, info, warning, danger; 我们可以设置对应的css颜色变量, 使用list存储不同的type,在each函数中生成不同颜色的方案.
对于不同size, 假定定义size为:small, normal, large; 我们可以使用set存储不同size对应使用不同的混合, 也是通过each生成不同的大小方案.
对于如何书写符合bem规范的代码,相信通过前面的代码也能看出,可以通过混合、重载混合、函数等手段创建出相应的block element modifier...
bem相关: 混合
@variablePrefix: sss;
@componentPrefix:s;
.d(@b, @r:{}) { //定义block
.@{b} {
@r();
}
}
.with(@v, @r:{}) { //相当于&&
&.@{v} {
@r();
}
}
.with(@f, @v, @r:{}) {
.@{f}.@{v} {
@r();
}
}
.include(@v, @r:{}) { //包括
& .@{v} {
@r();
}
}
.include(@f, @v, @r:{}) {
.@{f} .@{v} {
@r();
}
}
.b(@b, @r:{}) { //定义block
&-@{b}{
@r();
}
}
.e(@e, @r:{}) { //定义element
&__@{e} {
@r();
}
}
.m(@m, @r:{}) { //定义modifier
&--@{m} {
@r();
}
}
.bm(@b, @m, @r:{}){ //定义block--modifier
&-@{b}--@{m} {
@r();
}
}
.be(@b, @e, @r:{}){ //定义-block__element
&-@{b}__@{e}{
@r();
}
}
.em(@e, @m, @r:{}){ //定义__element--modifier
&__@{e}--@{m} {
@r();
}
}
code
@import "../mixn/util";
@import "../mixn/layout";
@import "../mixn/border";
@plugin "../plugin/index";
& {
@ns: @{componentPrefix}-button;
@types: primary, success, info, warning, danger;
@sizeSet: {
small: .useSizeSmall();
normal: .useSizeDft();
large: .useSizeLarge();
}
//定义一个block
.define(@ns, {
.useBorderDft();
//css
//循环定义types modifier
each(@types, .(@type) {
.m(@type, {
color: var(--sss-color-white);
background-color: getClrVar(@type);
&:hover, //高亮
&:focus {
background-color: getClrVar(@type, light);
}
&:active { //颜色暗一点, 制造点击效果
background-color: getClrVar(@type, dark);
}
})
})
//循环定义sizes modifier
each(@sizeSet, .(@rule, @size){
.m(@size, {
@rule();
})
})
})
}
最终翻译为:
.s-button {
border: 1px solid var(--sss-color-gray);
background-color: var(--sss-color-bg);
box-sizing: border-box;
}
.s-button * {
box-sizing: border-box;
}
.s-button--primary {
color: var(--sss-color-white);
background-color: var(--sss-color-primary);
}
.s-button--primary:hover,
.s-button--primary:focus {
background-color: var(--sss-color-primary-light);
}
.s-button--primary:active {
background-color: var(--sss-color-primary-dark);
}
.s-button--success {
color: var(--sss-color-white);
background-color: var(--sss-color-success);
}
.s-button--success:hover,
.s-button--success:focus {
background-color: var(--sss-color-success-light);
}
.s-button--success:active {
background-color: var(--sss-color-success-dark);
}
.s-button--info {
color: var(--sss-color-white);
background-color: var(--sss-color-info);
}
.s-button--info:hover,
.s-button--info:focus {
background-color: var(--sss-color-info-light);
}
.s-button--info:active {
background-color: var(--sss-color-info-dark);
}
.s-button--warning {
color: var(--sss-color-white);
background-color: var(--sss-color-warning);
}
.s-button--warning:hover,
.s-button--warning:focus {
background-color: var(--sss-color-warning-light);
}
.s-button--warning:active {
background-color: var(--sss-color-warning-dark);
}
.s-button--danger {
color: var(--sss-color-white);
background-color: var(--sss-color-danger);
}
.s-button--danger:hover,
.s-button--danger:focus {
background-color: var(--sss-color-danger-light);
}
.s-button--danger:active {
background-color: var(--sss-color-danger-dark);
}
.s-button--small {
min-height: var(--sss-component-height-small);
font-size: var(--sss-font-size-small);
font-size: 15px;
padding: 9px 14.5px;
}
.s-button--large {
height: var(--sss-component-height-large);
font-size: 16px;
padding: 12px 19.5px;
}
写在最后
关于less的更多用法,可以去less中文网看看, 这里算是做一个引子吧。
关于如何在less中如何写出符合bem规范的代码,同时还要做到优雅简洁,我认为还有很大的研究空间,文中的写法不可能就是最优的,欢迎提出更好的解决方案🥺
最后给自己的项目引个流:lastertd/sss-ui-plus: 适用于vue3的组件库 (github.com)。 希望留一个star✨
转载自:https://juejin.cn/post/7283748394602233896