提升组件库通用能力 - NutUI 在线主题定制功能探索
开发背景
NutUI 作为京东风格的组件库,已具备 H5 和多端小程序开发能力。随着业务的不断发展,组件库的应用场景越来越广。在公司内外面临诸如科技、金融、物流等各多个大型团队使用时,单一的京东 APP 视觉虽可以一键进行换肤操作,但是对于更个性化的定制需求(组件级样式、规范、尺寸等)近千行的主题样式变量对开发者来说工作量是非常大的。为提升开发体验,提高开发者效率,加强换肤功能以及实现「组件级式定制」功能迫在眉睫。
设计目标
允许用户在开发阶段切换不同主题风格的皮肤,也允许开发者对指定的组件直接进行样式修改,以满足不同设计风格的移动端业务场景。
效率提升
官网会提供多套主题供开发者选择,同时开发者也可以在多套主题基础上进行实时编辑修改,完成后下载配置变量,应用在项目中即可,非常易上手。完成一个全局样式配置仅需1分钟。
相对这种场景下的需求开发是比较快的,能够降低开发成本。
组件粒度
主题定制配置层分为全局基本变量、组件基本变量,开发者可以修改全局,比如组件库的全局主题颜色,字体等样式。组件层的配置可以更细致,比如 Button 按钮成功类型的圆角边框尺寸


通用扩展能力
现阶段官方会提供一些优质主题集成到官网的,对于社区开发者、开发团队、如果您的团队定制的样式主题文件受众非常之广,可以联系我们,将您的主题内置到官方 npm 包中,造福更多的开发者

开发者如何使用
视频教程
NutUI 一分钟快速在线主题定制 https://www.bilibili.com/video/BV1fi4y1D7qb
1、打开在线配置网站,按照下方图片进行修改预览下载

2、本地项目配置
修改本地项目 webpack 或者 vite 的配置文件将下载后的 custom_theme.sass 文件,集成到项目中比如assets/styles/custom_theme.sass
- vite 构建工具使用示例 vite.config
// https://vitejs.dev/config/
export default defineConfig({
//...
css: {
preprocessorOptions: {
scss: {
// 默认京东 APP 10.0主题 > @import "@nutui/nutui/dist/styles/variables.scss";
// 京东科技主题 > @import "@nutui/nutui/dist/styles/variables-jdt.scss";
additionalData: `@import "./assets/styles/custom_theme.scss";@import "@nutui/nutui/dist/styles/variables.scss";`
}
}
}
})
- webpack 构建工具使用示例
{
test: /\.(sa|sc)ss$/,
use: [
{
loader: 'sass-loader',
options: {
// 默认京东 APP 10.0主题 > @import "@nutui/nutui/dist/styles/variables.scss";
// 京东科技主题 > @import "@nutui/nutui/dist/styles/variables-jdt.scss";
data: `@import "./assets/styles/custom_theme.scss";@import "@nutui/nutui/dist/styles/variables.scss";`,
}
}
]
}
- taro 小程序使用示例
修改 config/index.js 文件中配置 scss 文件全局覆盖如:
const path = require('path');
const config = {
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2,
375: 2 / 1
},
sass: {
resource: [
path.resolve(__dirname, '..', 'src/assets/styles/custom_theme.scss')
],
// 默认京东 APP 10.0主题 > @import "@nutui/nutui-taro/dist/styles/variables.scss";
// 京东科技主题 > @import "@nutui/nutui-taro/dist/styles/variables-jdt.scss";
data: `@import "@nutui/nutui-taro/dist/styles/variables.scss";`
},
// ...
实现原理解析
整个组件库主题定制模块,实现可以分为两个方向,一个是内部的组件库设计(供开发者使用配置每个样式变量),另一个是在线配置官网(供开发者便捷的修改),接下来依次按照设计图来阐述。

