- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文共10076字,预计阅读20分钟 。
大家好啊,又是我 GaoNeng 。最近在给OpenTiny做贡献,感觉 renderless 这个架构还是挺有意思的,就贡献了一个 color-picker 组件,简单写篇文章稍微记录一下.
也欢迎朋友们给 TinyVue 开源项目点个 Star 🌟支持下:
https://github.com/opentiny/tiny-vue 。
阅读完本文,你将会获得如下收获 。
故事的发生非常的偶然。我在翻opentiny仓库issue的时候,偶然看到了这么一条 。
之前也在掘金上看过opentiny的介绍,感觉还不错,但是又抢不到组件。这一次终于让我抢到一个空闲组件了,于是我立刻就回复了.
一般写组件前只考虑两个问题 。
color-picker 颜色选择组件用于在应用程序和界面中让用户选择颜色。它是一个交互式的元素,通常由一个色彩光谱、色相环和颜色值输入框组成,用户可以通过这些元素来选择所需的颜色。ColorPicker的主要功能是让用户能够精确地选择特定的颜色,以便在应用程序的各种元素中使用.
ColorPicker 组件主要包含四个子组件 饱和度选择 , 色相选择 , alpha选择 , 工具栏 。比较简单,所以就没画图。主要的问题在于逻辑,也就是选择什么样的色彩空间更贴合用户的日常使用和直观体验.
常见的色彩空间分为 HSV , HSL , CMY , CMYK , HSB , RGB , LAB , YUB , YCrCb 。
前端最常见的应该是 HSV , HSL , RGB 这三种。 LAB , YUB , YCrCb 在日常业务中比较少见.
HSV, HSL, HEX, RGB 都是什么呢?
HSV,HSL,RGB都是色彩空间。而HEX可以看作是RGB的另一种表达方法.
色彩空间是为了让人们更好的认识色彩而建立的一种抽象的数学模型,它是将数值分布在N维的坐标系中,帮助人们更好地认识和理解色彩.
例如RGB色彩空间,就是将RGB分量映射在三维笛卡尔坐标系中。分量的数量代表该分量的亮度值。下图是经过归一处理的RGB色彩空间示意图 。
而HSV与HSL色彩空间都是将颜色映射到了柱坐标系。下图展示了HSV与HSL的示意图 。
HSV 。
HSL 。
了解了HSV,HSL,RGB色彩空间及其表达方法,我们需要考虑究竟哪一种色彩空间对于人类更加的直观呢?要不问问万能的音理吧 。
啊这,她说不知道。那看来只能问问万能的 chat-gpt 了 。
不愧是你,chatgpt总是能救我于危难之间。不过话又说回来,HSL与HSV都很直观,只是一个是V(Value)另一个是L(lightness)。两种色彩空间的柱坐标系如下图所示 。
HSV 。
HSL 。
可以看到,HSV越偏向右上角饱和度和亮度越高。但HSL则是偏向于截面的中间饱和度和亮度越高.
在PS和其他软件中,也大都选择了HSV作为选色时的色彩空间。为了保持统一,color-picker组件也选择了HSV作为选色时的色彩空间.
饱和度选择的时候,我们需要将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为光标位置.
和普通组件开发不同, tinyvue 是将逻辑抽离到了 renderless 下。这样做可以让开发者更着重于逻辑的编写。单测也更好测,测试的时候如果你想,可以只测renderless和被抽象的逻辑,UI层面甚至可以不测(因为UI主要是各个库来做渲染和依赖跟踪,单测是最小的可测试单元,所以库可以mock掉,只测renderless).
一个完整的组件至少要有以下几个要素 。
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
在 tiny-vue 下输入 pnpm create:ui color-picker 就可以创建最基本的模板了.
color-picker 组件主要分为以下几个部分。因为时间原因,在这里只讲解 trigger 与 tools 。
他们的层级关系是这样的 。
trigger
color-select
sv-select
hue-select
alpha-select
tools
开发组件,我习惯先思考入参和事件。入参我是设计这样的 。
{
modelValue: String, // 默认颜色,不存在即为transparent
visible: Boolean, // 默认color-select是否可见
alpha: Boolean // 是否启用alpha选择
}
事件则是 。
{
confirm: (hex: string)=>void, // 当用户点击confirm时,返回选择的颜色
cancel: ()=>void // 当用户点击取消或除了color-select子代的dom元素时,触发的事件
}
设计完成后,我们就可以开始开发了 。
trigger是ColorPicker组件的关键模块,主要控制 color-select , alpha-select , tools 的显示状态.
我们先来描述一下trigger的状态都有哪些 。
理顺清楚状态后,我们终于可以开始写第一行代码了 。
<!-- 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>
写完上述代码之后,我们将会获得一个没有交互逻辑的空壳。但是,先别着急,我们继续写下去 。
TinyVue主打一个关注点分离,所以这里简单介绍一下renderless的大概框架 。
export const api = [] // 允许暴露出去的api
export const renderless = (
props, //组件的props
context, // hooks
{ emit } // nextTick、attr……
): Record<string,any> => {
const api = {};
return api;
}
现在我们来补充逻辑 。
// 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 下没有我们的组件,现在我们要开始编写文档 。
打开 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 后就可以看到一个刚刚写的示例了 。
目前还比较简陋,我们可以加入一点样式 。
因为要适配多套主题,所以我们先来引用一下变量。更多的变量可以在 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,这意味着我们需要自己来写类型 。
因为我们这里只需要Ref,所以写起来很简单.
// tiny-vue/packages/renderless/types/color-picker.type.ts
export type IColorPickerRef<T> = {value: T}
// tiny-vue/packages/renderless/types/index.ts
export * 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';
我们的组件需要进行 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-select 、 hue-select 、 alpha-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选择) 。
本文主要介绍了HSV,HSL,RGB色彩空间及其数学表达方法,并分析了SV与二维XY的互相转换原理,最后以 ColorPicker 组件为例子,总结了 tiny-vue 组件开发的流程.
主要包含:
如有错漏之处,还望斧正.
OpenTiny Vue 正在招募社区贡献者,欢迎加入我们🎉 。
你可以通过以下方式参与贡献:
你可以根据自己的喜好认领以下类型的任务:
如何贡献单元测试:
packages/vue
目录下搜索 it.todo
关键字,找到待补充的单元测试 pnpm test:unit3 button
如果你是一位经验丰富的开发者,想接受一些有挑战的任务,可以考虑以下任务:
参与 OpenTiny 开源社区贡献,你将收获:
直接的价值:
Vite
+ Vue3
+ TypeScript
+ Vitest
技术 长远的价值:
OpenTiny 是一套华为云出品的企业级组件库解决方案,适配 PC 端 / 移动端等多端,涵盖 Vue2 / Vue3 / Angular 多技术栈,拥有主题配置系统 / 中后台模板 / CLI 命令行等效率提升工具,可帮助开发者高效开发 Web 应用.
核心亮点:
跨端跨框架
:使用 Renderless 无渲染组件设计架构,实现了一套代码同时支持 Vue2 / Vue3,适配 PC / Mobile 端,并支持函数级别的逻辑定制和全模板替换,灵活性好、二次开发能力强。 组件丰富
:PC 端有103个组件,移动端有35个组件,包含高频组件 Table、Tree、Select 等,内置虚拟滚动,保证大数据场景下的流畅体验,除了业界常见组件之外,我们还提供了一些独有的特色组件,如:Split 面板分割器、IpAddress IP地址输入框、Calendar 日历、Crop 图片裁切等 配置式组件
:组件支持模板式和配置式两种使用方式,适合低代码平台,目前团队已经将 OpenTiny 集成到内部的低代码平台,针对低码平台做了大量优化 周边生态齐全
:提供了基于 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 ⭐) 。
往期文章推荐 。
最后此篇关于GaoNeng:我是如何为OpenTiny贡献新组件的?的文章就讲到这里了,如果你想了解更多关于GaoNeng:我是如何为OpenTiny贡献新组件的?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我错过了什么,我已完成 的安装指南中要求的所有步骤 native 脚本 运行 tns doctor 给我以下输出... C:\abc\xyz>tns doctor √ Getting environm
尝试从 {addToCart(book)}}/>}> 传递数据至}> 问题: 购物车 ( render={()=> ) 收到 null,但没有收到我尝试发送的对象 已放置“console.log...
这是 _app.tsx 的外观: function MyApp({ Component, pageProps }: AppProps) { return } 我在构建项目时遇到了这个错误: Ty
我的 Laravel Vue 组件收到以下警告: [Vue warn]: Avoid mutating a prop directly since the value will be overwrit
根据这个example更详细this one我刚刚遇到了一件奇怪的事情...... 如果我使用方法作为 addTab(title,icon,component) 并且下一步想使用 setTabComp
目前我有一个捕获登录数据的表单,一个带有 TIWDBGrid 的表单,它应该返回与我从我的 mysql 数据库登录时创建的 user_id 关联的任何主机,以及一个共享数据模块。 下面是我的登录页面代
在我的react-native应用程序中,我目前有一个本地Android View (用java编写)正确渲染。当我尝试将我的react-native javascript 组件之一放入其中时,出现以
我为作业编写了简单的代码。我引用了文档和几个 youtube 视频教程系列。我的 react 代码是正确的我在运行代码时没有收到任何错误。但是这些 react-boostrap 元素没有渲染。此代码仅
几周前我刚刚开始使用 Flow,从一周前开始我就遇到了 Flow 错误,我不知道如何修复。 代码如下: // @flow import React, { Component } from "react
我想在同一个 View 中加载不同的 web2py 组件,但不是同时加载。我有 5 个 .load 文件,它们具有用于不同场景的表单字段,这些文件由 onchange 选择脚本动态调用。 web2py
关闭。这个问题是opinion-based .它目前不接受答案。 想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文回答问题. 6年前关闭。 Improve t
Blazor 有 InputNumber将输入限制为数字的组件。然而,这呈现了一个 firefox 不尊重(它允许任何文本)。 所以我尝试创建一个过滤输入的自定义组件: @inherits Inpu
我在学习 AngularDART 组件时编写了以下简单代码,但没有显示任何内容,任何人都可以帮助我知道我犯了什么错误: 我的 html 主文件:
我想在初始安装组件时或之后为 div 设置动画(淡入)。动画完成后,div 不应消失。我正在尝试使用 CSSTransition 组件并查看 reactcommunity.org 上的示例,但我根本无
我需要一个 JSF 组件来表示甘特图。是否有任何组件库(如 RichFaces)包含这样的组件? 最佳答案 JFreeChart有甘特图和PrimeFaces有一个图像组件,允许您动态地流式传输内容。
从软件工程的角度来看,组件、模块和子系统之间有什么区别? 提前致谢! 最佳答案 以下是 UML 2.5 的一些发现: 组件:该子句指定一组结构,可用于定义任意大小和复杂性的软件系统。特别是,它将组件指
我有使用非托管程序集(名为 unmanaged.dll)的托管应用程序(名为 managed.exe)。到目前为止,我们已经创建了 Interop.unmanaged.dll,managed.exe
我有一个跨多个应用程序复制的 DAL(我知道它的设计很糟糕,但现在忽略它),我想做的是这个...... 创建一个将通过所有桌面应用程序访问的 WCF DAL 组件。任何人都可以分享他们对关注的想法吗?
我有一个 ComboBox 的集合声明如下。 val cmbAll = for (i /** action here **/ } 所有这些都放在一个 TabbedPane 中。我想这不是问题。那么我
使用 VB6 创建一个 VB 应用程序。应用程序的一部分显示内部的闪存。 当我使用 printform它只是打印整个应用程序。我不知道如何单独打印闪光部分。任何帮助,将不胜感激!.. 谢谢。 最佳答案
我是一名优秀的程序员,十分优秀!