likes
comments
collection
share

less深入指南

作者站长头像
站长
· 阅读数 83

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
评论
请登录