一、老样纸,先上效果图

在这里插入图片描述

效果图说明

  1. 图中最顶部三个按钮是用来切换主题色的,然后中间是两个组件,一个是button,一个是input,是组件库内置的组件;
  2. 最下面下面的组件是开发者自定义的组件card,至于内置组件以及开发者自定义组件的区别,请看下文;
  3. 顶部三个按钮,代表三种主题色,其中“默认主题”和“黑色主题”是组件库自带的主题,“自定义主题”是开发者自定义的主题,至于自带主题与自定义主题区别请看下文;

二、背景

这里说一下背景,最近在写Vue组件库,写到了组件库支持主题色切换的部分,参考了一下目前比较流行的Vue组件库:ElementUI、IView等等,发现没有实现小编想要的效果,小编想要什么效果呢:

  1. 组件库一般都会有几种提示色:基本、成功、警告、错误、提示、禁用,这几种颜色可能会用于各种各样的组件,比如按钮、输入框、消息提示框、开关按钮、单选/复选框等等。当然,组件库预设的主题样式肯定是不能满足开发需求的,大多数产品都希望自己的内容样式是独一无二的,给人一种记忆深刻的感觉,所以开发者需要能够对组件库的颜色进行快速地调整;
  2. 组件库会有自己的预设主题,这里开发者希望组件库能够支持增加主题,开发者通过修改变量就能够增加主题,而不是通过写样式一个一个去改组件的样式,并且可以随意切换主题色;
  3. 开发者也会有自己的自定义组件,并且希望这些自定义组件也能够像组件库组件一样,支持组件库内置的主题。开发者自定义的主题。并且支持切换主题色;
  4. 开发者在自定义主题的时候,可能只是想要改变部分主题变量,部分未修改的变量希望能够使用组件库默认主题的变量显示;
  5. 最好是能够通过切换class的方式实现主题色切换(这一点可有可无,不过基于本文内容可以实现该功能);

三、设计分析

a. css变量

这个是个失败的方案,赶时间的同学可以不用看。css变量还是蛮强大的,不过基于css变量实现主题色切换既有优点,又有缺点,只不过在本文中,缺点大于优点,于是放弃了这个方案。

  1. 优点:组件库基于css变量实现主题色,只要在:root选择器下声明颜色变量,然后在不同的css选择器下,重新给这些css颜色变量赋值,就可以达到在不同css选择器下,展示不同颜色或者其他样式的效果,这个还是非常好的一个特性,甚至可以实现普通用户在网页上选择一个颜色,然后开发者将这个颜色通过style的方式改变css变量就可以实现切换颜色的效果。而且开发者不需要依赖sass、less、stylus等等。只需要改变css变量就可以实现主题切换。
  2. 但是,缺点是IE不支持css变量!小编通过各种方式,包括使用了让IE支持css变量的各种各样的腻子脚本,效果都不好,因为有的页面或者组件懒加载代码,这样会就会需要频繁调用腻子脚本,网页性能非常不好。但是,如果组件库针对的是移动应用,应该就不会有这个问题,因为ios或者android上的浏览器内核目前基本都支持css变量。
  3. 总的来说,辛辛苦苦写的组件库总不能因为主题色切换这种琐碎的功能而不支持IE,那样这个组件库就丧失了很大一部分的竞争力了,所以小编还是从sass开始,设计能够支持IE的主题色切换方案。

b. sass实现方案

  1. 优点:上面背景说的特性都能够支持,并且支持IE浏览器;
  2. 缺点:开发者需要通过sass-loader构建,否则基本上做不到背景中提到的特性;

四、实现步骤

1. Demo工程地址

https://github.com/martSforever/demo-theme

2. 具体实现

Demo工程运行起来,就是效果图中的内容,如果同学们要实现功能具体需要check out工程参考里面的代码,下面会对工程中的部分内容进行说明;

3. 工程目录结构

这个工程分为两部分,src可以理解为组件库,demo理解为开发者产品应用。这个工程是基于vueCli3搭建的,相关配置在vue.config.js中。

在这里插入图片描述

4. 组件库实现步骤

先看一下组件库目录下结构:

在这里插入图片描述

1) 先准备一个global.scss

准备默认主题

global.scss中首先是准备一个默认主题,默认主题的作用有两个,首先一个是主题,第二个就是,其他主题不必要声明所有的变量,只需要覆盖部分变量即可,比如开发者只需要改primary color,只需要在自定义主题中写上color-primary、color-primary-deep、color-primary-light即可,其他变量如果在自定义主题中找不到,则使用默认主题的颜色;

