2021-01-03 13:47:52 +08:00

258 lines
5.9 KiB
Vue

<template>
<div class="color-picker" @contextmenu.prevent>
<div class="picker-saturation-wrap">
<Saturation :value="color" :hue="hue" @change="value => changeColor(value)" />
</div>
<div class="picker-controls">
<div class="picker-color-wrap">
<div class="picker-current-color" :style="{ background: currentColor }"></div>
<Checkboard />
</div>
<div class="picker-sliders">
<div class="picker-hue-wrap">
<Hue :value="color" :hue="hue" @change="value => changeColor(value)" />
</div>
<div class="picker-alpha-wrap">
<Alpha :value="color" @change="value => changeColor(value)" />
</div>
</div>
</div>
<div class="picker-field">
<EditableInput :value="color" @change="value => changeColor(value)" />
</div>
<div class="picker-presets">
<div
class="picker-presets-color"
v-for="c in themeColors"
:key="c"
:style="{background: c}"
@click="selectPresetColor(c)"
></div>
</div>
<div class="picker-gradient-presets">
<div
class="picker-gradient-col"
v-for="(col, index) in presetColors"
:key="index"
>
<div class="picker-gradient-color"
v-for="c in col"
:key="c"
:style="{background: c}"
@click="selectPresetColor(c)"
></div>
</div>
</div>
<div class="picker-presets">
<div
v-for="c in standardColors"
:key="c"
class="picker-presets-color"
:style="{ background: c }"
@click="selectPresetColor(c)"
></div>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from 'vue'
import tinycolor, { ColorFormats } from 'tinycolor2'
import Alpha from './Alpha.vue'
import Checkboard from './Checkboard.vue'
import Hue from './Hue.vue'
import Saturation from './Saturation.vue'
import EditableInput from './EditableInput.vue'
const presetColorConfig = [
['#7f7f7f', '#f2f2f2'],
['#0d0d0d', '#808080'],
['#1c1a10', '#ddd8c3'],
['#0e243d', '#c6d9f0'],
['#233f5e', '#dae5f0'],
['#632623', '#f2dbdb'],
['#4d602c', '#eaf1de'],
['#3f3150', '#e6e0ec'],
['#1e5867', '#d9eef3'],
['#99490f', '#fee9da'],
]
const gradient = (startColor: string, endColor: string, step: number) => {
const _startColor = tinycolor(startColor).toRgb()
const _endColor = tinycolor(endColor).toRgb()
const rStep = (_endColor.r - _startColor.r) / step
const gStep = (_endColor.g - _startColor.g) / step
const bStep = (_endColor.b - _startColor.b) / step
const gradientColorArr = []
for(let i = 0; i < step; i++) {
const gradientColor = tinycolor({
r: _startColor.r + rStep * i,
g: _startColor.g + gStep * i,
b: _startColor.b + bStep * i,
}).toRgbString()
gradientColorArr.push(gradientColor)
}
return gradientColorArr
}
const getPresetColors = () => {
const presetColors = []
for(const color of presetColorConfig) {
presetColors.push(gradient(color[1], color[0], 5))
}
return presetColors
}
export default defineComponent({
name: 'color-picker',
components: {
Alpha,
Checkboard,
Hue,
Saturation,
EditableInput,
},
props: {
modelValue: {
type: String,
default: '#e86b99',
},
},
setup(props, { emit }) {
const hue = ref(0)
const color = computed({
get() {
return tinycolor(props.modelValue).toRgb()
},
set(rgba: ColorFormats.RGBA) {
const rgbaString = `rgba(${[rgba.r, rgba.g, rgba.b, rgba.a].join(',')})`
emit('update:modelValue', rgbaString)
},
})
const themeColors = ['#000000', '#ffffff', '#eeece1', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c']
const standardColors = ['#c21401', '#ff1e02', '#ffc12a', '#ffff3a', '#90cf5b', '#00af57', '#00afee', '#0071be', '#00215f', '#72349d']
const presetColors = getPresetColors()
const currentColor = computed(() => {
return `rgba(${[color.value.r, color.value.g, color.value.b, color.value.a].join(',')})`
})
const selectPresetColor = (colorString: string) => {
emit('update:modelValue', colorString)
}
const changeColor = (value: ColorFormats.RGBA | ColorFormats.HSLA | ColorFormats.HSVA) => {
if('h' in value) {
hue.value = value.h
color.value = tinycolor(value).toRgb()
}
else color.value = value
}
return {
themeColors,
standardColors,
presetColors,
color,
hue,
currentColor,
changeColor,
selectPresetColor,
}
},
})
</script>
<style lang="scss" scoped>
.color-picker {
position: relative;
width: 240px;
background: #fff;
user-select: none;
margin-bottom: -10px;
}
.picker-saturation-wrap {
width: 100%;
padding-bottom: 50%;
position: relative;
overflow: hidden;
}
.picker-controls {
display: flex;
}
.picker-sliders {
padding: 4px 0;
flex: 1;
}
.picker-hue-wrap {
position: relative;
height: 10px;
}
.picker-alpha-wrap {
position: relative;
height: 10px;
margin-top: 4px;
overflow: hidden;
}
.picker-color-wrap {
width: 24px;
height: 24px;
position: relative;
margin-top: 4px;
margin-right: 4px;
.checkerboard {
background-size: auto;
}
}
.picker-current-color {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
}
.picker-field {
margin-bottom: 8px;
}
.picker-presets {
@include grid-layout-wrapper();
}
.picker-presets-color {
@include grid-layout-item(10, 7%);
height: 0;
padding-bottom: 7%;
flex-shrink: 0;
position: relative;
cursor: pointer;
}
.picker-gradient-presets {
@include grid-layout-wrapper();
}
.picker-gradient-col {
@include grid-layout-item(10, 7%);
display: flex;
flex-direction: column;
}
.picker-gradient-color {
width: 100%;
height: 0;
padding-bottom: 100%;
position: relative;
cursor: pointer;
}
</style>