本文作者:高能
大家好,我又是高能。最近,我一直在为OpenTiny 做出贡献。我觉得renderless架构还蛮有趣的,所以贡献了一个color-picker组件,简单写了一篇文章记录一下。
也欢迎朋友们给一个star支持TinyVue开源项目:
https://github.com/opentiny/tiny-vue
读完本文,您将获得以下好处
HSV、HSL、HEX 和RGB 之间的区别。从SV到双向转换在opentiny仓库翻issues的时候,无意间看到了这篇文章
之前也在掘金上看到过opentiny的介绍,觉得还不错,就是拿不到组件。这次终于拿到免费组件了,所以我就立即回复了。
1 事情的起因
一般来说,在编写组件之前只需考虑两个问题。
逻辑是什么样的? color-picker 颜色选择组件用于让用户在应用程序和界面中选择颜色。它是一种交互元素,通常由色谱、色相环和颜色值输入框组成,用户可以通过它选择所需的颜色。 ColorPicker 的主要功能是使用户能够精确选择应用程序各个元素中使用的特定颜色。
组件效果如下:
ColorPicker组件主要包含四个子组件:饱和度选择、色调选择、alpha选择和工具栏。比较简单,所以没有画图。主要问题在于逻辑,即选择哪种色彩空间更适合用户的日常使用和直观体验。
常见的色彩空间分为HSV、HSL、CMY、CMYK、HSB、RGB、LAB、YUB、YCrCb
最常见的前端应该是HSV、HSL、RGB。 LAB、YUB、YCrCb在日常业务中比较少见。
2 初步分析
什么是HSV、HSL、HEX 和RGB?
HSV、HSL 和RGB 都是颜色空间。 HEX可以看作是RGB的另一种表达方式。
3 色彩空间基础知识
色彩空间是为了让人们更好地理解颜色而建立的抽象数学模型。它将数值分布在N维坐标系中,帮助人们更好地理解和认识颜色。
例如,RGB 颜色空间将RGB 分量映射到三维笛卡尔坐标系中。分量的数量代表该分量的亮度值。下图是归一化后的RGB颜色空间示意图
HSV 和HSL 颜色空间都将颜色映射到柱坐标系。下图是HSV和HSL的示意图
单纯疱疹病毒
HSL
3.1 什么是色彩空间?
了解了HSV、HSL、RGB色彩空间及其表达方式后,我们需要考虑哪种色彩空间对人类来说更加直观?问问万能的乐理怎么样?
啊,她说她不知道。那么看来唯一的选择就是询问全能的chat-gpt。
不愧是你,chatgpt总能在危难时刻拯救我。不过话又说回来,HSL和HSV都是直观的,只不过一个是V(Value),一个是L(lightness)。两种颜色空间的柱坐标系如下图所示
单纯疱疹病毒
HSL
可以看出,HSV越靠近右上角,饱和度和亮度越高。但HSL往往在截面中间具有较高的饱和度和亮度。
在PS等软件中,选择颜色时大多选择HSV作为颜色空间。为了保持一致性,颜色选择器组件在选择颜色时也选择HSV作为颜色空间。
3.2 HSV,HSL,RGB 孰优孰劣?
选择饱和度时,我们需要将XY分量转换为SV分量。有一种表达方式。 SV和XY之间存在计算关系
其中width和height都是容器的宽度和高度,XY是光标位置。
3.3 SV与XY的双向转换
与普通组件开发不同,tinyvue 将逻辑抽象为无渲染。这样做可以让开发人员更加专注于编写逻辑。单一测试也更容易测试。如果要测试的话,只能测试无渲染和抽象逻辑,甚至可以忽略UI层面(因为UI主要是由各种库来完成渲染和依赖跟踪,而单个测试是最小的可测试单元,因此该库可以被模拟并且只能进行无渲染测试)。
一个完整的组件至少必须具备以下要素:
组件UI逻辑类型文档中文英文测试单测E2E测试
4 组件设计
tiny-vue简化目录如下。文件带有!前缀表示需要选择,文件前缀为?表示可选
例如!index.js 表示需要index.js。
例子
文档
民众
网站
mpt应用程序
![组件名称]
!webdoc
![component-name].cn.md //中文文档
![component-name].cn.md //英文文档
![component-name].js //组件文档配置
![demo].vue //示例文件
?[demo].spec.ts //e2e测试示例
概述图像//图标
资源
webdoc //对应的使用指南
配置文件
!menu.js //目录文件,你需要在这里添加你的组件
包
无渲染
源代码
![组件名称]
?[组件名称]
vue.ts
index.ts //其中函数是抽象的
vue.ts
index.ts //其中函数是抽象的
theme //桌面风格
源代码
?[component-name] //有些组件不一定需要样式(如: config-provider)
index.less //样式
vars.less //变量声明
theme-mobile //移动风格
源代码
?[组件名称]
index.less //样式
vars.less //变量声明
视图
源代码
![组件名称]
!__测试__
![component-name].spec.vue //必须至少有一个单元测试文件
源代码
pc.vue //桌面模板
?mobile.vue //移动端模板,如果你的组件不需要移动端,可以删除
index.ts //组件导出
package.json
4.1 目录梳理
在tiny-vue下输入pnpm create:ui color-picker,创建最基本的模板。
颜色选择器组件主要分为以下几个部分。由于时间原因,这里只讲解触发器和工具。
triggercolor-selectsv-selecthue-selectalpha-selecttools的层级关系如下
扳机
颜色选择
sv-选择
色调选择
阿尔法选择
tools
4.2 模块设计
在开发组件时,我习惯首先考虑参与和事件。我入参的时候是这样设计的
javascript
{
modelValue: String, //默认颜色,不存在则透明
visible: Boolean, //默认颜色选择是否可见?
alpha: Boolean //是否启用alpha选择
}事件是
javascript
{
inform: (hex: string)=void, //当用户点击确认时,返回选择的颜色
cancel: ()=void //当用户点击取消或移除color-select子元素的dom元素时触发的事件
}设计完成后,我们就可以开始开发了
4.3 Props 定义
触发器是ColorPicker组件的关键模块,主要控制颜色选择、alpha选择和工具的显示状态。
5 组件开发
首先描述一下触发器的状态。
整理完状态之后,我们终于可以开始写第一行代码了
html
!--packages/vue/color-picker/src/vue.pc--
模板
div class='tiny-color-picker__trigger' v-clickoutside='onCancel' @click='()=changeVisible(!state.isShow)'
分区
类='tiny-color-picker__inner':style='{
背景:状态.triggerBg ? ”
}'
图标Chevron向下/
/div
/div
div style='width: 200px;height: 200px;background: #66ccff;' v-if='state.isShow'/div
/模板
脚本
从'@opentiny/vue-renderless/color-picker/vue' 导入{ renderless, api }
从'@opentiny/vue-common' 导入{ props, setup, DefineComponent, Directive }
从'@opentiny/vue-icon' 导入{ IconChevronDown }
从'@opentiny/vue-renderless/common/deps/clickoutside' 导入Clickoutside
导出默认定义组件({
发出: ['update:modelValue', '确认', '取消'],
props: [.props, 'modelValue', 'visible', 'alpha'],
组件: {
IconChevronDown: IconChevronDown(),
},
指令:指令({Clickoutside}),
设置(道具,上下文){
返回设置({ props, context, renderless, api })
}
})
/script 写完上面的代码后,我们会得到一个没有交互逻辑的空壳。不过别着急,我们继续写吧
5.1 组件模板开发
TinyVue注重关注点分离,所以这里简单介绍一下renderless的总体框架。
打字稿
export const api=[] //允许暴露的API
导出常量无渲染=(
props, //组件的属性
上下文,//钩子
{ 发出} //nextTick, attr……
): 记录字符串,任意={
常量API={};
返回API;
}现在让我们添加逻辑
打字稿
//无渲染/src/color-picker/index.ts
从'vue' 导入类型{Ref};
导出常量onCancel=(isShow: Refboolean, 发出)={
返回()={
if (isShow.value){
发出('取消')
}
isShow.value=false
}
}
//无渲染/src/color-picker/vue.ts
导出const api=['状态', 'onCancel'];
导出常量无渲染=(
道具,
语境,
{ 发射}
): 记录字符串,任意={
const { modelValue, 可见}=context.toRefs(props)
const isShow=context.ref(visible?value ? false)
const triggerBg=context.ref(modelValue.value ? '透明');
context.watch(可见, (可见)={
isShow.value=可见
})
常量状态={
触发Bg,
是显示
}
常量API={
状态,
onCancel: onCancel(isShow, e
mit)
}
return api;
}补全上述代码后,运行pnpm run dev打开http://localhost:7130/,我们会发现在侧边无法搜索到自己的组件。这是因为menu.js下没有我们的组件,现在我们要开始编写文档
5.3 组件文档
打开 tiny-vue/examples/sites/demos/menus.js 找到 cmpMenus 变量。color-picker应该是算作表单组件,所以我们需要在表单组件的children字段下新增我们的组件
diff
{
'label': '表单组件',
'labelEn': 'Form Components',
'key': 'cmp_form_components',
'children': [
{ 'nameCn': '自动完成', 'name': 'Autocomplete', 'key': 'autocomplete' },
...
+ { 'nameCn': '颜色选择器', 'name': 'ColorPicker', 'key': 'color-picker' }
]
},之后,我们要在demos/app下,新建color-picker文件夹。目录要求如下
![component-name]
!webdoc
![component-name].cn.md // 中文文档
![component-name].cn.md //英文文档
![component-name].js // 组件文档配置
![demo].vue //示例文件
?[demo].spec.ts //示例的e2e测试[component-name].js 该文件主要用于阐述组件props,event,slots等信息。
typescript
export default {
demos: [
{
'demoId': 'demo-id',
'name': { 'zh-CN': '中文名', 'en-US': '英文名' },
'desc': { 'zh-CN': '中文介绍', 'en-US': '英文介绍' },
'codeFiles': ['base.vue']
}
],
apis: [
{
'name': '组件名',
'type': '组件/指令/其他',
'properties': [
{
'name': '名称',
'type': '类型',
'defaultValue': '默认值',
desc: {
'zh-CN': '中文介绍',
'en-US': '英文介绍'
},
demoId: 'demo示例'
},
],
'events': [
{
name: '事件名',
type: '事件类型',
defaultValue: '默认值',
desc: {
'zh-CN': '中文简述',
'en-US': '英文简述'
},
demoId: 'demo示例'
},
],
'slots': [
{
'name': '插槽名',
'type': '类型',
'defaultValue': '默认值',
'desc': { 'zh-CN': '中文简述', 'en-US': '英文简述' },
'demoId': 'demo跳转'
}
]
}
]
}现在我们来补充示例
html
<!-- tiny-vue/examples/sites/demos/app/color-picker/base.vue -->
<template>
<div>
<tiny-color-picker v-model="color" />
</div>
</template>
<script lang="jsx">
import {ColorPicker} from '@opentiny/vue';
export default {
components: {
TinyColorPicker: ColorPicker
}
}
</script>之后,我们运行pnpm dev,打开浏览器http://localhost:7130/pc/color-picker/basic-usage后就可以看到一个刚刚写的示例了
目前还比较简陋,我们可以加入一点样式
5.4 主题变量
因为要适配多套主题,所以我们先来引用一下变量。更多的变量可以在 tiny-vue/packages/theme/src/vars.less 中找到
less
// tiny-vue/packages/theme/src/color-picker/vars.less
.component-css-vars-colorpicker() {
--ti-color-picker-background: var(--ti-common-color-transparent);
--ti-color-picker-border-color: var(--ti-base-color-common-2);
--ti-color-picker-border-weight: var(--ti-common-border-weight-normal);
--ti-color-picker-border-radius-sm: var(--ti-common-border-radius-1);
--ti-color-picker-spacing: var(--ti-common-space-base);
}之后我们就可以愉快的开始写样式了,样式统一都写在 tiny-vue/packages/theme/src/<component-name>/index.less 中,如果单个样式文件过大可以考虑拆分,最好按照 tiny-vue/packages/theme/src/<component-name>/<child-component-name>.less 来进行拆分。color-picker样式不算太大,所以就没做拆分。
less
// tiny-vue/packages/theme/src/color-picker/index.less
@import '../custom.less';
@import './vars.less';
@colorPickerPrefix: ~'@{css-prefix}color-picker';
.@{colorPickerPrefix} {
.component-css-vars-colorpicker();
&__trigger {
position: relative;
width: 32px;
height: 32px;
border-radius: var(--ti-color-picker-border-radius-sm);
border: var(--ti-color-picker-border-weight) solid var(--ti-color-picker-border-color);
box-sizing: content-box;
padding: var(--ti-color-picker-spacing);
cursor: pointer;
.@{colorPickerPrefix}__inner {
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
border-radius: var(--ti-color-picker-border-radius-sm);
background: var(--ti-color-picker-background);
}
}
}
但是目前这样就可以了么?还不行,TinyVue自己做了一套适配层,组件开发时不允许导入Vue,这意味着我们需要自己来写类型
5.4 类型声明
因为我们这里只需要Ref,所以写起来很简单。
typescript
// tiny-vue/packages/renderless/types/color-picker.type.ts
export type IColorPickerRef<T> = {value: T}diff
// tiny-vue/packages/renderless/types/index.ts
export * from './year-table.type'
+export * from './color-picker.type'之后修改renderless/color-picker/index.ts即可
diff
-import type {Ref} from 'vue';
+import {IColorPickerRef as Ref} from '@/types';
5.6 国际化
我们的组件需要进行i18n的处理。因为需要用户自己手动点击确认按钮来确认颜色。但并不是所有用户都是中国人,所以我们要进行i18n的适配。现在我们回到pc.vue增加如下
diff
<template>
<div class="tiny-color-picker__trigger" v-clickoutside="onCancel" @click="() => changeVisible(!state.isShow)">
<div
class="tiny-color-picker__inner" :style="{
background: state.triggerBg ?? ''
}"
>
<IconChevronDown />
</div>
+ <Transition name="tiny-zoom-in-top">
+ <div class="tiny-color-picker__wrapper" @click.stop v-if="state.isShow">
+ <color-select
+ @hue-update="onHueUpdate"
+ @sv-update="onSVUpdate"
+ :color="state.hex"
+ />
+ <alpha-select :color="state.res" @alpha-update="onAlphaUpdate" v-if="alpha" />
+ <div class="tiny-color-picker__wrapper__tools">
+ <tiny-input v-model="state.res" />
+ <tiny-button-group>
+ <tiny-button type="text" @click="onCancel">
+ {{ t('ui.colorPicker.cancel') }}
+ </tiny-button>
+ <tiny-button @click="onConfirm">
+ {{ t('ui.colorPicker.confirm') }}
+ </tiny-button>
+ </tiny-button-group>
+ </div>
+ </div>
+ </Transition>
</div>
</template>
<script>
+import { t } from '@opentiny/vue-locale'
...sv-select、hue-select、alpha-select因为时间原因不做介绍,有兴趣可以自行前往TinyVue仓库观看。
增加完上述代码后,我们需要前往vue-locale/src/lang/zh-CN.ts文件和vue-locale/src/lang/en-US.ts文件添加字段。因为篇幅原因,在这里省略了其他组建的i18字段
typescript
export default {
ui: {
colorPicker:{
cancel: '',
confirm: ''
}
}补充好zh-CN与en-US后,再次返回文档,就可以看到完整的组件了(下图未开启alpha选择)
总结
本文主要介绍了HSV,HSL,RGB色彩空间及其数学表达方法,并分析了SV与二维XY的互相转换原理,最后以 ColorPicker 组件为例子,总结了 tiny-vue 组件开发的流程。
主要包含:
组件模块设计组件 API 定义组件模板和逻辑开发组件文档编写主题变量类型声明组件的国际化如有错漏之处,还望斧正。
OpenTiny 社区招募贡献者啦
OpenTiny Vue 正在招募社区贡献者,欢迎加入我们🎉
你可以通过以下方式参与贡献:
在 issue 列表中选择自己喜欢的任务阅读贡献者指南,开始参与贡献你可以根据自己的喜好认领以下类型的任务:
编写单元测试修复组件缺陷为组件添加新特性完善组件的文档如何贡献单元测试:
在packages/vue目录下搜索it.todo关键字,找到待补充的单元测试按照以上指南编写组件单元测试执行单个组件的单元测试:pnpm test:unit3 button如果你是一位经验丰富的开发者,想接受一些有挑战的任务,可以考虑以下任务:
✨ [Feature]: 希望提供 Skeleton 骨架屏组件✨ [Feature]: 希望提供 Divider 分割线组件✨ [Feature]: tree树形控件能增加虚拟滚动功能✨ [Feature]: 增加视频播放组件✨ [Feature]: 增加思维导图组件✨ [Feature]: 添加类似飞书的多维表格组件✨ [Feature]: 添加到 unplugin-vue-components✨ [Feature]: 兼容formily参与 OpenTiny 开源社区贡献,你将收获:
直接的价值:
通过参与一个实际的跨端、跨框架组件库项目,学习最新的Vite+Vue3+TypeScript+Vitest技术学习从 0 到 1 搭建一个自己的组件库的整套流程和方法论,包括组件库工程化、组件的设计和开发等为自己的简历和职业生涯添彩,参与过优秀的开源项目,这本身就是受面试官青睐的亮点结识一群优秀的、热爱学习、热爱开源的小伙伴,大家一起打造一个伟大的产品长远的价值:
打造个人品牌,提升个人影响力培养良好的编码习惯获得华为云 OpenTiny 团队的荣誉和定制小礼物受邀参加各类技术大会成为 PMC 和 Committer 之后还能参与 OpenTiny 整个开源生态的决策和长远规划,培养自己的管理和规划能力未来有更多机会和可能
关于 OpenTiny
OpenTiny 是一套企业级组件库解决方案,适配 PC 端 / 移动端等多端,涵盖 Vue2 / Vue3 / Angular 多技术栈,拥有主题配置系统 / 中后台模板 / CLI 命令行等效率提升工具,可帮助开发者高效开发 Web 应用。
核心亮点:
跨端跨框架:使用 Renderless 无渲染组件设计架构,实现了一套代码同时支持 Vue2 / Vue3,PC / Mobile 端,并支持函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强。组件丰富:PC 端有100+组件,移动端有30+组件,包含高频组件 Table、Tree、Select 等,内置虚拟滚动,保证大数据场景下的流畅体验,除了业界常见组件之外,我们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP地址输入框、Calendar 日历、Crop 图片裁切等配置式组件:组件支持模板式和配置式两种使用方式,适合低代码平台,目前团队已经将 OpenTiny 集成到内部的低代码平台,针对低码平台做了大量优化周边生态齐全:提供了基于 Angular + TypeScript 的 TinyNG 组件库,提供包含 10+ 实用功能、20+ 典型页面的 TinyPro 中后台模板,提供覆盖前端开发全流程的 TinyCLI 工程化工具,提供强大的在线主题配置平台 TinyTheme联系我们:
官方公众号:OpenTinyOpenTiny 代码仓库:https://github.com/opentiny/Vue 组件库:https://github.com/opentiny/tiny-vue (欢迎 Star)Angluar组件库:https://github.com/opentiny/ng (欢迎 Star)CLI工具:https://github.com/opentiny/tiny-cli (欢迎 Star)更多视频内容也可以关注OpenTiny社区,B站/抖音/小红书/视频号。