$default-theme: (
        font-family: (Arial, "PingFang SC", "Hiragino Sans GB", STHeiti, "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif),
        color-primary: #3F86E8,
        color-primary-light: #92BBF3,
        color-primary-deep: #4F77AE,
        color-primary-lighter: #edf6ff,
        color-success: #30ED69,
        color-success-light: #8AF6AB,
        color-success-deep: #48B268,
        color-warn: #FFAF34,
        color-warn-light: #FFD38F,
        color-warn-deep: #BF924D,
        color-error: #FF3333,
        color-error-light: #FF8F8F,
        color-error-deep: #BF4C4C,
        color-info: #929292,
        color-info-light: #C8C8C8,
        color-info-deep: #6D6D6D,
        color-disabled: #EBEAE7,
        color-disabled-light: #F5F4F3,
        color-disabled-deep: #B0B0AE,
        color-title:#000,
        background-color:#eee,
        background-color-deep:#ddd,
        background-color-light:#fff,
);

声明所有的主题

可以看到,组件库中内置的主题有两个,一个是default,一个black,其中black的颜色基本与默认主题色是不一样的;

$pl-default-themes: (
        "default":$default-theme,
        "black":(
                color-primary: #e800a8,
                color-primary-light: #ea8bd1,
                color-primary-deep: #ba008b,
                color-primary-lighter: #e8d1e4,
                color-success: #3eede5,
                color-success-light: #8bede5,
                color-success-deep: #299e99,
                color-warn: #9a61ff,
                color-warn-light: #d1acff,
                color-warn-deep: #6540a8,
                color-error: #ffa100,
                color-error-light: #ffbe7a,
                color-error-deep: #a36700,
                color-info: #a8fc57,
                color-info-light: #d4fca1,
                color-info-deep: #72ab3b,
                color-disabled: #d9d2d5,
                color-disabled-light: #F5F4F3,
                color-disabled-deep: #B0B0AE,
                color-title:#fff,
                background-color:#333,
                background-color-deep:#000,
                background-color-light:#999,
        ),
);

获取变量函数function

这个pl-var的作用是根据传入的变量名从当前全局主题 $theme-map中获取变量,而这个当前全局主题变量 $theme-map是动态变化的。

@function pl-var($key) {
  @if (map_has_key($theme-map, $key)) {
    @return map-get($theme-map, $key);
  } @else {
    @return map-get($default-theme, $key);
  }
}

生成主题代码的mixin

这个pl-themeify的作用是生成主题代码,参数有两个,第一个 $theme表示新增加的主题变量,格式与 $default-theme一致,第二个 $append-default-theme为true表示传入的 $theme将与 $pl-default-themes合并生成样式,为false的话,则只生成 $theme的主题样式;

@mixin pl-themeify($theme,$append-default-theme:true) {
  @if (length(map-keys($theme))>0 and $append-default-theme) {
    $pl-themes: map-merge($pl-default-themes, $theme) !global;
  } @else {
    $pl-themes: $theme !global;
  }
  @each $theme-name, $theme-map in $pl-themes {
    $theme-map: $theme-map !global;

    $p-font-family: pl-var(font-family) !global;
    $p-color-primary: pl-var(color-primary) !global;
    $p-color-primary-light: pl-var(color-primary-light) !global;
    $p-color-primary-deep: pl-var(color-primary-deep) !global;
    $p-color-primary-lighter: pl-var(color-primary-lighter) !global;
    $p-color-success: pl-var(color-success) !global;
    $p-color-success-light: pl-var(color-success-light) !global;
    $p-color-success-deep: pl-var(color-success-deep) !global;
    $p-color-warn: pl-var(color-warn) !global;
    $p-color-warn-light: pl-var(color-warn-light) !global;
    $p-color-warn-deep: pl-var(color-warn-deep) !global;
    $p-color-error: pl-var(color-error) !global;
    $p-color-error-light: pl-var(color-error-light) !global;
    $p-color-error-deep: pl-var(color-error-deep) !global;
    $p-color-info: pl-var(color-info) !global;
    $p-color-info-light: pl-var(color-info-light) !global;
    $p-color-info-deep: pl-var(color-info-deep) !global;
    $p-color-disabled: pl-var(color-disabled) !global;
    $p-color-disabled-light: pl-var(color-disabled-light) !global;
    $p-color-disabled-deep: pl-var(color-disabled-deep) !global;
    $p-color-title:pl-var(color-title) !global;
    $p-background-color: pl-var(background-color) !global;
    $p-background-color-light: pl-var(background-color-light) !global;
    $p-background-color-deep: pl-var(background-color-deep) !global;

    @if (str_length($theme-name) >0) {
      .pl-theme-#{$theme-name} {
        font-family: $p-font-family;
        @content;
      }
    } @else {
      html {
        @content;
      }
    }
  }
}

