用uniapp写调色板组件
废话不多说,最近业务原因,需要用uniapp写一个调色板,记录一下
先上效果展示:
最下边的结果色可以实时跟踪,颜色值也可以实时变化,有个小缺陷就是,数值变化跟不上结果值,这个原因是因为我用的input标签进行了双向绑定,如果用view标签的话就能跟上,不过这不影响整体功能,目前来说还是非常丝滑的。
代码展示
color-picker.vue
<template>
<view class="color-picker" @touchmove.stop.prevent>
<!-- 色相-饱和度取色器 -->
<view ref="slider" class="sv-picker" @touchstart="setSlider($event, 'sv')" @touchmove="moveSlide($event, 'sv')">
<view class="sv-picker-background" :style="{backgroundColor: svBackgroundColor}"></view>
<view class="sv-picker-background" :style="{background: 'linear-gradient(to right, white, #ffffff00)'}">
</view>
<view class="sv-picker-background" :style="{background: 'linear-gradient(to top, black, #ffffff00)'}">
</view>
<view class="sv-slider" :style="svSliderStyle" @touchstart="touchSlider($event, 'sv')"
@touchmove="moveSlide($event, 'sv')"></view>
</view>
<!-- 右侧的色彩结果展示及滑块区域 -->
<view class="picker-warp">
<!-- 滑块区域 -->
<view class="slider-box">
<!-- 色相滑块 -->
<view class="hue-slider" @touchstart="setSlider($event, 'hue')" @touchmove="moveSlide($event, 'hue')">
<view class="slider" :style="hueSliderStyle" @touchstart="touchSlider($event, 'hue')"
@touchmove="moveSlide($event, 'hue')"></view>
</view>
<!-- 透明度滑块 -->
<view v-if="alpha" class="alpha-slider alpha-background-image" @touchstart="setSlider($event, 'alpha')"
@touchmove="moveSlide($event, 'alpha')">
<view class="alpha-background" :style="{background: alphaBackground}"></view>
<view class="slider" :style="alphaSliderStyle" @touchstart="touchSlider($event, 'alpha')"
@touchmove="moveSlide($event, 'alpha')"></view>
</view>
</view>
</view>
<view class="result-area">
<!-- 色彩结果展示 -->
<view class="result" :style="{background:pickedColor.hex}"></view>
<input class="color-value" type="text" v-model="currentColor" />
<!-- <view class="color-value">{{currentColor}}</view> -->
</view>
</view>
</template>
<script>
import {
isValidColor
} from '@/utils/tools';
export default {
props: {
color: {
type: String,
default: '#ffffff'
},
show: {
type: Boolean,
default: true
},
alpha: {
type: Boolean,
default: false
},
},
data() {
return {
// 色相滑块的位置,用于表示选择的色相值。
hueSlider: 0,
// 透明度滑块的位置,用于表示选择的透明度值
alphaSlider: 0,
// 饱和度滑块的位置,用于表示选择的饱和度值
saturationSlider: 0,
// 明度滑块的位置,用于表示选择的明度值。
valueSlider: 0,
// 取色器的宽度,用于计算滑块位置和颜色
pickerWidth: 0,
// 取色器的高度,用于计算滑块位置和颜色。
pickerHeight: 0,
// 取色器相对于父元素的水平偏移量,用于计算滑块位置和颜色。
pickerOffsetX: 0,
// 滑块宽度的一半,用于调整滑块位置。
halfSilder: uni.getSystemInfoSync().windowWidth / 750 * 15,
currentColor: this.color
}
},
watch: {
// 监听color属性的变化
color() {
if (this.color && this.color !== this.pickedColor.hex) {
this.retryColor();
}
},
// 监听show属性的变化
show(v) {
if (v) {
setTimeout(() => this.init(), 600);
}
},
currentColor(val) {
if (val) {
this.retryColor()
}
}
},
computed: {
// 计算选中的色相颜色
pickedHueColor() {
const hue = this.hueSlider / this.pickerWidth
return hsvToRgb(hue, 1, 1)
},
// 计算选中的颜色
pickedColor() {
if (!this.pickerWidth) {
return {
r: 0,
g: 0,
b: 0,
a: 0
}
}
const hue = this.hueSlider / this.pickerWidth
const saturation = this.saturationSlider / this.pickerWidth
// const value = (this.pickerWidth - this.valueSlider) / this.pickerWidth
const value = (this.pickerHeight - this.valueSlider) / this.pickerHeight
const alpha = this.alphaSlider / this.pickerWidth
const rgb = hsvToRgb(hue, saturation, value)
let color
if (alpha === 1) {
color = {
...rgb,
a: alpha,
rgb: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
hex: rgbaToHex(rgb.r, rgb.g, rgb.b, 255)
}
} else {
color = {
...rgb,
a: alpha,
rgb: `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${Math.round(alpha * 100) / 100})`,
hex: rgbaToHex(rgb.r, rgb.g, rgb.b, Math.round(alpha * 255))
}
}
return color
},
// 计算饱和度-明度取色器的背景颜色
svBackgroundColor() {
return `rgb(${this.pickedHueColor.r}, ${this.pickedHueColor.g}, ${this.pickedHueColor.b})`
},
// 计算透明度取色器的背景样式
alphaBackground() {
return `linear-gradient(to right,
rgba(${this.pickedHueColor.r}, ${this.pickedHueColor.g}, ${this.pickedHueColor.b}, 0),
rgba(${this.pickedHueColor.r}, ${this.pickedHueColor.g}, ${this.pickedHueColor.b}, 1))`
},
// 计算色相滑块的样式
hueSliderStyle() {
return {
transform: `translate(${this.hueSlider - this.halfSilder}px, -50%)`
}
},
// 计算透明度滑块的样式
alphaSliderStyle() {
return {
transform: `translate(${this.alphaSlider - this.halfSilder}px, -50%)`
}
},
// 计算饱和度-明度滑块的样式
svSliderStyle() {
return {
transform: `translate(${this.saturationSlider - this.halfSilder}px, ${this.valueSlider - this.halfSilder}px)`
}
},
},
mounted() {
// setTimeout(() => {
this.init();
// }, 300)
// this.init();
},
methods: {
async init() {
await uni.$u.sleep();
const sliderInfo = await this.$u.getRect('.sv-picker');
this.pickerWidth = sliderInfo.width
this.pickerHeight = sliderInfo.height
this.pickerOffsetX = sliderInfo.left
this.pickerOffsetY = sliderInfo.top
this.alphaSlider = sliderInfo.width
this.saturationSlider = sliderInfo.width
if (this.color) this.retryColor();
// 加载完实时配色重新计算高度
setTimeout(() => {
this.$u.getRect('.sv-picker').then(res => {
this.pickerOffsetY = res.top
});
}, 300)
},
// 重新设置颜色
retryColor() {
this.$nextTick(() => {
const color = isValidColor(this.currentColor) ? this.currentColor : '#ffffff';
const {
r,
g,
b,
a
} = hexToRgba(color.toLowerCase())
const {
h,
s,
v
} = rgbToHsv(r, g, b)
this.hueSlider = h * this.pickerWidth
this.alphaSlider = a * this.pickerWidth
this.saturationSlider = s * this.pickerWidth
// this.valueSlider = this.pickerWidth - v * this.pickerWidth
this.valueSlider = this.pickerHeight - v * this.pickerHeight
})
},
// 触摸滑块事件处理方法
touchSlider(e, component) {
if (component === 'sv') {
this.startLeft = this.saturationSlider
this.startPageX = e.touches[0].pageX
this.startTop = this.valueSlider
this.startPageY = e.touches[0].clientY
} else {
this.startLeft = this[component + 'Slider']
this.startPageX = e.touches[0].pageX
}
},
// 滑动滑块事件处理方法
moveSlide(e, component) {
if (component === 'sv') {
this.setSliderValue('saturation', this.startLeft + e.touches[0].pageX - this.startPageX)
this.setSliderValue('value', this.startTop + e.touches[0].clientY - this.startPageY)
} else {
this.setSliderValue(component, this.startLeft + e.touches[0].pageX - this.startPageX)
}
this.emitPickedColor()
},
// 设置滑块位置
setSlider(e, component) {
if (component === 'sv') {
this.setSliderValue('saturation', e.touches[0].pageX - this.pickerOffsetX)
this.setSliderValue('value', e.touches[0].clientY - this.pickerOffsetY)
} else {
this.setSliderValue(component, e.touches[0].pageX - this.pickerOffsetX)
}
this.touchSlider(e, component)
this.emitPickedColor()
},
// 设置滑块值
setSliderValue(component, value) {
const data = component === 'value' ? this.pickerHeight : this.pickerWidth
this[component + 'Slider'] = Math.min(Math.max(value, 0), data)
// this[component + 'Slider'] = Math.min(Math.max(value, 0), this.pickerWidth)
},
// 发送选中颜色事件
emitPickedColor() {
clearTimeout(this.emitTimer)
this.currentColor = this.pickedColor.hex
this.emitTimer = setTimeout(() => {
this.$emit('pickerColor', {
rgb: this.pickedColor.rgb,
hex: this.pickedColor.hex
})
}, 100);
}
}
}
// 辅助函数:HSV颜色转RGB颜色
function hsvToRgb(h, s, v) {
var r, g, b, i, f, p, q, t;
if (arguments.length === 1) {
s = h.s, v = h.v, h = h.h;
}
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0:
r = v, g = t, b = p;
break;
case 1:
r = q, g = v, b = p;
break;
case 2:
r = p, g = v, b = t;
break;
case 3:
r = p, g = q, b = v;
break;
case 4:
r = t, g = p, b = v;
break;
case 5:
r = v, g = p, b = q;
break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
}
}
// 辅助函数:RGB颜色转HSV颜色
function rgbToHsv(r, g, b) {
if (arguments.length === 1) {
g = r.g, b = r.b, r = r.r;
}
var max = Math.max(r, g, b),
min = Math.min(r, g, b),
d = max - min,
h,
s = (max === 0 ? 0 : d / max),
v = max / 255;
switch (max) {
case min:
h = 0;
break;
case r:
h = (g - b) + d * (g < b ? 6 : 0);
h /= 6 * d;
break;
case g:
h = (b - r) + d * 2;
h /= 6 * d;
break;
case b:
h = (r - g) + d * 4;
h /= 6 * d;
break;
}
return {
h,
s,
v
};
}
// 辅助函数:RGBA颜色转十六进制颜色
function rgbaToHex(r, g, b, a) {
let hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
if (a !== 255) {
hex += ((1 << 8) + a).toString(16).slice(1)
}
return hex
}
// 辅助函数:十六进制颜色转RGBA颜色
function hexToRgba(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
a: result[4] ? parseInt(result[4], 16) / 255 : 1,
} : null;
}
</script>
<style lang="scss" scoped>
.color-picker {
position: relative;
width: 100%;
}
.sv-picker {
position: relative;
width: 100%;
margin-bottom: 20rpx;
// padding-top: 100%;
height: 215rpx;
.sv-slider {
position: absolute;
top: 0;
left: 0;
width: 30rpx;
height: 30rpx;
border: 1px solid #FFF;
background-color: rgba(73, 73, 73, 0.5);
border-radius: 50rpx;
}
.sv-picker-background {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
}
.hue-slider,
.alpha-slider {
// margin-top: 20rpx;
margin-bottom: 20rpx;
position: relative;
width: 100%;
height: 35rpx;
}
.hue-slider {
background: linear-gradient(90deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red);
}
.alpha-background-image {
background-size: 35rpx;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");
}
.slider {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-20%);
width: 22rpx;
height: 36rpx;
background: rgba(234, 231, 231, 0.5);
border: 2rpx solid #E2E2E2;
}
.alpha-background {
width: 100%;
height: 100%;
}
.picker-warp {
display: flex;
align-items: center;
margin-top: 25rpx;
.slider-box {
flex: 1;
}
}
.result-area {
display: flex;
justify-content: flex-start;
align-items: center;
.result {
width: 112rpx;
height: 78rpx;
border-radius: 4rpx;
margin-right: 10rpx;
}
.color-value {
width: 514rpx;
height: 80rpx;
border-radius: 4rpx;
border: 2rpx solid #EFEFEF;
padding-left: 32rpx;
}
}
</style>
使用方式:
<color-picker :show="true" :alpha="true" :color="#F8F9FF"></color-picker>
又学到了吧,下课!!