|
<template> |
|
<div class="gradient-bar"> |
|
<div class="bar" ref="barRef" :style="{ backgroundImage: gradientStyle }" @click="$event => addPoint($event)"></div> |
|
<div class="point" |
|
:class="{ 'active': index === i }" |
|
v-for="(item, i) in points" |
|
:key="item.pos + '-' + i" |
|
:style="{ |
|
backgroundColor: item.color, |
|
left: `calc(${item.pos}% - 5px)`, |
|
}" |
|
@mousedown.left="movePoint(i)" |
|
@click.right="removePoint(i)" |
|
></div> |
|
</div> |
|
</template> |
|
|
|
<script lang="ts" setup> |
|
import type { GradientColor } from '@/types/slides' |
|
import { ref, computed, watchEffect } from 'vue' |
|
|
|
const props = defineProps<{ |
|
value: GradientColor[] |
|
index: number |
|
}>() |
|
|
|
const emit = defineEmits<{ |
|
(event: 'update:value', payload: GradientColor[]): void |
|
(event: 'update:index', payload: number): void |
|
}>() |
|
|
|
const points = ref<GradientColor[]>([]) |
|
|
|
const barRef = ref<HTMLElement>() |
|
|
|
watchEffect(() => { |
|
points.value = props.value |
|
if (props.index > props.value.length - 1) emit('update:index', 0) |
|
}) |
|
|
|
const gradientStyle = computed(() => { |
|
const list = points.value.map(item => `${item.color} ${item.pos}%`) |
|
return `linear-gradient(to right, ${list.join(',')})` |
|
}) |
|
|
|
const removePoint = (index: number) => { |
|
if (props.value.length <= 2) return |
|
|
|
let targetIndex = 0 |
|
|
|
if (index === props.index) { |
|
targetIndex = (index - 1 < 0) ? 0 : index - 1 |
|
} |
|
else if (props.index === props.value.length - 1) { |
|
targetIndex = props.value.length - 2 |
|
} |
|
|
|
const values = props.value.filter((item, _index) => _index !== index) |
|
emit('update:index', targetIndex) |
|
emit('update:value', values) |
|
} |
|
|
|
const movePoint = (index: number) => { |
|
let isMouseDown = true |
|
|
|
document.onmousemove = e => { |
|
if (!isMouseDown) return |
|
if (!barRef.value) return |
|
|
|
let pos = Math.round((e.clientX - barRef.value.getBoundingClientRect().left) / barRef.value.clientWidth * 100) |
|
if (pos > 100) pos = 100 |
|
if (pos < 0) pos = 0 |
|
|
|
points.value = points.value.map((item, _index) => { |
|
if (_index === index) return { ...item, pos } |
|
return item |
|
}) |
|
} |
|
document.onmouseup = () => { |
|
isMouseDown = false |
|
|
|
const point = points.value[index] |
|
const _points = [...points.value] |
|
_points.splice(index, 1) |
|
|
|
let targetIndex = 0 |
|
for (let i = 0; i < _points.length; i++) { |
|
if (point.pos > _points[i].pos) targetIndex = i + 1 |
|
} |
|
|
|
_points.splice(targetIndex, 0, point) |
|
|
|
emit('update:index', targetIndex) |
|
emit('update:value', _points) |
|
|
|
document.onmousemove = null |
|
document.onmouseup = null |
|
} |
|
} |
|
|
|
const addPoint = (e: MouseEvent) => { |
|
if (props.value.length >= 6) return |
|
if (!barRef.value) return |
|
const pos = Math.round((e.clientX - barRef.value.getBoundingClientRect().left) / barRef.value.clientWidth * 100) |
|
|
|
let targetIndex = 0 |
|
for (let i = 0; i < props.value.length; i++) { |
|
if (pos > props.value[i].pos) targetIndex = i + 1 |
|
} |
|
const color = props.value[targetIndex - 1] ? props.value[targetIndex - 1].color : props.value[targetIndex].color |
|
const values = [...props.value] |
|
values.splice(targetIndex, 0, { pos, color }) |
|
emit('update:index', targetIndex) |
|
emit('update:value', values) |
|
} |
|
</script> |
|
|
|
<style lang="scss" scoped> |
|
.gradient-bar { |
|
width: calc(100% - 10px); |
|
height: 18px; |
|
padding: 1px 0; |
|
margin: 3px 0; |
|
position: relative; |
|
left: 5px; |
|
|
|
.bar { |
|
height: 16px; |
|
border: 1px solid #d9d9d9; |
|
} |
|
.point { |
|
width: 10px; |
|
height: 18px; |
|
background-color: #fff; |
|
position: absolute; |
|
top: 0; |
|
border: 2px solid #fff; |
|
outline: 1px solid #d9d9d9; |
|
box-shadow: 0 0 2px 2px #d9d9d9; |
|
border-radius: 1px; |
|
cursor: pointer; |
|
|
|
&.active { |
|
outline: 1px solid $themeColor; |
|
box-shadow: 0 0 2px 2px $themeColor; |
|
} |
|
} |
|
} |
|
</style> |