用于开发者自定义组件主题化的mixin

@mixin pl-theme-wrap($theme) {
  @include pl-themeify($theme, true) {
    @content;
  }
}

用于生成组件库内置组件适配开发者自定义主题样式的mixin

@mixin pl-use-theme($theme:()) {
  @include pl-themeify($theme, false) {
    @include component-mixin;
  }
}

用于快速生成颜色的mixin

@mixin pl-colors($prefix) {
  @each $key in (primary, success, warn, error, info, disabled) {
    $key: $key !global;
    $pl-color: pl-var(color-#{$key}) !global;
    $value: pl-var(color-#{$key}) !global;
    $pl-color-deep: pl-var(color-#{$key}-deep) !global;
    $pl-color-light: pl-var(color-#{$key}-light) !global;
    @if (str_length($prefix)>0) {
      &.#{$prefix}-color-#{$key} {
        @content;
      }
    } @else {
      @content;
    }
  }
}

2) 组件库index.scss

组件库需要生成一份自带组件默认样式,所以index.scss内容如下

@import "global";

@include pl-use-theme($pl-default-themes)

3) 开发者global.scss

开发者的global.scss中声明新的主题色,然后还需要使用新的主题色封装pl-theme-wrap:themeify,themeify用来在开发者自定义组件中快速生成样式:

global.scss

@import "../src/styles/global";

$custom: (
        custom:(
                color-primary: #3ccb3a,
                color-primary-light: #4af947,
                color-primary-deep: #31a730,
                color-primary-lighter: #d5f9c9,
                color-success: #3597cb,
                color-success-light: #85cfef,
                color-success-deep: #24678a,
                color-warn: #cb6436,
                color-warn-light: #cb9682,
                color-warn-deep: #a6522c,
                color-error: #cb6596,
                color-error-light: #cba5b4,
                color-error-deep: #954a6e,
                color-info: #751bcb,
                color-info-light: #9d7ccb,
                color-info-deep: #5d15a1,
                color-disabled: #d9d2d5,
                color-disabled-light: #F5F4F3,
                color-disabled-deep: #B0B0AE,
                color-title:#fff,
                background-color:#77d2e6,
                background-color-deep:#5799a8,
                background-color-light:#a8f2ff,
        )
);

@mixin themeify {
  @include pl-theme-wrap($custom) {
    @content;
  }
}

demo-card

<template>
    <div class="demo-card" :class="[`demo-card-color-${color}`]">
        <div class="demo-card-head">
            卡片标题
        </div>
    </div>
</template>

<script>
    export default {
        name: "demo-card",
        props: {
            color: {type: String, default: 'primary'},
        }
    }
</script>

<style lang="scss">
    @include themeify {
        .demo-card {
            display: inline-block;
            width: 150px;
            height: 150px;
            border: solid 1px;
            border-radius: 4px;
            .demo-card-head {
                height: 40px;
                padding: 0 12px;
                line-height: 40px;
                font-size: 13px;
                color: white;
            }
            @include pl-colors(demo-card) {
                border-color: $value;
                .demo-card-head {
                    background-color: $value;
                }
            }
        }
    }
</style>

3) 开发者index.scss

开发者index.scss中的任务是生成组件库自带组件+自定义主题色的css

@import "global";

@include pl-use-theme($custom)

五、总结

上面的代码有点乱,这里理清一下结构。生成样式,总共分为四部分内容:

  1. 组件库自带组件;
  2. 组件库自带主题;
  3. 开发者自定义组件;
  4. 开发者自定义主题;

生成css

  1. 组件库自带组件+组件库自带主题:是在组件库src下的index.scss这句代码中完成的:

    @include pl-use-theme($pl-default-themes)
    
  2. 组件库自带组件+开发者自定义主题:实在开发者demo下的index.scss这句代码中完成的:

    @include pl-use-theme($custom)
    
  3. 开发者自定义组件+(组件自带主题+开发者自定义主题)是在开发者自定义的mixin中实现的:

    @mixin themeify {
      @include pl-theme-wrap($custom) {
        @content;
      }
    }
    

    然后开发的自定义组件中需要使用themeify包裹,使用全局变量:

    @include themeify {
            .demo-card {
                display: inline-block;
                width: 150px;
                height: 150px;
                border: solid 1px;
                border-radius: 4px;
                .demo-card-head {
                    height: 40px;
                    padding: 0 12px;
                    line-height: 40px;
                    font-size: 13px;
                    color: white;
                }
                @include pl-colors(demo-card) {
                    border-color: $value;
                    .demo-card-head {
                        background-color: $value;
                    }
                }
            }
        }
    
Logo

前往低代码交流专区

更多推荐