本文共10076字,预计浏览20分钟

大家好啊,又是我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 初步剖析

个别写组件前只思考两个问题

  1. 长什么样
  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的示意图

<center>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2baa31a6cd3440da9f87c82d0d924115~tplv-k3u1fbpfcp-watermark.image?" style="zoom: .5" alt="HSV">
<p>HSV</p>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/05c3c41894ee48dfa9e6908befbbfbea~tplv-k3u1fbpfcp-watermark.image?" style="zoom: .5" alt="HSL"></center>
<p>HSL</p>
</center>

3.2 HSV,HSL,RGB 孰优孰劣?

理解了HSV,HSL,RGB色调空间及其表达方法,咱们须要思考到底哪一种色调空间对于人类更加的直观呢?要不问问万能的音理吧

<center>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1463fe17d6d84bf88a361c72bf3a83de~tplv-k3u1fbpfcp-watermark.image?" style="zoom:0.5;" alt="让音理通知你吧">
</center>

啊这,她说不晓得。那看来只能问问万能的chat-gpt

不愧是你,chatgpt总是能救我于危难之间。不过话又说回来,HSL与HSV都很直观,只是一个是V(Value)另一个是L(lightness)。两种色调空间的柱坐标系如下图所示

<center>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2baa31a6cd3440da9f87c82d0d924115~tplv-k3u1fbpfcp-watermark.image?" style="zoom: .5" alt="HSV">
<p>HSV</p>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/05c3c41894ee48dfa9e6908befbbfbea~tplv-k3u1fbpfcp-watermark.image?" style="zoom: .5" alt="HSL"></center>
<p>HSL</p>
</center>

能够看到,HSV越偏差右上角饱和度和亮度越高。但HSL则是偏差于截面的两头饱和度和亮度越高。

在PS和其他软件中,也大都抉择了HSV作为选色时的色调空间。为了放弃对立,color-picker组件也抉择了HSV作为选色时的色调空间。

3.3 SV与XY的双向转换

饱和度抉择的时候,咱们须要将XY重量转为SV重量。这存在一种表达方式。SV与XY存在一种计算关系

$$\begin{cases} S=\frac{x}{width} \times 100& S \in [0,100]\\ V=100 - \frac{y}{height} \times 100& V\in [0,100] \\ X=\max(\frac{(S \times width)}{100}, 0)& X \in [0,width] \\ Y=\max(\frac{(V \times height)}{100},0)& Y \in [0,heght]\end{cases}$$

其中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 组件次要分为以下几个局部。因为工夫起因,在这里只解说triggertools

  • trigger
  • color-select
  • sv-select
  • hue-select
  • alpha-select
  • tools

他们的层级关系是这样的

trigger    color-select        sv-select        hue-select    alpha-select    tools

4.3 Props 定义

开发组件,我习惯先思考入参和事件。入参我是设计这样的

