开发背景

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.scssvariables-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 在线主题定制功能探索的更多相关文章

  1. 微信小程序一键生成源码 在线制作定制功能强大的微信小程序

    微信小程序发展到现在,短短的一年不到的时间(很快就要迎来微信小程序周年庆),在快迎来周年庆之际,百牛信息技术bainiu.ltd特记录一下这个发展的历程,用于将来见证小程序发展的辉煌时刻,我们还能知道 ...

  2. 小技巧!CSS 提取图片主题色功能探索

    本文将介绍一种利用 CSS 获取图片主题色的小技巧.一起看看~ 背景 起因是微信技术群里有个同学发问,有什么方法能够获取图片的主色呢?有一张图片,获取他的主色调: 利用获取到的这个颜色值,来实现类似这 ...

  3. beeshell —— 开源的 React Native 组件库

    介绍 beeshell 是一个 React Native 应用的基础组件库,基于 0.53.3 版本,提供一整套开箱即用的高质量组件,包含 JavaScript(以下简称 JS)组件和复合组件(包含 ...

  4. 滴滴开源 Vue 组件库— cube-ui

    cube-ui 是滴滴去年底开源的一款基于 Vue.js 2.0 的移动端组件库,主要核心目标是做到体验极致.灵活性强.易扩展以及提供良好的周边生态-后编译. 自 17 年 11 月开源至今已有 5 ...

  5. HslCommunication组件库使用说明 (转载)

    一个由个人开发的组件库,携带了一些众多的功能,包含了数据网络通信,文件上传下载,日志组件,PLC访问类,还有一些其他的基础类库. nuget地址:https://www.nuget.org/packa ...

  6. HslCommunication组件库使用说明

    一个由个人开发的组件库,携带了一些众多的功能,包含了数据网络通信,文件上传下载,日志组件,PLC访问类,还有一些其他的基础类库. nuget地址:https://www.nuget.org/packa ...

  7. 如何快速构建React组件库

    前言 俗话说:"麻雀虽小,五脏俱全",搭建一个组件库,知之非难,行之不易,涉及到的技术方方面面,犹如海面风平浪静,实则暗礁险滩,处处惊险- 目前团队内已经有较为成熟的 Vue 技术 ...

  8. 为公司架构一套高质量的 Vue UI 组件库

    有没有曾遇过,产品要我们实现一个功能,但是 iview 或者 elementui 不支持,我们然后义正言辞的说,不好意思,组件库不支持,没法做到. 有没有曾和设计师争论得面红耳赤,其实也是因为组件库暂 ...

  9. wussUI v1.0.0小程序UI组件库 第一期开发已完成

    经过了两个月不到的开发时间,我们phonycode团队顺利的发布了小程序的UI组件库 wuss-ui 的第一个版本.目前大体预览如下 介绍 wussUI 现在有大概27个组件左右, 目前基础组件都有了 ...

随机推荐

  1. MySQL架构原理之存储引擎InnoDB线程模型

    如下图示,为InnoDB线程模型示意图: 1.IO Thread 在InnoDB中使用了大量的AIO(Async IO)来做读写处理,这样可以极大提高数据库的性能.其提供了write/read/ins ...

  2. transient关键字有何作用

    使用对象流保存对象时,将对象的全部信息都保存了,但是有些信息是不希望保存,如密码,该如何避免该信息的保存? 使用transient关键字修饰的属性,在保存对象时,该属性并不会被保存. transien ...

  3. [题解]Codeforces Round #519 - A. Elections

    [题目] A. Elections [描述] Awruk和Elodreip参加选举,n个人投票,每个人有k张票,第i个人投a[i]张票给Elodreip,投k-a[i]张票给Awruk.求最小的k,使 ...

  4. [旧][Android] Retrofit 源码分析之 Retrofit 对象

    备注 原发表于2016.04.27,资料已过时,仅作备份,谨慎参考 前言 在上一周学习了一下 Retrofit 的执行流程.接下来的文章要更为深入地学习 Retrofit 的各个类,这次我们先学习一下 ...

  5. Renix修改报文长度——网络测试仪实操

    Renix软件修改报文长度的方式有4种,分别是固定.递增.随机和自动.接下来对这四种方式,分别配置和验证. 一.固定(fixed) 描述:流中的帧具有固定长度 1.配置fixed 64Byte 2.w ...

  6. RENIX报文字段跳变——网络测试仪实操

    什么是报文字段跳变? 报文字段跳变是指字段的值进行一些列有规则的变化,Renix支持对字段进行递增.递减.列表和随机变化. 如当用户想要仿真大量的源IP变化的数据时,就可以使用Modifier进行规则 ...

  7. 三大流行BI分析平台推荐,企业数据化选择工具

    进入大数据时代以来,对于企业来说,海量的数据不仅是财富,也是负担.无论是大型企业还是小型企业,都面临着同样的挑战--如何利用大数据客户体验,有效达到优化生产力的效果.这也是近年来许多企业选择搭建现代大 ...

  8. 【C# 异常处理】 开端

    异常概述 在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是 ...

  9. npm install 报错:command failed git -c core.longpaths

    最近需要angularjs,从github上下载下来程序,在安装目录下执行命令 npm install(安装依赖包)时报错了.

  10. Git——版本控制器概述

    一.版本控制 版本控制(Revision contontrol)是一种在开发过程中用于管理修改历史,方便查看更改历史记录,备份以便恢复以前版本的软件工程的技术. 1.实现跨区域多人协同开发 2.追踪和 ...