组件库内部设计
首先源码内部style文件夹下,分别存在variables.scss、variables-jdt.scss多个文件对应的不同的官方主题,每个主题的全局的variables.scss文件,内部其实按标准的规则存放存放通用样式变量和每个组件的样式变量,像下面一样
// --------base begin-------
// 主色调
$primary-color: #fa2c19 !default;
$primary-color-end: #fa6419 !default;
// 辅助色
$help-color: #f5f5f5 !default;
// 标题常规文字
$title-color: #1a1a1a !default;
// 副标题
$title-color2: #666666 !default;
// 次内容
$text-color: #808080 !default;
//...
// Font
$font-size-0: 10px !default;
$font-size-1: 12px !default;
$font-size-2: 14px !default;
$font-size-3: 16px !default;
$font-size-4: 18px !default;
$font-weight-bold: 400 !default;
$font-size-small: $font-size-1 !default;
$font-size-base: $font-size-2 !default;
$font-size-large: $font-size-3 !default;
$line-height-base: 1.5 !default;
// --------base end-------
// button
$button-border-radius: 25px !default;
$button-border-width: 1px !default;
$button-default-bg-color: $white !default;
$button-default-border-color: rgba(204, 204, 204, 1) !default;
$button-default-color: rgba(102, 102, 102, 1) !default;
//...
// icon
// ...
这里啰嗦一句,可以看到每一行后面都有一个 !default,这个是必不可少的,如果不加,开发者本地项目是无法覆盖这个变量的
https://www.sass.hk/docs/#t6-9 Tips: 可以在变量的结尾添加 !default 给一个未通过 !default 声明赋值的变量赋值,此时,如果变量已经被赋值,不会再被重新赋值,但是如果变量还没有被赋值,则会被赋予新的值。
对于每一个组件的内部,例如button/index.scss下是这样引用height: $button-default-height;
.nut-button {
position: relative;
display: inline-block;
flex-shrink: 0;
height: $button-default-height;
// ...
}
其实最终组件库构建成 npm 包时,将主题的全局的variables.scss等主题文件暴露给开发者,然后开发者根据需求替换其中的样式变量,至此组件库内部实现主题定制就实现了
可视化配置官网
源码抢先看:https://github.com/jdf2e/nutui/tree/theme/src/sites/doc/components/ThemeSetting
整体实现流程如下,接下来依次阐述
- 将
variables.scss源文件,通过组件配置数据 + 正则匹配拆分,得到这样的数据结构
// 主色调
$primary-color: #fa2c19 !default;
$primary-color-end: #fa6419 !default;
//...
// button
$button-border-radius: 25px !default;
$button-border-width: 1px !default;
//...
// icon
// ...
[
{name: 'Base', lowerCaseName: 'base', key: '$primary-color', rawValue: '#fa2c19', computedRawValue: ''}
{name: 'Base', lowerCaseName: 'base', key: '$primary-color-end', rawValue: '#fa6419', computedRawValue: ''}
// ...
{name: 'Button', lowerCaseName: 'button', key: '$button-border-width', rawValue: '1px', computedRawValue: ''}
{name: 'Button', lowerCaseName: 'button', key: '$button-border-radius', rawValue: '25px', computedRawValue: ''}
//{name: 'components1', lowerCaseName: 'components1', key: '$components1-border-radius', rawValue: 'xx', computedRawValue: ''}
//...
]
const findStyle = (componentName: string) => {
// https://raw.githubusercontent.com/jdf2e/nutui/next/src/packages/styles/variables.scss
// var pattern = /\$button.*;/g;
var p = new RegExp(`\\$${componentName}.*;`, 'g');
let parray: any[] = varcss.match(p) || [];
// 需要包含换行
let commponetns = parray.map((item) => {
let cArray = item.split(':');
let name = cArray[0],
value: string = cArray[1].replace(' !default;', '').trim();
return {
name: componentName,
key: name,
rawValue:value,
computedRawValue: ''
}
});
}
components.map(item=>{ findStyle(item.name) });
- 接下来根据组件不同展示该组件下所有变量,监听组件切换切换或者编辑,进行实时编译
const cssText = computed(() => {
const variablesText = store.variables.map(({ key, value }) => `${key}:${value}`).join(';');
cachedStyles = cachedStyles || extractStyle(store.rawStyles);
return `${variablesText};${cachedStyles}`;
});
const formItems = computed(() => {
const name = route.path.substring(1);
return store.variables.filter(({ lowerCaseName }) => lowerCaseName === name);
});
watch(
() => cssText.value,
(css) => {
clearTimeout(timer);
timer = setTimeout(() => {
const Sass = (window as any).Sass;
let beginTime = new Date().getTime();
console.log('sass编译开始', beginTime);
Sass &&
Sass.compile(css, async (res: Obj) => {
await awaitIframe();
const iframe = window.frames[0] as any;
if (res.text && iframe) {
console.log('sass编译成功', new Date().getTime() - beginTime);
if (!iframe.__styleEl) {
const style = iframe.document.createElement('style');
style.id = 'theme';
iframe.__styleEl = style;
}
iframe.__styleEl.innerHTML = res.text;
iframe.document.head.appendChild(iframe.__styleEl);
} else {
console.log('sass编译失败', new Date().getTime() - beginTime);
console.error(res);
}
if (res.status !== 0 && res.message) {
console.log(res.message);
}
});
}, 300);
},
{ immediate: true }
);
- 下载配置变量操作
由于变量文件近千行,以后可能还会更大,直接采用Blob文件流进行生成下载。
downloadScssVariables() {
if (!store.variables.length) {
return;
}
let temp = '';
const variablesText = store.variables
.map(({ name, key, value }) => {
let comment = '';
if (temp !== name) {
temp = name;
comment = `\n// ${name}\n`;
}
return comment + `${key}: ${value};`;
})
.join('\n');
download(`// NutUI主题定制\n${variablesText}`, 'custom_theme.scss');
}
function download(content: string, filename: string) {
const eleLink = document.createElement('a');
eleLink.download = filename;
eleLink.style.display = 'none';
const blob = new Blob([content]);
eleLink.href = URL.createObjectURL(blob);
document.body.appendChild(eleLink);
eleLink.click();
document.body.removeChild(eleLink);
}
总结
文章详细介绍了 NutUI 的「主题定制」和「组件级样式定制」功能实现机制。「主题定制」能实现简单的颜色切换,「组件级样式定制」功能更强大,通过将组件的样式变量暴露出来开发者几乎可以任意修改自己想要的设计风格(组件尺寸、字体、边距)。通过强大的主题定制可以让组件库的使用不局限于原设计者的设计范畴,可灵活扩展组件,让组件库的应用范围更广,能满足更广泛的业务场景。
期待您的使用与反馈 ️~
提升组件库通用能力 - NutUI 在线主题定制功能探索的更多相关文章
- 微信小程序一键生成源码 在线制作定制功能强大的微信小程序
微信小程序发展到现在,短短的一年不到的时间(很快就要迎来微信小程序周年庆),在快迎来周年庆之际,百牛信息技术bainiu.ltd特记录一下这个发展的历程,用于将来见证小程序发展的辉煌时刻,我们还能知道 ...
- 小技巧!CSS 提取图片主题色功能探索
本文将介绍一种利用 CSS 获取图片主题色的小技巧.一起看看~ 背景 起因是微信技术群里有个同学发问,有什么方法能够获取图片的主色呢?有一张图片,获取他的主色调: 利用获取到的这个颜色值,来实现类似这 ...
- beeshell —— 开源的 React Native 组件库
介绍 beeshell 是一个 React Native 应用的基础组件库,基于 0.53.3 版本,提供一整套开箱即用的高质量组件,包含 JavaScript(以下简称 JS)组件和复合组件(包含 ...
- 滴滴开源 Vue 组件库— cube-ui
cube-ui 是滴滴去年底开源的一款基于 Vue.js 2.0 的移动端组件库,主要核心目标是做到体验极致.灵活性强.易扩展以及提供良好的周边生态-后编译. 自 17 年 11 月开源至今已有 5 ...
- HslCommunication组件库使用说明 (转载)
一个由个人开发的组件库,携带了一些众多的功能,包含了数据网络通信,文件上传下载,日志组件,PLC访问类,还有一些其他的基础类库. nuget地址:https://www.nuget.org/packa ...
- HslCommunication组件库使用说明
一个由个人开发的组件库,携带了一些众多的功能,包含了数据网络通信,文件上传下载,日志组件,PLC访问类,还有一些其他的基础类库. nuget地址:https://www.nuget.org/packa ...
- 如何快速构建React组件库
前言 俗话说:"麻雀虽小,五脏俱全",搭建一个组件库,知之非难,行之不易,涉及到的技术方方面面,犹如海面风平浪静,实则暗礁险滩,处处惊险- 目前团队内已经有较为成熟的 Vue 技术 ...
- 为公司架构一套高质量的 Vue UI 组件库
有没有曾遇过,产品要我们实现一个功能,但是 iview 或者 elementui 不支持,我们然后义正言辞的说,不好意思,组件库不支持,没法做到. 有没有曾和设计师争论得面红耳赤,其实也是因为组件库暂 ...
- wussUI v1.0.0小程序UI组件库 第一期开发已完成
经过了两个月不到的开发时间,我们phonycode团队顺利的发布了小程序的UI组件库 wuss-ui 的第一个版本.目前大体预览如下 介绍 wussUI 现在有大概27个组件左右, 目前基础组件都有了 ...
随机推荐
- 树莓派GPIO开发(一):激光头传感器模块的使用
配置环境 系统:Raspbian11(64位) 设备:树莓派CM4 一.写在前面 主要为了测试我捡漏买的CM4的拓展版 拓展板子没有焊接引脚,但是预留的接口 手动焊接一下 测试成功 ,说明我捡的这块板 ...
- 二,配置jdk,安装tomcat.以及tomcat项目的发布
1.jdk配置 一.环境准备 Windows10 jdk-9.0.1 二.下载并安装JDK 选择一个适合自己的JDK版本下载并安装即可,具体流程不详述. 三.环境变量配置 1.右键桌面上"我 ...
- 数据库delete from和truncate删除的区别详解
一:区别 1.delete from 后面可以直接接条件,truncate不可以 2.delete from 记录是一条条删除的,所删除的每行记录都会进入日志,而truncate一次性删除整个页,因此 ...
- BI工具是什么,有什么用,怎么用?
BI工具是什么,有什么用,怎么用?这应该是很多刚刚接触BI工具的读者最开始想了解的问题,经常在看到数据可视化.数据分析.数据仓库和大数据等词汇时会有的摸不着头脑,本次小编就围绕BI工具是什么,有什么用 ...
- 【译】在 ASP.NET 和 ASP.NET Core 之间共享代码
原文 | Ken 翻译 | 郑子铭 随着 .NET 6 的发布,使用 ASP.NET Core 可以获得更多好处.但是将现有代码迁移到 ASP.NET Core 通常听起来像是一项巨大的投资.今天我们 ...
- idea教程--使用mave创建普通java项目
1.点击箭头所示的"Create New Project"选项 2.勾选图示所示的"Create From Archetype"复选框,在下面的下拉选项中我们选 ...
- vue项目npm run dev 报错error in ./src/main.js Module build failed: ReferenceError: Unknown plugin "transform-vue-jsx" specified in......
问题: vue项目npm run dev运行时报错,如下图: 原因: 缺少组件 解决办法: 安装相应的组件: npm install babel-plugin-transform-vue-jsx ...
- shell 和python 实现ftp文件上传或者下载
一.shell脚本 #####从ftp服务器上的/home/data 到 本地的/home/databackup#####!/bin/bashftp -n<<!open 172.168.1 ...
- Go基础知识梳理(一)
Go基础知识梳理(一) Go中package的用法及作用 package hello 用于分包,Go通过包来管理命名空间 import ( "hello" //通过import关键 ...
- 『现学现忘』Docker基础 — 9、Docker简介
目录 1.什么是Docker? 2.Docker的出现解决了什么问题? 3.Docker的特别之处 4.Docker相关网站 1.什么是Docker? 2010年dotCloud公司在旧金山成立,PA ...