{    modelValue: String, // 默认色彩,不存在即为transparent    visible: Boolean, // 默认color-select是否可见    alpha: Boolean // 是否启用alpha抉择}

事件则是

{    confirm: (hex: string)=>void, // 当用户点击confirm时,返回抉择的色彩    cancel: ()=>void // 当用户点击勾销或除了color-select子代的dom元素时,触发的事件}

设计实现后,咱们就能够开始开发了

5 组件开发

trigger是ColorPicker组件的要害模块,次要管制color-select, alpha-select, tools的显示状态。

5.1 组件模板开发

咱们先来形容一下trigger的状态都有哪些

---title: color-select state---stateDiagram-v2  [*] --> visible  [*] --> click  visible --> show: true  visible --> hidden: false  click --> show: trigger  click --> hidden: outside

理顺分明状态后,咱们终于能够开始写第一行代码了

<!-- 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的大略框架

export const api = [] // 容许裸露进来的apiexport const renderless = (  props, //组件的props  context, // hooks  { emit } // nextTick、attr……): Record<string,any> => {    const api = {};    return api;}

当初咱们来补充逻辑

// renderless/src/color-picker/index.tsimport 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.tsexport 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字段下新增咱们的组件

  {    '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等信息。

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跳转'        }      ]    }  ]}

当初咱们来补充示例

<!-- 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后就能够看到一个刚刚写的示例了

<center>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e05d2519d501471f921f97e66f72a7e8~tplv-k3u1fbpfcp-watermark.image?" alt="ColorPicker成果">
</center>

目前还比拟简陋,咱们能够退出一点款式

5.4 主题变量

因为要适配多套主题,所以咱们先来援用一下变量。更多的变量能够在 tiny-vue/packages/theme/src/vars.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款式不算太大,所以就没做拆分。

// 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,所以写起来很简略。

// tiny-vue/packages/renderless/types/color-picker.type.tsexport type IColorPickerRef<T> = {value: T}
// tiny-vue/packages/renderless/types/index.tsexport * from './year-table.type'+export * from './color-picker.type'

之后批改renderless/color-picker/index.ts即可

-import type {Ref} from 'vue';+import {IColorPickerRef as Ref} from '@/types';

5.6 国际化

咱们的组件须要进行i18n的解决。因为须要用户本人手动点击确认按钮来确认色彩。但并不是所有用户都是中国人,所以咱们要进行i18n的适配。当初咱们回到pc.vue减少如下

<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-selecthue-selectalpha-select因为工夫起因不做介绍,有趣味能够自行返回TinyVue仓库观看。

减少完上述代码后,咱们须要返回vue-locale/src/lang/zh-CN.ts文件和vue-locale/src/lang/en-US.ts文件增加字段。因为篇幅起因,在这里省略了其余组建的i18字段

export default {  ui: {    colorPicker:{        cancel: '',        confirm: ''    }  }

补充好zh-CN与en-US后,再次返回文档,就能够看到残缺的组件了(下图未开启alpha抉择)

<center>
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15928dd10d31408cb9a13d9e6fc93276~tplv-k3u1fbpfcp-watermark.image?" alt="ColorPicker成果">
</center>

总结

本文次要介绍了HSV,HSL,RGB色调空间及其数学表达方法,并剖析了SV与二维XY的相互转换原理,最初以 ColorPicker 组件为例子,总结了 tiny-vue 组件开发的流程。

次要蕴含:

  1. 组件模块设计
  2. 组件 API 定义
  3. 组件模板和逻辑开发
  4. 组件文档编写
  5. 主题变量
  6. 类型申明
  7. 组件的国际化

如有错漏之处,还望斧正。

OpenTiny 社区招募贡献者啦

OpenTiny Vue 正在招募社区贡献者,欢送退出咱们

你能够通过以下形式参加奉献:

  • 在 issue 列表中抉择本人喜爱的工作
  • 浏览贡献者指南,开始参加奉献

你能够依据本人的爱好认领以下类型的工作:

  • 编写单元测试
  • 修复组件缺点
  • 为组件增加新个性
  • 欠缺组件的文档

如何奉献单元测试:

  • packages/vue目录下搜寻it.todo关键字,找到待补充的单元测试
  • 依照以上指南编写组件单元测试
  • 执行单个组件的单元测试:pnpm test:unit3 button

如果你是一位经验丰富的开发者,想承受一些有挑战的工作,能够思考以下工作:

参加 OpenTiny 开源社区奉献,你将播种:

间接的价值:

  1. 通过参加一个理论的跨端、跨框架组件库我的项目,学习最新的Vite+Vue3+TypeScript+Vitest技术
  2. 学习从 0 到 1 搭建一个本人的组件库的整套流程和方法论,包含组件库工程化、组件的设计和开发等
  3. 为本人的简历和职业生涯添彩,参加过优良的开源我的项目,这自身就是受面试官青眼的亮点
  4. 结识一群优良的、酷爱学习、酷爱开源的小伙伴,大家一起打造一个平凡的产品

久远的价值:

  1. 打造集体品牌,晋升集体影响力
  2. 造就良好的编码习惯
  3. 取得华为云 OpenTiny 团队的荣誉和定制小礼物
  4. 受邀加入各类技术大会
  5. 成为 PMC 和 Committer 之后还能参加 OpenTiny 整个开源生态的决策和长远规划,造就本人的治理和布局能力
  6. 将来有更多机会和可能

对于 OpenTiny

OpenTiny 是一套华为云出品的企业级组件库解决方案,适配 PC 端 / 挪动端等多端,涵盖 Vue2 / Vue3 / Angular 多技术栈,领有主题配置零碎 / 中后盾模板 / CLI 命令行等效率晋升工具,可帮忙开发者高效开发 Web 利用。

外围亮点:

  1. 跨端跨框架:应用 Renderless 无渲染组件设计架构,实现了一套代码同时反对 Vue2 / Vue3,适配 PC / Mobile 端,并反对函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强。
  2. 组件丰盛:PC 端有103个组件,挪动端有35个组件,蕴含高频组件 Table、Tree、Select 等,内置虚构滚动,保障大数据场景下的晦涩体验,除了业界常见组件之外,咱们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP地址输入框、Calendar 日历、Crop 图片裁切等
  3. 配置式组件:组件反对模板式和配置式两种应用形式,适宜低代码平台,目前团队曾经将 OpenTiny 集成到外部的低代码平台,针对低码平台做了大量优化
  4. 周边生态齐全:提供了基于 Angular + TypeScript 的 TinyNG 组件库,提供蕴含 10+ 实用功能、20+ 典型页面的 TinyPro 中后盾模板,提供笼罩前端开发全流程的 TinyCLI 工程化工具,提供弱小的在线主题配置平台 TinyTheme

欢送退出 OpenTiny 开源社区。

增加微信小助手:opentiny-official,一起参加共建!

OpenTiny 官网:https://opentiny.design/

Vue组件库:https://opentiny.design/tiny-vue

Angular组件库:https://opentiny.design/tiny-ng

OpenTiny 代码仓库:https://github.com/opentiny/ (欢送 Star ⭐)

往期文章举荐

  • OpenTiny Vue 3.10.0 版本公布:组件 Demo 反对 Composition 写法,新增4个新组件
  • 但因酷爱,愿迎万难,OpenTiny 社区减少一枚前端程序媛贡献者
  • OpenTiny 前端组件库正式开源啦!面向未来,为开发者而生
  • 前端Vuer,请收好这份《Vue组件单元测试》宝典,给本人多一些安全感
  • OpenTiny Vue 3.9.0 版本公布:新增3个新组件、反对 SSR
  • OpenTiny Vue 3.8.0 正式公布:推出「极客黑」新主题!
  • 应用 TinyCLI 两行命令创立一个好看大气的 Admin 零碎
  • 一个 OpenTiny,Vue2 Vue3 都反对!
  • 历史性的时刻!OpenTiny 跨端、跨框架组件库正式降级 TypeScript,10 万行代码重获新生!