<!--
|
* @Author: 秦少卫
|
* @Date: 2023-02-16 22:52:00
|
* @LastEditors: 秦少卫
|
* @LastEditTime: 2024-05-21 15:15:04
|
* @Description: 颜色选择器
|
-->
|
<template>
|
<div class="box">
|
<!-- 颜色开关 -->
|
<iSwitch v-model="isGradient" size="large" class="switch">
|
<template #open>
|
<span>渐变</span>
|
</template>
|
<template #close>
|
<span>纯色</span>
|
</template>
|
</iSwitch>
|
<!-- 渐变 -->
|
<div v-if="isGradient">
|
<div class="gradient-bar" :style="bgStr"></div>
|
<!-- 颜色插件 -->
|
|
<gradientColorPicker
|
:is-gradient="true"
|
:gradient="currentGradient"
|
@change="changeGradientColor"
|
:cancel-text="$t('cancel')"
|
:confirm-text="$t('ok')"
|
/>
|
</div>
|
|
<!-- 纯色选择器 -->
|
<ColorPicker v-show="!isGradient" v-model="fill" @on-change="changePureColor" alpha />
|
</div>
|
</template>
|
|
<script setup name="ColorSelector">
|
// import 'color-gradient-picker-vue3/dist/style.css';
|
// import gradientColorPicker from 'color-gradient-picker-vue3';
|
import { fabric } from 'fabric';
|
import useSelect from '@/hooks/select';
|
import { debounce } from 'lodash-es';
|
const { canvasEditor } = useSelect();
|
const generateFabricGradientFromColorStops = (handlers, width, height, orientation, angle) => {
|
// 角度转换坐标
|
const gradAngleToCoords = (paramsAngle) => {
|
const anglePI = -parseInt(paramsAngle, 10) * (Math.PI / 180);
|
return {
|
x1: Math.round(50 + Math.sin(anglePI) * 50) / 100,
|
y1: Math.round(50 + Math.cos(anglePI) * 50) / 100,
|
x2: Math.round(50 + Math.sin(anglePI + Math.PI) * 50) / 100,
|
y2: Math.round(50 + Math.cos(anglePI + Math.PI) * 50) / 100,
|
};
|
};
|
|
// 生成线性渐变
|
const generateLinear = (colorStops) => {
|
const angleCoords = gradAngleToCoords(angle);
|
return new fabric.Gradient({
|
type: 'linear',
|
coords: {
|
x1: angleCoords.x1 * width,
|
y1: angleCoords.y1 * height,
|
x2: angleCoords.x2 * width,
|
y2: angleCoords.y2 * height,
|
},
|
colorStops,
|
});
|
};
|
|
// 生成径向渐变
|
const generateRadial = (colorStops) => {
|
return new fabric.Gradient({
|
type: 'radial',
|
coords: {
|
x1: width / 2,
|
y1: height / 2,
|
r1: 0,
|
x2: width / 2,
|
y2: height / 2,
|
r2: width / 2,
|
},
|
colorStops,
|
});
|
};
|
|
let bgGradient = {};
|
const colorStops = [...handlers];
|
if (orientation === 'linear') {
|
bgGradient = generateLinear(colorStops);
|
} else if (orientation === 'radial') {
|
bgGradient = generateRadial(colorStops);
|
}
|
|
return bgGradient;
|
};
|
const props = defineProps({
|
angleKey: {
|
type: String,
|
default: 'gradientAngle',
|
},
|
color: {
|
type: [Object, String],
|
default: '',
|
},
|
});
|
const emitChange = defineEmits(['change']);
|
// const poptipCreated = ref(false);
|
// 是否渐变
|
const isGradient = ref(false);
|
// 纯色
|
const fill = ref('');
|
// 渐变
|
const bgStr = ref('background: linear-gradient(124deg, rgb(28, 27, 27) 0%, rgb(255, 0, 0) 100%);');
|
const currentGradient = reactive({
|
type: 'linear',
|
degree: 0,
|
points: [
|
{
|
left: 0,
|
red: 0,
|
green: 0,
|
blue: 0,
|
alpha: 1,
|
},
|
{
|
left: 100,
|
red: 255,
|
green: 0,
|
blue: 0,
|
alpha: 1,
|
},
|
],
|
});
|
// const onPoptipCreated = () => {
|
// poptipCreated.value = true;
|
// };
|
// 回显颜色
|
const checkColor = (val) => {
|
if (typeof val === 'string') {
|
isGradient.value = false;
|
fill.value = val;
|
} else {
|
// 渐变
|
isGradient.value = true;
|
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
|
if (activeObject) {
|
// 控件属性设置
|
fabricGradientToCss(val, activeObject);
|
// bar背景设置
|
fabricGradientToBar(val);
|
}
|
}
|
};
|
const changeGradientColor = debounce(function (val) {
|
const activeObject = canvasEditor.canvas.getActiveObjects()[0];
|
const { gradient } = val;
|
if (activeObject) {
|
const currentGradient = cssToFabricGradient(gradient, activeObject);
|
// TODO:
|
emitChange('change', currentGradient);
|
|
// 保存角度,用于下一次选中展示
|
activeObject.set(props.angleKey, gradient.degree);
|
setGradientBar(val);
|
}
|
}, 500);
|
// 设置渐变颜色条
|
const setGradientBar = (val) => {
|
if (val.gradient.type === 'linear') {
|
bgStr.value = `background: ${val.style};`;
|
} else {
|
bgStr.value = `background: ${val.style.replace('radial', 'linear')};`;
|
}
|
};
|
// Fabric渐变bar背景设置
|
const fabricGradientToBar = (val) => {
|
// 百分比排序
|
if (!val?.colorStops) return; // 防止从模板加载后出现colorStops报错
|
val.colorStops.sort((a, b) => a.offset - b.offset);
|
const str = val.colorStops.map((item) => `${item.color} ${item.offset * 100}%`);
|
bgStr.value = `background: linear-gradient(124deg, ${str});`;
|
};
|
// Fabric渐变转css
|
const fabricGradientToCss = (val, activeObject) => {
|
// 渐变类型
|
if (!val) return;
|
currentGradient.type = val.type;
|
currentGradient.degree = activeObject.get(props.angleKey, val.degree);
|
currentGradient.points = val.colorStops.map((item) => {
|
const [red, green, blue, alpha] = item.color.replace(/^rgba?\(|\s+|\)$/g, '').split(',');
|
return {
|
left: item.offset * 100,
|
red: Number(red),
|
green: Number(green),
|
blue: Number(blue),
|
alpha: Number(alpha),
|
};
|
});
|
};
|
// css转Fabric渐变
|
const cssToFabricGradient = (val, activeObject) => {
|
const handlers = val.points.map((item) => ({
|
offset: item.left / 100,
|
color: `rgba(${item.red}, ${item.green}, ${item.blue}, ${item.alpha})`,
|
}));
|
return generateFabricGradientFromColorStops(
|
handlers,
|
activeObject.width,
|
activeObject.height,
|
val.type,
|
val.degree
|
);
|
};
|
// 纯色颜色
|
const changePureColor = (val) => {
|
emitChange('change', val);
|
};
|
watch(
|
() => props.color,
|
(val) => {
|
checkColor(val);
|
}
|
);
|
onMounted(() => {
|
checkColor(props.color);
|
});
|
</script>
|
|
<style scoped lang="less">
|
.box {
|
padding: 10px 0;
|
}
|
|
// 渐变条
|
.gradient-bar {
|
width: 100%;
|
height: 30px;
|
cursor: pointer;
|
border-radius: 5px;
|
}
|
|
.switch {
|
margin-bottom: 10px;
|
}
|
|
// 提示弹框
|
:deep(.ivu-color-picker) {
|
display: block;
|
}
|
|
:deep(.ivu-poptip-body) {
|
padding: 5px;
|
}
|
|
:deep(.ivu-poptip) {
|
width: 100%;
|
|
.ivu-poptip-rel {
|
width: 100%;
|
}
|
}
|
|
// 渐变选择器
|
:deep(.ui-color-picker) {
|
.picker-area,
|
.gradient-controls,
|
.color-preview-area {
|
padding: 0;
|
}
|
border-radius: 10px;
|
padding: 8px;
|
margin: 0;
|
margin-top: 10px;
|
width: 100%;
|
}
|
</style>
|