热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

提升组件库通用能力–NutUI在线主题定制功能探索

开发背景NutUI作为京东风格的组件库,已具备H5和多端小程序开发能力。随着业务的不断发展,组件库的应用场景越来越广。在公司内外面临诸如科技、金融、物流等各多个大型团队使用时,单一

开发背景

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 的「主题定制」和「组件级样式定制」功能实现机制。「主题定制」能实现简单的颜色切换,「组件级样式定制」功能更强大,通过将组件的样式变量暴露出来开发者几乎可以任意修改自己想要的设计风格(组件尺寸、字体、边距)。通过强大的主题定制可以让组件库的使用不局限于原设计者的设计范畴,可灵活扩展组件,让组件库的应用范围更广,能满足更广泛的业务场景。

期待您的使用与反馈 ❤️~



推荐阅读
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了如何使用vue-awesome-swiper组件,包括在main.js中引入和使用swiper和swiperSlide组件,以及设置options和ref属性。同时还介绍了如何在模板中使用swiper和swiperSlide组件,并展示了如何通过循环渲染swipes数组中的数据,并使用picUrl属性显示图片。最后还介绍了如何添加分页器。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了为什么在main.js中写import不会全局生效的问题,并提供了解决方案。在每一个vue文件中都需要写import语句才能使其生效,而在main.js中写import语句则不会全局生效。本文还介绍了使用Swal和sweetalert2库的示例。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • web.py开发web 第八章 Formalchemy 服务端验证方法
    本文介绍了在web.py开发中使用Formalchemy进行服务端表单数据验证的方法。以User表单为例,详细说明了对各字段的验证要求,包括必填、长度限制、唯一性等。同时介绍了如何自定义验证方法来实现验证唯一性和两个密码是否相等的功能。该文提供了相关代码示例。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
author-avatar
king_her灬o1
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有