一、组件介绍
官网链接:ColorPicker 颜色选择器 | Element (gitee.io)。
ColorPicker颜色选择器组件,用于颜色选择,支持多种颜色格式。
1.1 属性
- modelValue/v-model: string类型,绑定的颜色值;
- color-format: string类型,颜色值的格式,如
hsl / hsv / hex / rgb; - show-alpha:boolean类型,是否支持透明度选择,若为true,则展示一个透明度的滑动选择条,默认为false;
- predefine: array类型,预定义颜色列表,默认为空;
- size: string类型,尺寸,可选值
medium/small/mini; - disabled: boolean类型,是否禁用,默认false;
- popper-class: string类型,下拉弹框的类名;
1.2 事件
- change:颜色值变化时触发
二、源码分析
2.1 template
<template>
<!-- 使用el-popper组件展示弹出框内容 -->
<el-popper
ref="popper"
v-model:visible="showPicker"
effect="light"
manual-mode
trigger="click"
:show-arrow="false"
:fallback-placements="['bottom', 'top', 'right', 'left']"
:offset="0"
transition="el-zoom-in-top"
:gpu-acceleration="false"
:popper-class="`el-color-picker__panel el-color-dropdown ${popperClass}`"
:stop-popper-mouse-event="false"
>
<!-- 弹出内容 -->
<template #default>
<!-- click-outside指令,点击外部区域时,关闭弹出内容 -->
<div v-click-outside="hide">
<div class="el-color-dropdown__main-wrapper">
<!-- 颜色滑动条,纵向 -->
<!-- 注意,color属性并不是一个颜色值,而是一个Color的实例 -->
<hue-slider ref="hue" class="hue-slider" :color="color" vertical />
<!-- 颜色选择板,矩形 -->
<sv-panel ref="svPanel" :color="color" />
</div>
<!-- 透明度滑动条,横向 -->
<alpha-slider v-if="showAlpha" ref="alpha" :color="color" />
<!-- 预定义颜色块 -->
<predefine v-if="predefine" ref="predefine" :color="color" :colors="predefine" />
<!-- 色值输入框,确认/清除按钮 -->
<div class="el-color-dropdown__btns">
<span class="el-color-dropdown__value">
<el-input
v-model="customInput"
:validate-event="false"
size="mini"
@keyup.enter="handleConfirm"
@blur="handleConfirm"
/>
</span>
<el-button
size="mini"
type="text"
class="el-color-dropdown__link-btn"
@click="clear"
>{{ t('el.colorpicker.clear') }}</el-button>
<el-button
plain
size="mini"
class="el-color-dropdown__btn"
@click="confirmValue"
>{{ t('el.colorpicker.confirm') }}</el-button>
</div>
</div>
</template>
<!-- 触发内容,常驻显示 -->
<template #trigger>
<div
:class="[
'el-color-picker',
colorDisabled ? 'is-disabled' : '',
colorSize ? `el-color-picker--${colorSize}` : ''
]"
>
<!-- disabled状态的掩盖层,起到禁止点击的效果 -->
<div v-if="colorDisabled" class="el-color-picker__mask"></div>
<div class="el-color-picker__trigger" @click="handleTrigger">
<!-- 当前颜色展示 -->
<span class="el-color-picker__color" :class="{ 'is-alpha': showAlpha }">
<span
class="el-color-picker__color-inner"
:style="{
backgroundColor: displayedColor
}"
></span>
<!-- 当前没有选中值时,展示为X -->
<span
v-if="!modelValue && !showPanelColor"
class="el-color-picker__empty el-icon-close"
></span>
</span>
<!-- 向下箭头 -->
<span
v-show="modelValue || showPanelColor"
class="el-color-picker__icon el-icon-arrow-down"
></span>
</div>
</div>
</template>
</el-popper>
</template>
2.2 script部分
setup(props, { emit }) {
const ELEMENT = useGlobalConfig()
const elForm = inject(elFormKey, {} as ElFormContext)
const elFormItem = inject(elFormItemKey, {} as ElFormItemContext)
// 子组件的ref
const hue = ref(null)
const svPanel = ref(null)
const alpha = ref(null)
const popper = ref(null)
// data
// Color是一个Class,内部定义了一些颜色相关的属性和方法,这里实例化一个Color对象
const color = reactive(new Color({
enableAlpha: props.showAlpha,
format: props.colorFormat,
}))
const showPicker = ref(false)
const showPanelColor = ref(false)
const customInput = ref('')
// computed
// 计算属性,当前展示的颜色
const displayedColor = computed(() => {
// 如果没有传值 且 未选中任何颜色,则显示透明
if (!props.modelValue && !showPanelColor.value) {
return 'transparent'
}
// 否则,转换成rgb/rgba形式
return displayedRgb(color, props.showAlpha)
})
// trigger部分的size: 传入的size > form-item的size > 全局设置的size
const colorSize = computed(() => {
return props.size || elFormItem.size || ELEMENT.size
})
// 是否禁用
const colorDisabled = computed(() => {
return props.disabled || elForm.disabled
})
// 当前颜色
const currentColor = computed(() => {
return !props.modelValue && !showPanelColor.value ? '' : color.value
})
// watch
// 监测传入值的变化
watch(() => props.modelValue, newVal => {
// 新值为空,则trigger显示为X
if (!newVal) {
showPanelColor.value = false
} else if (newVal && newVal !== color.value) {
// 调用Color的fromString方法,计算出新的颜色
color.fromString(newVal)
}
})
// 当前颜色变化
watch(() => currentColor.value, val => {
// 色值输入框的值同步变化
customInput.value = val
// 向上emit事件
emit('active-change', val)
// showPanelColor.value = true
})
// 监测color.value变化
watch(() => color.value, () => {
if (!props.modelValue && !showPanelColor.value) {
showPanelColor.value = true
}
})
// methods
// 转换成可用作background-color的rgb格式
function displayedRgb(color, showAlpha) {
if (!(color instanceof Color)) {
throw Error('color should be instance of _color Class')
}
// 调用Color的toRgb方法
const { r, g, b } = color.toRgb()
return showAlpha
? `rgba(${r}, ${g}, ${b}, ${color.get('alpha') / 100})`
: `rgb(${r}, ${g}, ${b})`
}
function setShowPicker(value) {
showPicker.value = value
}
// 防抖
const debounceSetShowPicker = debounce(setShowPicker, 100)
// v-click-outside的响应函数,关闭颜色选择弹框,重新设置颜色值
function hide() {
debounceSetShowPicker(false)
resetColor()
}
// 重新设置颜色值
function resetColor() {
nextTick(() => {
if (props.modelValue) {
color.fromString(props.modelValue)
} else {
showPanelColor.value = false
}
})
}
// trigger点击事件
function handleTrigger() {
if (colorDisabled.value) return
// 展示/不展示切换
debounceSetShowPicker(!showPicker.value)
}
// 色值输入框的blur/change事件
function handleConfirm() {
// 会触发color.value的改变,进而触发上面的watch方法
color.fromString(customInput.value)
}
// “确定” 按钮事件
function confirmValue() {
// 值为当前color.value
const value = color.value
// v-model事件
emit(UPDATE_MODEL_EVENT, value)
// change事件
emit('change', value)
// 在form中时,触发el.form.change事件
elFormItem.formItemMitt?.emit('el.form.change', value)
// 关闭弹框
debounceSetShowPicker(false)
// check if modelValue change, if not change, then reset color.
nextTick(() => {
const newColor = new Color({
enableAlpha: props.showAlpha,
format: props.colorFormat,
})
newColor.fromString(props.modelValue)
if (!color.compare(newColor)) {
resetColor()
}
})
}
// “清除”颜色 事件
function clear() {
// 关闭弹框
debounceSetShowPicker(false)
// v-model change事件
emit(UPDATE_MODEL_EVENT, null)
emit('change', null)
if (props.modelValue !== null) {
elFormItem.formItemMitt?.emit('el.form.change', null)
}
// 重设颜色
resetColor()
}
onMounted(() => {
// 如果有传入值,则用传入值进行初始化
if (props.modelValue) {
color.fromString(props.modelValue)
customInput.value = currentColor.value
}
})
// 显示/隐藏弹框时,调用子组件的update方法,设置子组件的数据
watch(() => showPicker.value, () => {
nextTick(() => {
hue.value?.update()
svPanel.value?.update()
alpha.value?.update()
})
})
// 向子组件提供当前的色值
provide<IUseOptions>(OPTIONS_KEY, {
currentColor,
})
},
2.3 总结:
- 颜色有多种格式,涉及到相互转换等计算方法,代码中使用
Color class,定义了相关属性及转换计算方法,在父组件中实例化Color,并将实例化对象作为参数传入子组件,子组件可调用实例的方法进行运算;父组件对color.value等进行监听,形成联动;