颜色选择器将选项式改组合式并改用TS

This commit is contained in:
Dftre 2025-03-21 23:44:11 +08:00
parent f0200ac849
commit 2f8bbb6ca2

View File

@ -1,3 +1,391 @@
<script setup lang="ts">
import { ref, reactive, watch, nextTick, getCurrentInstance } from 'vue';
//
interface RGBAColor {
r: number;
g: number;
b: number;
a: number;
}
interface HSBColor {
h: number;
s: number;
b: number;
}
interface SitePosition {
top: number;
left: number;
}
interface ElementPosition {
top: number;
left: number;
width: number;
height: number;
}
const emit = defineEmits<{
(e: 'confirm', data: { rgba: RGBAColor; hex: string }): void;
}>();
const instance = getCurrentInstance();
const props = defineProps({
color: {
type: Object as () => RGBAColor,
default() {
return { r: 0, g: 0, b: 0, a: 0 }
}
},
spareColor: {
type: Array as () => RGBAColor[],
default() {
return []
}
}
});
//
const show = ref<boolean>(false);
const active = ref<boolean>(false);
const rgba = reactive<RGBAColor>({ r: 0, g: 0, b: 0, a: 1 });
const hsb = reactive<HSBColor>({ h: 0, s: 0, b: 0 });
const site = reactive<SitePosition[]>([{ top: 0, left: 0 }, { left: 0, top: 0 }, { left: 0, top: 0 }]);
const index = ref<number>(0);
const bgcolor = reactive<RGBAColor>({ r: 255, g: 0, b: 0, a: 1 });
const hex = ref<string>('#000000');
const mode = ref<boolean>(true);
const position = ref<ElementPosition[] | null>(null);
const colorList = ref<RGBAColor[]>([
{ r: 244, g: 67, b: 54, a: 1 },
{ r: 233, g: 30, b: 99, a: 1 },
{ r: 156, g: 39, b: 176, a: 1 },
{ r: 103, g: 58, b: 183, a: 1 },
{ r: 63, g: 81, b: 181, a: 1 },
{ r: 33, g: 150, b: 243, a: 1 },
{ r: 3, g: 169, b: 244, a: 1 },
{ r: 0, g: 188, b: 212, a: 1 },
{ r: 0, g: 150, b: 136, a: 1 },
{ r: 76, g: 175, b: 80, a: 1 },
{ r: 139, g: 195, b: 74, a: 1 },
{ r: 205, g: 220, b: 57, a: 1 },
{ r: 255, g: 235, b: 59, a: 1 },
{ r: 255, g: 193, b: 7, a: 1 },
{ r: 255, g: 152, b: 0, a: 1 },
{ r: 255, g: 87, b: 34, a: 1 },
{ r: 121, g: 85, b: 72, a: 1 },
{ r: 158, g: 158, b: 158, a: 1 },
{ r: 0, g: 0, b: 0, a: 0.5 },
{ r: 0, g: 0, b: 0, a: 0 },
]);
// created
Object.assign(rgba, props.color);
if (props.spareColor.length !== 0) {
colorList.value = props.spareColor;
}
/**
* 初始化
*/
const init = (): void => {
// hsb
Object.assign(hsb, rgbToHsb(rgba));
setValue(rgba);
};
const moveHandle = (): void => { };
const open = (): void => {
show.value = true;
nextTick(() => {
init();
setTimeout(() => {
active.value = true;
setTimeout(() => {
getSelectorQuery();
}, 350)
}, 50)
})
};
const close = (): void => {
active.value = false;
nextTick(() => {
setTimeout(() => {
show.value = false;
}, 500)
})
};
const confirm = (): void => {
close();
emit('confirm', {
rgba: rgba,
hex: hex.value
})
};
//
const select = (): void => {
mode.value = !mode.value
};
//
const selectColor = (item: RGBAColor): void => {
setColorBySelect(item)
};
const touchstart = (e: TouchEvent, idx: number): void => {
const { pageX, pageY, clientX, clientY } = e.touches[0];
setPosition(clientX, clientY, idx);
};
const touchmove = (e: TouchEvent, idx: number): void => {
const { pageX, pageY, clientX, clientY } = e.touches[0];
setPosition(clientX, clientY, idx);
};
const touchend = (e: TouchEvent, idx: number): void => {
//
};
/**
* 设置位置
*/
const setPosition = (x: number, y: number, idx: number): void => {
index.value = idx;
if (!position.value || !position.value[idx]) return;
const {
top,
left,
width,
height
} = position.value[idx];
//
site[idx].left = Math.max(0, Math.min(parseInt(String(x - left)), width));
if (idx === 0) {
site[idx].top = Math.max(0, Math.min(parseInt(String(y - top)), height));
//
hsb.s = parseInt(String((100 * site[idx].left) / width));
hsb.b = parseInt(String(100 - (100 * (site[idx].top as number)) / height));
setColor();
setValue(rgba);
} else {
setControl(idx, site[idx].left);
}
};
/**
* 设置 rgb 颜色
*/
const setColor = (): void => {
const rgb = HSBToRGB(hsb);
rgba.r = rgb.r;
rgba.g = rgb.g;
rgba.b = rgb.b;
};
/**
* 设置二进制颜色
* @param {RGBAColor} rgb
*/
const setValue = (rgb: RGBAColor): void => {
hex.value = '#' + rgbToHex(rgb);
};
const setControl = (idx: number, x: number): void => {
if (!position.value || !position.value[idx]) return;
const {
width
} = position.value[idx];
if (idx === 1) {
hsb.h = parseInt(String((360 * x) / width));
const newRgb = HSBToRGB({
h: hsb.h,
s: 100,
b: 100
});
bgcolor.r = newRgb.r;
bgcolor.g = newRgb.g;
bgcolor.b = newRgb.b;
setColor();
} else {
rgba.a = parseFloat((x / width).toFixed(1));
}
setValue(rgba);
};
/**
* rgb 二进制 hex
* @param {RGBAColor} rgb
* @returns {string} 十六进制颜色字符串不含#
*/
const rgbToHex = (rgb: RGBAColor): string => {
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
hex.map(function (str, i) {
if (str.length == 1) {
hex[i] = '0' + str;
}
});
return hex.join('');
};
const setColorBySelect = (getrgb: RGBAColor): void => {
const {
r,
g,
b,
a
} = getrgb;
rgba.r = r ? parseInt(String(r)) : 0;
rgba.g = g ? parseInt(String(g)) : 0;
rgba.b = b ? parseInt(String(b)) : 0;
rgba.a = a ? a : 0;
Object.assign(hsb, rgbToHsb(rgba));
changeViewByHsb();
};
const changeViewByHsb = (): void => {
if (!position.value || position.value.length < 3) return;
const [a, b, c] = position.value;
site[0].left = parseInt(String(hsb.s * a.width / 100));
site[0].top = parseInt(String((100 - hsb.b) * a.height / 100));
setColor();
setValue(rgba);
const newRgb = HSBToRGB({
h: hsb.h,
s: 100,
b: 100
});
bgcolor.r = newRgb.r;
bgcolor.g = newRgb.g;
bgcolor.b = newRgb.b;
site[1].left = hsb.h / 360 * b.width;
site[2].left = rgba.a * c.width;
};
/**
* hsb rgb
* @param {HSBColor} hsb 颜色模式 H(hues)表示色相S(saturation)表示饱和度Bbrightness表示亮度
* @returns {RGBAColor} RGB颜色对象
*/
const HSBToRGB = (hsb: HSBColor): RGBAColor => {
let rgb: { r: number; g: number; b: number } = { r: 0, g: 0, b: 0 };
let h = Math.round(hsb.h);
let s = Math.round((hsb.s * 255) / 100);
let v = Math.round((hsb.b * 255) / 100);
if (s == 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
let t1 = v;
let t2 = ((255 - s) * v) / 255;
let t3 = ((t1 - t2) * (h % 60)) / 60;
if (h == 360) h = 0;
if (h < 60) {
rgb.r = t1;
rgb.b = t2;
rgb.g = t2 + t3;
} else if (h < 120) {
rgb.g = t1;
rgb.b = t2;
rgb.r = t1 - t3;
} else if (h < 180) {
rgb.g = t1;
rgb.r = t2;
rgb.b = t2 + t3;
} else if (h < 240) {
rgb.b = t1;
rgb.r = t2;
rgb.g = t1 - t3;
} else if (h < 300) {
rgb.b = t1;
rgb.g = t2;
rgb.r = t2 + t3;
} else if (h < 360) {
rgb.r = t1;
rgb.g = t2;
rgb.b = t1 - t3;
} else {
rgb.r = 0;
rgb.g = 0;
rgb.b = 0;
}
}
return {
r: Math.round(rgb.r),
g: Math.round(rgb.g),
b: Math.round(rgb.b),
a: 1
};
};
/**
* rgb转hsb
* @param {RGBAColor} rgb RGB颜色对象
* @returns {HSBColor} HSB颜色对象
*/
const rgbToHsb = (rgb: RGBAColor): HSBColor => {
let hsb = {
h: 0,
s: 0,
b: 0
};
let min = Math.min(rgb.r, rgb.g, rgb.b);
let max = Math.max(rgb.r, rgb.g, rgb.b);
let delta = max - min;
hsb.b = max;
hsb.s = max != 0 ? 255 * delta / max : 0;
if (hsb.s != 0) {
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
} else hsb.h = -1;
hsb.h *= 60;
if (hsb.h < 0) hsb.h = 0;
hsb.s *= 100 / 255;
hsb.b *= 100 / 255;
return hsb;
};
const getSelectorQuery = (): void => {
const views = uni.createSelectorQuery().in(instance!.proxy);
views.selectAll('.boxs')
.boundingClientRect(data => {
if (Array.isArray(data) ? data.length === 0 : !data) {
setTimeout(() => getSelectorQuery(), 20)
return
}
position.value = data as unknown as ElementPosition[];
setColorBySelect(rgba);
})
.exec();
};
// props
watch(() => props.spareColor, (newVal) => {
colorList.value = newVal;
});
//
defineExpose({
open,
close,
confirm
});
</script>
<template>
<view v-show="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
<view class="t-mask" :class="{ active: active }" @click.stop="close"></view>
@ -11,23 +399,28 @@
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)"
@touchend="touchend($event, 0)">
<view class="t-color-mask"></view>
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
<view class="t-pointer" :style="{
top: site[0].top - 8 + 'px',
left: site[0].left - 8 + 'px'
}">
</view>
</view>
</view>
<view class="t-control__box">
<view class="t-control__color">
<view class="t-control__color-content"
:style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
:style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }">
</view>
</view>
<view class="t-control-box__item">
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)"
@touchend="touchend($event, 1)">
<view class="t-controller boxs" @touchstart="touchstart($event, 1)"
@touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
<view class="t-hue">
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
</view>
</view>
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)"
@touchend="touchend($event, 2)">
<view class="t-controller boxs" @touchstart="touchstart($event, 2)"
@touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
<view class="t-transparency">
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
</view>
@ -76,336 +469,6 @@
</view>
</view>
</template>
<script>
export default {
props: {
color: {
type: Object,
default() {
return { r: 0, g: 0, b: 0, a: 0 }
}
},
spareColor: {
type: Array,
default() {
return []
}
}
},
data() {
return {
show: false,
active: false,
// rgba
rgba: { r: 0, g: 0, b: 0, a: 1 },
// hsb
hsb: { h: 0, s: 0, b: 0 },
site: [{ top: 0, left: 0 }, { left: 0 }, { left: 0 }],
index: 0,
bgcolor: { r: 255, g: 0, b: 0, a: 1 },
hex: '#000000',
mode: true,
colorList: [
{ r: 244, g: 67, b: 54, a: 1 },
{ r: 233, g: 30, b: 99, a: 1 },
{ r: 156, g: 39, b: 176, a: 1 },
{ r: 103, g: 58, b: 183, a: 1 },
{ r: 63, g: 81, b: 181, a: 1 },
{ r: 33, g: 150, b: 243, a: 1 },
{ r: 3, g: 169, b: 244, a: 1 },
{ r: 0, g: 188, b: 212, a: 1 },
{ r: 0, g: 150, b: 136, a: 1 },
{ r: 76, g: 175, b: 80, a: 1 },
{ r: 139, g: 195, b: 74, a: 1 },
{ r: 205, g: 220, b: 57, a: 1 },
{ r: 255, g: 235, b: 59, a: 1 },
{ r: 255, g: 193, b: 7, a: 1 },
{ r: 255, g: 152, b: 0, a: 1 },
{ r: 255, g: 87, b: 34, a: 1 },
{ r: 121, g: 85, b: 72, a: 1 },
{ r: 158, g: 158, b: 158, a: 1 },
{ r: 0, g: 0, b: 0, a: 0.5 },
{ r: 0, g: 0, b: 0, a: 0 },
]
};
},
created() {
this.rgba = this.color;
if (this.spareColor.length !== 0) {
this.colorList = this.spareColor;
}
},
methods: {
/**
* 初始化
*/
init() {
// hsb
this.hsb = this.rgbToHex(this.rgba);
this.setValue(this.rgba);
},
moveHandle() { },
open() {
this.show = true;
this.$nextTick(() => {
this.init();
setTimeout(() => {
this.active = true;
setTimeout(() => {
this.getSelectorQuery();
}, 350)
}, 50)
})
},
close() {
this.active = false;
this.$nextTick(() => {
setTimeout(() => {
this.show = false;
}, 500)
})
},
confirm() {
this.close();
this.$emit('confirm', {
rgba: this.rgba,
hex: this.hex
})
},
//
select() {
this.mode = !this.mode
},
//
selectColor(item) {
this.setColorBySelect(item)
},
touchstart(e, index) {
const { pageX, pageY, clientX, clientY } = e.touches[0];
this.pageX = clientX;
this.pageY = clientY;
this.setPosition(this.pageX, this.pageY, index);
},
touchmove(e, index) {
const { pageX, pageY, clientX, clientY } = e.touches[0];
this.moveX = clientX;
this.moveY = clientY;
this.setPosition(this.moveX, this.moveY, index);
},
touchend(e, index) {
},
/**
* 设置位置
*/
setPosition(x, y, index) {
this.index = index;
const {
top,
left,
width,
height
} = this.position[index];
//
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
if (index === 0) {
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
//
this.hsb.s = parseInt((100 * this.site[index].left) / width);
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
this.setColor();
this.setValue(this.rgba);
} else {
this.setControl(index, this.site[index].left);
}
},
/**
* 设置 rgb 颜色
*/
setColor() {
const rgb = this.HSBToRGB(this.hsb);
this.rgba.r = rgb.r;
this.rgba.g = rgb.g;
this.rgba.b = rgb.b;
},
/**
* 设置二进制颜色
* @param {Object} rgb
*/
setValue(rgb) {
this.hex = '#' + this.rgbToHex(rgb);
},
setControl(index, x) {
const {
top,
left,
width,
height
} = this.position[index];
if (index === 1) {
this.hsb.h = parseInt((360 * x) / width);
this.bgcolor = this.HSBToRGB({
h: this.hsb.h,
s: 100,
b: 100
});
this.setColor()
} else {
this.rgba.a = (x / width).toFixed(1);
}
this.setValue(this.rgba);
},
/**
* rgb 二进制 hex
* @param {Object} rgb
*/
rgbToHex(rgb) {
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
hex.map(function (str, i) {
if (str.length == 1) {
hex[i] = '0' + str;
}
});
return hex.join('');
},
setColorBySelect(getrgb) {
const {
r,
g,
b,
a
} = getrgb;
let rgb = {}
rgb = {
r: r ? parseInt(r) : 0,
g: g ? parseInt(g) : 0,
b: b ? parseInt(b) : 0,
a: a ? a : 0,
};
this.rgba = rgb;
this.hsb = this.rgbToHsb(rgb);
this.changeViewByHsb();
},
changeViewByHsb() {
const [a, b, c] = this.position;
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
this.setColor(this.hsb.h);
this.setValue(this.rgba);
this.bgcolor = this.HSBToRGB({
h: this.hsb.h,
s: 100,
b: 100
});
this.site[1].left = this.hsb.h / 360 * b.width;
this.site[2].left = this.rgba.a * c.width;
},
/**
* hsb rgb
* @param {Object} 颜色模式 H(hues)表示色相S(saturation)表示饱和度Bbrightness表示亮度
*/
HSBToRGB(hsb) {
let rgb = {};
let h = Math.round(hsb.h);
let s = Math.round((hsb.s * 255) / 100);
let v = Math.round((hsb.b * 255) / 100);
if (s == 0) {
rgb.r = rgb.g = rgb.b = v;
} else {
let t1 = v;
let t2 = ((255 - s) * v) / 255;
let t3 = ((t1 - t2) * (h % 60)) / 60;
if (h == 360) h = 0;
if (h < 60) {
rgb.r = t1;
rgb.b = t2;
rgb.g = t2 + t3;
} else if (h < 120) {
rgb.g = t1;
rgb.b = t2;
rgb.r = t1 - t3;
} else if (h < 180) {
rgb.g = t1;
rgb.r = t2;
rgb.b = t2 + t3;
} else if (h < 240) {
rgb.b = t1;
rgb.r = t2;
rgb.g = t1 - t3;
} else if (h < 300) {
rgb.b = t1;
rgb.g = t2;
rgb.r = t2 + t3;
} else if (h < 360) {
rgb.r = t1;
rgb.g = t2;
rgb.b = t1 - t3;
} else {
rgb.r = 0;
rgb.g = 0;
rgb.b = 0;
}
}
return {
r: Math.round(rgb.r),
g: Math.round(rgb.g),
b: Math.round(rgb.b)
};
},
rgbToHsb(rgb) {
let hsb = {
h: 0,
s: 0,
b: 0
};
let min = Math.min(rgb.r, rgb.g, rgb.b);
let max = Math.max(rgb.r, rgb.g, rgb.b);
let delta = max - min;
hsb.b = max;
hsb.s = max != 0 ? 255 * delta / max : 0;
if (hsb.s != 0) {
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
} else hsb.h = -1;
hsb.h *= 60;
if (hsb.h < 0) hsb.h = 0;
hsb.s *= 100 / 255;
hsb.b *= 100 / 255;
return hsb;
},
getSelectorQuery() {
const views = uni.createSelectorQuery().in(this);
views
.selectAll('.boxs')
.boundingClientRect(data => {
if (!data || data.length === 0) {
setTimeout(() => this.getSelectorQuery(), 20)
return
}
this.position = data;
// this.site[0].top = data[0].height;
// this.site[0].left = 0;
// this.site[1].left = data[1].width;
// this.site[2].left = data[2].width;
this.setColorBySelect(this.rgba);
})
.exec();
}
},
watch: {
spareColor(newVal) {
this.colorList = newVal;
}
}
};
</script>
<style lang="scss" scoped>
.t-wrapper {
position: fixed;
@ -477,6 +540,7 @@ export default {
}
}
// 使 &__box
.t-color__box {
position: relative;
height: 400upx;
@ -488,15 +552,6 @@ export default {
box-sizing: border-box;
}
.t-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.t-color-mask {
position: absolute;
top: 0;
@ -508,6 +563,15 @@ export default {
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
}
.t-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.t-pointer {
position: absolute;
bottom: -8px;
@ -524,6 +588,7 @@ export default {
height: 50upx;
}
// 使使
.t-control__box {
margin-top: 50upx;
width: 100%;
@ -593,6 +658,7 @@ export default {
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
}
// 使
.t-result__box {
margin-top: 20upx;
padding: 10upx;