前言
计划阅读element的源码,之后会写一系列的文章,先来看el-color-picker的
目录结构
阅读源码首先要看下项目的文件结构,下图是element的文件结构。
build是打包构建配置相关的一些文件,examples是组件使用例子,lib是打包后的文件,packages是组件代码文件,src用于存放入口文件及一些辅助工具,test是单元测试文件,types是ts类型文件,以及项目中还有一些配置文件
找到packages目录下的el-color-picker目录,就是要阅读的部分了
开始阅读
index.js
在index.js中导出了ColorPicker
import ColorPicker from './src/main';
/* istanbul ignore next */
ColorPicker.install = function(Vue) {
Vue.component(ColorPicker.name, ColorPicker);
};
export default ColorPicker;
main.vue
main.vue是主体组件,由一个默认颜色的小方块和一个下拉颜色框组成。文档上有下面这些属性,我们看下这些属性是怎么在代码中作用的。
上面的属性都在main.vue的prop中传入了。其中value是选择颜色的默认值
props: {
value: String,
showAlpha: Boolean,
colorFormat: String,
disabled: Boolean,
size: String,
popperClass: String,
predefine: Array
},
当showAlpha传入true时,会给一个透明像素的背景图
<span class="el-color-picker__color" :class="{ 'is-alpha': showAlpha }">
color-format是颜色的格式,有hsl / hsv / hex / rgb几种类型。在data中定义了color对象,我们找到color.js看color对象内部是如何实现的。
const color = new Color({
enableAlpha: this.showAlpha,
format: this.colorFormat
});
color.js
// hsv转hsl
const hsv2hsl = function(hue, sat, val) {};
// 是否为1.0
const isOnePointZero = function(n) {};
// 是否为百分号
const isPercentage = function(n) {};
// Take input from [0, n] and return it as [0, 1]
const bound01 = function(value, max) {};
// 十进制转十六进制的map
const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' };
// 转为十六进制颜色
const toHex = function({ r, g, b }) {};
// 十六进制转十进制
const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 };
// 解析十六进制
const parseHexChannel = function(hex) {};
// hsl转hsv
const hsl2hsv = function(hue, sat, light) {};
// rgb转hsv
const rgb2hsv = function(r, g, b) {};
// hsv转rgb
const hsv2rgb = function(h, s, v) {};
export default class Color {
constructor(options) {
// 定义一些实例属性
this._hue = 0;
this._saturation = 100;
this._value = 100;
this._alpha = 100;
this.enableAlpha = false;
this.format = 'hex';
this.value = '';
options = options || {};
for (let option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
this.doOnChange();
}
// 设置属性值
set(prop, value) {
if (arguments.length === 1 && typeof prop === 'object') {
for (let p in prop) {
if (prop.hasOwnProperty(p)) {
this.set(p, prop[p]);
}
}
return;
}
this['_' + prop] = value;
this.doOnChange();
}
// 获取值
get(prop) {
return this['_' + prop];
}
// 转rgb
toRgb() {
return hsv2rgb(this._hue, this._saturation, this._value);
}
// 格式化传入的值
fromString(value) {
if (!value) {
this._hue = 0;
this._saturation = 100;
this._value = 100;
this.doOnChange();
return;
}
const fromHSV = (h, s, v) => {
this._hue = Math.max(0, Math.min(360, h));
this._saturation = Math.max(0, Math.min(100, s));
this._value = Math.max(0, Math.min(100, v));
this.doOnChange();
};
// 一些逻辑...
}
compare(color) {
return Math.abs(color._hue - this._hue) < 2 &&
Math.abs(color._saturation - this._saturation) < 1 &&
Math.abs(color._value - this._value) < 1 &&
Math.abs(color._alpha - this._alpha) < 1;
}
// 根据组件传入的color-format计算需要对应格式的颜色值
doOnChange() {
const { _hue, _saturation, _value, _alpha, format } = this;
if (this.enableAlpha) { // 允许透明色
switch (format) {
case 'hsl':
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
this.value = `hsla(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%, ${ _alpha / 100})`;
break;
case 'hsv':
this.value = `hsva(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%, ${ _alpha / 100})`;
break;
default:
const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
this.value = `rgba(${r}, ${g}, ${b}, ${ _alpha / 100 })`;
}
} else {
switch (format) {
case 'hsl':
const hsl = hsv2hsl(_hue, _saturation / 100, _value / 100);
this.value = `hsl(${ _hue }, ${ Math.round(hsl[1] * 100) }%, ${ Math.round(hsl[2] * 100) }%)`;
break;
case 'hsv':
this.value = `hsv(${ _hue }, ${ Math.round(_saturation) }%, ${ Math.round(_value) }%)`;
break;
case 'rgb':
const { r, g, b } = hsv2rgb(_hue, _saturation, _value);
this.value = `rgb(${r}, ${g}, ${b})`;
break;
default:
this.value = toHex(hsv2rgb(_hue, _saturation, _value));
}
}
}
};
接着回到main.vue,在computed中计算了一些属性,这些属性会用在template上
computed: {
displayedColor() {
if (!this.value && !this.showPanelColor) {
return 'transparent';
}
return this.displayedRgb(this.color, this.showAlpha);
},
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
colorSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
colorDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
}
根据colorSize属性返回的true/false来绑定了一个对应的类名,disabled属性也是同样的
<template>
<div
:class="[
'el-color-picker',
colorDisabled ? 'is-disabled' : '',
colorSize ? `el-color-picker--${ colorSize }` : ''
]"
v-clickoutside="hide">
<div class="el-color-picker__mask" v-if="colorDisabled"></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>
<span class="el-color-picker__empty el-icon-close" v-if="!value && !showPanelColor"></span>
</span>
<span class="el-color-picker__icon el-icon-arrow-down" v-show="value || showPanelColor"></span>
</div>
<picker-dropdown
ref="dropdown"
:class="['el-color-picker__panel', popperClass || '']"
v-model="showPicker"
@pick="confirmValue"
@clear="clearValue"
:color="color"
:show-alpha="showAlpha"
:predefine="predefine">
</picker-dropdown>
</div>
</template>
css样式
在main.vue中并没有看到css样式,那写在了哪里呢?在packages目录下有个theme-chalk目录,底下的src就是每个组件对应的样式。因为里边的css不像我们平时写的简单的css,对于css样式的分析,这里先不写,后续会专门写一篇分析element的css是如何设计的。
picker-dropdown.vue
picker-dropdown就是下拉选择颜色的组件,由以下组件组成
hue-slider的实现其实很简单,用css属性就可以实现
// html
<div class="hue-slider"></div>
// css
.hue-slider {
width: 27px;
height: 350px;
background: linear-gradient(to bottom, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red 100%);
}
sv-panel由3个div组成
// html
<div class="panel">
<div class="color"></div>
<div class="mask"></div>
</div>
// css
.color {
width: 450px;
height: 350px;
position: absolute;
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.mask {
width: 450px;
height: 350px;
position: absolute;
background: linear-gradient(to top, #000, transparent);
}
.panel {
width: 450px;
height: 350px;
position: relative;
background: #00ff7f;
}
还有个透明度选择alpha-slider,也是差不多的
上面三个组件UI都是用css的line-gradient属性来实现的,line-gradient需要两个参数,具体的可以参考[菜鸟教程](www.runoob.com/cssref/func…)
剩余一个predefine组件就是预定义的颜色遍历展示了
计算逻辑
在移动滑块和点击位置时是如何进行颜色的切换和选择的呢?我们看下主要逻辑
在hue-slider、sv-panel、alpha-slider组件中,鼠标滑动或者点击时,会获取鼠标在当前元素中的位置,根据位置信息去计算颜色值,再进行赋值,从而得到结果。