本文作者:GaoNeng
大家好啊,又是我 GaoNeng
。最近在给 OpenTiny 做奉献,感觉renderless
这个架构还是挺有意思的,就奉献了一个 color-picker
组件,简略写篇文章略微记录一下。
也欢送敌人们给 TinyVue 开源我的项目点个 Star 🌟反对下:
https://github.com/opentiny/tiny-vue
浏览完本文,你将会取得如下播种
- HSV,HSL,HEX,RGB 的区别
- HSV 色调空间下,SV 到 XY 的双向转换
- ColorPicker 组件的实现原理
- OpenTiny 新组件开发全流程
1 事件的起因
故事的产生十分的偶尔。我在翻 opentiny 仓库 issue 的时候,偶尔看到了这么一条
之前也在掘金上看过 opentiny 的介绍,感觉还不错,然而又抢不到组件。这一次终于让我抢到一个闲暇组件了,于是我立即就回复了。
2 初步剖析
个别写组件前只思考两个问题
- 长什么样
- 逻辑是什么
color-picker
色彩抉择组件用于在应用程序和界面中让用户抉择色彩。它是一个交互式的元素,通常由一个色调光谱、色相环和色彩值输入框组成,用户能够通过这些元素来抉择所需的色彩。ColorPicker 的次要性能是让用户可能准确地抉择特定的色彩,以便在应用程序的各种元素中应用。
组件成果如下:
ColorPicker 组件次要蕴含四个子组件 饱和度抉择
, 色相抉择
, alpha 抉择
, 工具栏
。比较简单,所以就没画图。次要的问题在于逻辑,也就是抉择什么样的色调空间更贴合用户的日常应用和直观体验。
常见的色调空间分为 HSV
, HSL
, CMY
, CMYK
, HSB
,RGB
, LAB
, YUB
, YCrCb
前端最常见的应该是 HSV
,HSL
,RGB
这三种。LAB
, YUB
, YCrCb
在日常业务中比拟少见。
3 色调空间基础知识
HSV, HSL, HEX, RGB 都是什么呢?
HSV,HSL,RGB 都是色调空间。而 HEX 能够看作是 RGB 的另一种表达方法。
3.1 什么是色调空间?
色调空间是为了让人们更好的意识色调而建设的一种形象的数学模型,它是将数值散布在 N 维的坐标系中,帮忙人们更好地意识和了解色调。
例如 RGB 色调空间,就是将 RGB 重量映射在三维笛卡尔坐标系中。重量的数量代表该重量的亮度值。下图是通过归一解决的 RGB 色调空间示意图
而 HSV 与 HSL 色调空间都是将色彩映射到了柱坐标系。下图展现了 HSV 与 HSL 的示意图
HSV
HSL
3.2 HSV,HSL,RGB 孰优孰劣?
理解了 HSV,HSL,RGB 色调空间及其表达方法,咱们须要思考到底哪一种色调空间对于人类更加的直观呢?要不问问万能的音理吧
啊这,她说不晓得。那看来只能问问万能的 chat-gpt
了
不愧是你,chatgpt 总是能救我于危难之间。不过话又说回来,HSL 与 HSV 都很直观,只是一个是 V(Value)另一个是 L(lightness)。两种色调空间的柱坐标系如下图所示
HSV
HSL
能够看到,HSV 越偏差右上角饱和度和亮度越高。但 HSL 则是偏差于截面的两头饱和度和亮度越高。
在 PS 和其他软件中,也大都抉择了 HSV 作为选色时的色调空间。为了放弃对立,color-picker 组件也抉择了 HSV 作为选色时的色调空间。
3.3 SV 与 XY 的双向转换
饱和度抉择的时候,咱们须要将 XY 重量转为 SV 重量。这存在一种表达方式。SV 与 XY 存在一种计算关系
其中 width 与 height 均为容器的宽度和高度, XY 为光标地位。
4 组件设计
和一般组件开发不同,tinyvue
是将逻辑抽离到了 renderless
下。这样做能够让开发者更着重于逻辑的编写。单测也更好测,测试的时候如果你想,能够只测 renderless 和被形象的逻辑,UI 层面甚至能够意外(因为 UI 次要是各个库来做渲染和依赖跟踪,单测是最小的可测试单元,所以库能够 mock 掉,只测 renderless)。
一个残缺的组件至多要有以下几个因素
- 组件
-
- UI
- 逻辑
- 类型
- 文档
-
- 中文
- 英文
- 测试
-
- 单测
- E2E 测试
4.1 目录梳理
tiny-vue
简化目录如下所示. 带有 !
前缀的文件示意必选,?
前缀的文件示意可选
例如 !index.js
示意 index.js
是必选的。
examples
docs
public
sites
<mpt> app
![component-name]
!webdoc
![component-name].cn.md // 中文文档
![component-name].cn.md // 英文文档
![component-name].js // 组件文档配置
![demo].vue // 示例文件
?[demo].spec.ts // 示例的 e2e 测试
overviewimage // 图标
resource
webdoc // 对应使用指南
config.js
!menu.js // 目录文件,须要在此追加你的组件
packages
renderless
src
![component-name]
?[component-name]
vue.ts
index.ts // 函数形象的中央
vue.ts
index.ts // 函数形象的中央
theme // 桌面端款式
src
?[component-name] // 有些组件不肯定须要款式(例如: config-provider)
index.less // 款式
vars.less // 变量申明
theme-mobile // 挪动端款式
src
?[component-name]
index.less // 款式
vars.less // 变量申明
vue
src
![component-name]
!__tests__
![component-name].spec.vue // 至多要有一个单元测试文件
src
pc.vue // 桌面端模板
?mobile.vue // 挪动端模板, 如果你的组件不须要挪动端那么能够删除
index.ts // 组件导出
package.json
4.2 模块设计
在 tiny-vue
下输出 pnpm create:ui color-picker
就能够创立最根本的模板了。
color-picker
组件次要分为以下几个局部。因为工夫起因,在这里只解说 trigger
与tools
- trigger
- color-select
- sv-select
- hue-select
- alpha-select
- tools
他们的层级关系是这样的
trigger
color-select
sv-select
hue-select
alpha-select
tools
4.3 Props 定义
开发组件,我习惯先思考入参和事件。入参我是设计这样的
javascript
{
modelValue: String, // 默认色彩,不存在即为 transparent
visible: Boolean, // 默认 color-select 是否可见
alpha: Boolean // 是否启用 alpha 抉择
}
事件则是
javascript
{confirm: (hex: string)=>void, // 当用户点击 confirm 时,返回抉择的色彩
cancel: ()=>void // 当用户点击勾销或除了 color-select 子代的 dom 元素时,触发的事件}
设计实现后,咱们就能够开始开发了
5 组件开发
trigger 是 ColorPicker 组件的要害模块,次要管制 color-select
, alpha-select
, tools
的显示状态。
5.1 组件模板开发
咱们先来形容一下 trigger 的状态都有哪些
理顺分明状态后,咱们终于能够开始写第一行代码了
html
<!-- packages/vue/color-picker/src/vue.pc -->
<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>
</div>
<div style="width: 200px;height: 200px;background: #66ccff;" v-if="state.isShow"></div>
</template>
<script>
import {renderless, api} from '@opentiny/vue-renderless/color-picker/vue'
import {props, setup, defineComponent, directive} from '@opentiny/vue-common'
import {IconChevronDown} from '@opentiny/vue-icon'
import Clickoutside from '@opentiny/vue-renderless/common/deps/clickoutside'
export default defineComponent({emits: ['update:modelValue', 'confirm', 'cancel'],
props: [...props, 'modelValue', 'visible', 'alpha'],
components: {IconChevronDown: IconChevronDown(),
},
directives: directive({Clickoutside}),
setup(props, context) {return setup({ props, context, renderless, api})
}
})
</script>
写完上述代码之后,咱们将会取得一个没有交互逻辑的空壳。然而,先别着急,咱们持续写下去
5.2 组件逻辑开发
TinyVue 主打一个关注点拆散,所以这里简略介绍一下 renderless 的大略框架
typescript
export const api = [] // 容许裸露进来的 api
export const renderless = (
props, // 组件的 props
context, // hooks
{emit} // nextTick、attr……
): Record<string,any> => {const api = {};
return api;
}
当初咱们来补充逻辑
typescript
// renderless/src/color-picker/index.ts
import type {Ref} from 'vue';
export const onCancel = (isShow: Ref<boolean>, emit) => {return ()=>{if (isShow.value){emit('cancel')
}
isShow.value = false
}
}
// renderless/src/color-picker/vue.ts
export const api = ['state', 'onCancel'];
export const renderless = (
props,
context,
{emit}
): Record<string,any> => {const { modelValue, visible} = context.toRefs(props)
const isShow = context.ref(visible?.value ?? false)
const triggerBg = context.ref(modelValue.value ?? 'transparent');
context.watch(visible, (visible) => {isShow.value = visible})
const state = {
triggerBg,
isShow
}
const api = {
state,
onCancel: onCancel(isShow, emit)
}
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
分割咱们:
- 官网公众号:
OpenTiny
- OpenTiny 官网:https://opentiny.design/
- OpenTiny 代码仓库: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 站 / 抖音 / 小红书 / 视频号。