描述
color-picker 在属于常见的表单组件,尤其在更换背景颜色这些地方用的多。ant-design-vue 没有自带的 color-picker 拾色器。社区有用的比较多的 vue-color,但是目前还不支持 vue3,这里将源码经过改造,让其可以在 vue3 环境中使用。
技术栈
- vue3
- typescript
- lodash
- tinycolor2
正式开始
先开发几个基础组件,后面的不同类型的拾色器都是在基础组件组合来的。
基础组件
组件的划分,见下图
checkboard
一个单纯的显示颜色的面板,默认为棋盘格面板
<template>
<div class="vc-checkerboard" :style="bgStyle" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
const checkboardCache: Record<string, string> = {}
/**
* get base 64 data by canvas
*
* @param {String} c1 hex color
* @param {String} c2 hex color
* @param {Number} size
*/
function renderCheckboard(c1: string, c2: string, size: number) {
// Dont Render On Server
if (typeof document === 'undefined') {
return null
}
const canvas = document.createElement('canvas')
canvas.width = canvas.height = size * 2
const ctx = canvas.getContext('2d')
// If no context can be found, return early.
if (!ctx) {
return null
}
ctx.fillStyle = c1
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = c2
ctx.fillRect(0, 0, size, size)
ctx.translate(size, size)
ctx.fillRect(0, 0, size, size)
return canvas.toDataURL()
}
/**
* get checkboard base data and cache
*
* @param {String} c1 hex color
* @param {String} c2 hex color
* @param {Number} size
*/
function getCheckboard(c1: string, c2: string, size: number) {
const key = c1 + ',' + c2 + ',' + size
if (checkboardCache[key]) {
return checkboardCache[key]
}
const checkboard = renderCheckboard(c1, c2, size)
if (checkboard === null) {
return null
}
checkboardCache[key] = checkboard
return checkboard
}
export default defineComponent({
props: {
size: {
type: Number,
default: 8
},
white: {
type: String,
default: '#fff'
},
grey: {
type: String,
default: '#e6e6e6'
}
},
computed: {
bgStyle() {
const checkboard = getCheckboard(this.white, this.grey, this.size)
if (checkboard === null) {
return {}
}
return {
'background-image': `url(${checkboard})`
}
}
}
})
</script>
<style>
.vc-checkerboard {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background-size: contain;
}
</style>
alpha
透明度,主要功能根据传入的颜色生成渐变色背景,当鼠标移动滑块时,计算出透明度
<template>
<div class="vc-alpha">
<div class="vc-alpha-checkboard-wrap">
<Checkboard />
</div>
<div class="vc-alpha-gradient" :style="{ background: gradientColor }" />
<div
ref="container"
class="vc-alpha-container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange"
>
<div class="vc-alpha-pointer" :style="{ left: colors.a * 100 + '%' }">
<div class="vc-alpha-picker" />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import Checkboard from './checkboard.vue'
import { ColorResult, AlphaChangeData } from '../../types'
export default defineComponent({
emits: ['change'],
components: {
Checkboard
},
props: {
value: {
type: Object as PropType<ColorResult>,
default: () => ({})
}
},
computed: {
colors(): ColorResult {
return this.value
},
gradientColor(): string {
const rgba = this.colors.rgba
const rgbStr = [rgba.r, rgba.g, rgba.b].join(',')
return 'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)'
}
},
methods: {
handleChange(e: MouseEvent | TouchEvent, skip?: boolean) {
!skip && e.preventDefault()
const container = this.$refs.container as HTMLElement
if (!container) {
// for some edge cases, container may not exist. see #220
return
}
const containerWidth = container.clientWidth
const xOffset = container.getBoundingClientRect().left + window.pageXOffset
const pageX = (e as MouseEvent).pageX || ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageX : 0)
const left = pageX - xOffset
let a = 1
if (left < 0) {
a = 0
} else if (left > containerWidth) {
a = 1
} else {
a = Math.round((left * 100) / containerWidth) / 100
}
if (this.colors.a !== a) {
const alphaData: AlphaChangeData = {
h: this.colors.hsl.h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a,
source: 'rgba'
}
this.$emit('change', alphaData)
}
},
handleMouseDown(e: MouseEvent | TouchEvent) {
this.handleChange(e, true)
window.addEventListener('mousemove', this.handleChange)
window.addEventListener('mouseup', this.handleMouseUp)
},
handleMouseUp() {
this.unbindEventListeners()
},
unbindEventListeners() {
window.removeEventListener('mousemove', this.handleChange)
window.removeEventListener('mouseup', this.handleMouseUp)
}
}
})
</script>
<style>
.vc-alpha {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.vc-alpha-checkboard-wrap {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
overflow: hidden;
}
.vc-alpha-gradient {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.vc-alpha-container {
cursor: pointer;
position: relative;
z-index: 2;
height: 100%;
margin: 0 3px;
-webkit-tap-highlight-color: transparent; /* for removing the highlight */
}
.vc-alpha-pointer {
z-index: 2;
position: absolute;
}
.vc-alpha-picker {
cursor: pointer;
width: 4px;
border-radius: 1px;
height: 8px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
background: #fff;
margin-top: 1px;
transform: translateX(-2px);
}
</style>
hue
色调,显示默认色板,根据传入颜色计算滑块的位置,可以直接移动滑块改变当前色调
<template>
<div :class="['vc-hue', directionClass]">
<div
class="vc-hue-container"
role="slider"
:aria-valuenow="colors.hsl.h"
aria-valuemin="0"
aria-valuemax="360"
ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange"
>
<div class="vc-hue-pointer" :style="{ top: pointerTop, left: pointerLeft }" role="presentation">
<div class="vc-hue-picker"></div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { ColorResult, HueChangeData } from '../../types'
export default defineComponent({
name: 'Hue',
emits: ['change'],
props: {
value: {
type: Object as PropType<ColorResult>,
default: () => ({})
},
direction: {
type: String as PropType<'horizontal' | 'vertical'>,
default: 'horizontal'
}
},
data() {
return {
oldHue: 0,
pullDirection: ''
}
},
watch: {
value(newVal: ColorResult) {
const h = newVal.hsl.h
if (h !== 0 && h - this.oldHue > 0) {
this.pullDirection = 'right'
}
if (h !== 0 && h - this.oldHue < 0) {
this.pullDirection = 'left'
}
this.oldHue = h
}
},
computed: {
colors(): ColorResult {
return this.value
},
directionClass(): Record<string, boolean> {
return {
'vc-hue--horizontal': this.direction === 'horizontal',
'vc-hue--vertical': this.direction === 'vertical'
}
},
pointerTop(): number | string {
if (this.direction === 'vertical') {
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') {
return 0
}
return -((this.colors.hsl.h * 100) / 360) + 100 + '%'
}
return 0
},
pointerLeft(): number | string {
if (this.direction === 'vertical') {
return 0
}
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') {
return '100%'
}
return (this.colors.hsl.h * 100) / 360 + '%'
}
},
methods: {
handleChange(e: MouseEvent | TouchEvent, skip?: boolean) {
!skip && e.preventDefault()
const container = this.$refs.container as HTMLElement
if (!container) {
// for some edge cases, container may not exist. see #220
return
}
const containerWidth = container.clientWidth
const containerHeight = container.clientHeight
const xOffset = container.getBoundingClientRect().left + window.pageXOffset
const yOffset = container.getBoundingClientRect().top + window.pageYOffset
const pageX = (e as MouseEvent).pageX || ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageX : 0)
const pageY = (e as MouseEvent).pageY || ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageY : 0)
const left = pageX - xOffset
const top = pageY - yOffset
let h = 0
let percent = 0
if (this.direction === 'vertical') {
if (top < 0) {
h = 360
} else if (top > containerHeight) {
h = 0
} else {
percent = -((top * 100) / containerHeight) + 100
h = (360 * percent) / 100
}
if (this.colors.hsl.h !== h) {
const hueData: HueChangeData = {
h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: this.colors.hsl.a,
source: 'hsl'
}
this.$emit('change', hueData)
}
} else {
if (left < 0) {
h = 0
} else if (left > containerWidth) {
h = 360
} else {
percent = (left * 100) / containerWidth
h = (360 * percent) / 100
}
if (this.colors.hsl.h !== h) {
const hueData: HueChangeData = {
h,
s: this.colors.hsl.s,
l: this.colors.hsl.l,
a: this.colors.hsl.a,
source: 'hsl'
}
this.$emit('change', hueData)
}
}
},
handleMouseDown(e: MouseEvent | TouchEvent) {
this.handleChange(e, true)
window.addEventListener('mousemove', this.handleChange)
window.addEventListener('mouseup', this.handleMouseUp)
},
handleMouseUp() {
this.unbindEventListeners()
},
unbindEventListeners() {
window.removeEventListener('mousemove', this.handleChange)
window.removeEventListener('mouseup', this.handleMouseUp)
}
}
})
</script>
<style>
.vc-hue {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
border-radius: 2px;
}
.vc-hue--horizontal {
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
.vc-hue--vertical {
background: linear-gradient(to top, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
.vc-hue-container {
cursor: pointer;
margin: 0 2px;
position: relative;
height: 100%;
}
.vc-hue-pointer {
z-index: 2;
position: absolute;
}
.vc-hue-picker {
cursor: pointer;
margin-top: 1px;
width: 4px;
border-radius: 1px;
height: 8px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
background: #fff;
transform: translateX(-2px);
}
</style>
saturation
饱和度,主要功能调节颜色的亮暗
<template>
<div
class="vc-saturation"
:style="{ background: bgColor }"
ref="container"
@mousedown="handleMouseDown"
@touchmove="handleChange"
@touchstart="handleChange"
>
<div class="vc-saturation--white"></div>
<div class="vc-saturation--black"></div>
<div class="vc-saturation-pointer" :style="{ top: pointerTop, left: pointerLeft }">
<div class="vc-saturation-circle"></div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { clamp } from '../../helpers/utils'
import { throttle } from 'lodash'
import { ColorResult, SaturationChangeData } from '../../types'
export default defineComponent({
name: 'Saturation',
emits: ['change'],
props: {
value: {
type: Object as PropType<ColorResult>,
default: () => ({})
}
},
computed: {
colors(): ColorResult {
return this.value
},
bgColor(): string {
return `hsl(${this.colors.hsv.h}, 100%, 50%)`
},
pointerTop(): string {
return -(this.colors.hsv.v * 100) + 1 + 100 + '%'
},
pointerLeft(): string {
return this.colors.hsv.s * 100 + '%'
}
},
methods: {
throttle: throttle(
(fn, data) => {
fn(data)
},
20,
{
leading: true,
trailing: false
}
),
handleChange(e: MouseEvent | TouchEvent, skip?: boolean) {
!skip && e.preventDefault()
const container = this.$refs.container as HTMLElement
if (!container) {
// for some edge cases, container may not exist. see #220
return
}
const containerWidth = container.clientWidth
const containerHeight = container.clientHeight
const xOffset = container.getBoundingClientRect().left + window.pageXOffset
const yOffset = container.getBoundingClientRect().top + window.pageYOffset
const pageX = (e as MouseEvent).pageX || ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageX : 0)
const pageY = (e as MouseEvent).pageY || ((e as TouchEvent).touches ? (e as TouchEvent).touches[0].pageY : 0)
const left = clamp(pageX - xOffset, 0, containerWidth)
const top = clamp(pageY - yOffset, 0, containerHeight)
const saturation = left / containerWidth
const bright = clamp(-(top / containerHeight) + 1, 0, 1)
const saturationData: SaturationChangeData = {
h: this.colors.hsv.h,
s: saturation,
v: bright,
a: this.colors.hsv.a,
source: 'hsva'
}
this.throttle(this.onChange, saturationData)
},
onChange(param: SaturationChangeData) {
this.$emit('change', param)
},
handleMouseDown() {
window.addEventListener('mousemove', this.handleChange)
window.addEventListener('mouseup', this.handleChange)
window.addEventListener('mouseup', this.handleMouseUp)
},
handleMouseUp() {
this.unbindEventListeners()
},
unbindEventListeners() {
window.removeEventListener('mousemove', this.handleChange)
window.removeEventListener('mouseup', this.handleChange)
window.removeEventListener('mouseup', this.handleMouseUp)
}
}
})
</script>
<style>
.vc-saturation,
.vc-saturation--white,
.vc-saturation--black {
cursor: pointer;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.vc-saturation--white {
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.vc-saturation--black {
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
}
.vc-saturation-pointer {
cursor: pointer;
position: absolute;
}
.vc-saturation-circle {
cursor: head;
width: 4px;
height: 4px;
box-shadow: 0 0 0 1.5px #fff, inset 0 0 1px 1px rgba(0, 0, 0, 0.3), 0 0 1px 2px rgba(0, 0, 0, 0.4);
border-radius: 50%;
transform: translate(-2px, -2px);
}
</style>
editable-input
输入框,显示和改变输入值,用来手动输入精确的色值
<template>
<div class="vc-editable-input">
<input
:aria-labelledby="labelId"
class="vc-input__input"
v-model="val"
@keydown="handleKeyDown"
@input="update"
ref="input"
/>
<span :for="label" class="vc-input__label" :id="labelId">{{ labelSpanText }}</span>
<span class="vc-input__desc">{{ desc }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'editableInput',
emits: ['change'],
props: {
label: String,
labelText: String,
desc: String,
value: {
type: [String, Number],
required: true
},
max: Number,
min: Number,
arrowOffset: {
type: Number,
default: 1
}
},
computed: {
val: {
get(): string | number {
return this.value
},
set(v: any) {
// TODO: min
if (!(this.max === undefined) && +v > this.max) {
;(this.$refs.input as HTMLInputElement).value = String(this.max)
return
}
return v
}
},
labelId(): string {
return `input__label__${this.label}__${Math.random().toString().slice(2, 5)}`
},
labelSpanText(): string {
return this.labelText || this.label || ''
}
},
methods: {
update(e: MouseEvent) {
this.handleChange((e.target as HTMLInputElement).value)
},
handleChange(newVal: string) {
const data: Record<string, string> = {}
if (this.label) {
data[this.label] = newVal
}
if (data.hex === undefined && data['#'] === undefined) {
this.$emit('change', data)
} else if (newVal.length > 5) {
this.$emit('change', data)
}
},
handleKeyDown(e: KeyboardEvent) {
let val = this.val
const number = Number(val)
if (number) {
const amount = this.arrowOffset || 1
// Up
if (e.keyCode === 38) {
val = number + amount
this.handleChange(String(val))
e.preventDefault()
}
// Down
if (e.keyCode === 40) {
val = number - amount
this.handleChange(String(val))
e.preventDefault()
}
}
}
}
})
</script>
<style>
.vc-editable-input {
position: relative;
}
.vc-input__input {
padding: 0;
border: 0;
outline: none;
}
.vc-input__label {
text-transform: capitalize;
}
</style>
chrome
chrome 的拾色器是平时接触的比较多的,在 Chrome 浏览器的调试面板可以看到。chrome 也是比较全的组件,用上了上面的全部基础组件。
<template>
<div
role="application"
aria-label="Chrome color picker"
:class="['vc-chrome', disableAlpha ? 'vc-chrome__disable-alpha' : '']"
>
<div class="vc-chrome-saturation-wrap">
<saturation v-model:value="colors" @change="childChange"></saturation>
</div>
<div class="vc-chrome-body">
<div class="vc-chrome-controls">
<div class="vc-chrome-color-wrap">
<div
:aria-label="`current color is ${colors.hex}`"
class="vc-chrome-active-color"
:style="{ background: activeColor }"
></div>
<checkboard v-if="!disableAlpha"></checkboard>
</div>
<div class="vc-chrome-sliders">
<div class="vc-chrome-hue-wrap">
<hue v-model:value="colors" @change="childChange"></hue>
</div>
<div class="vc-chrome-alpha-wrap" v-if="!disableAlpha">
<alpha v-model:value="colors" @change="childChange"></alpha>
</div>
</div>
</div>
<div class="vc-chrome-fields-wrap" v-if="!disableFields">
<div class="vc-chrome-fields" v-show="fieldsIndex === 0">
<!-- hex -->
<div class="vc-chrome-field">
<ed-in v-if="!hasAlpha" label="hex" :value="colors.hex" @change="inputChange"></ed-in>
<ed-in v-if="hasAlpha" label="hex" :value="colors.hex8" @change="inputChange"></ed-in>
</div>
</div>
<div class="vc-chrome-fields" v-show="fieldsIndex === 1">
<!-- rgba -->
<div class="vc-chrome-field">
<ed-in label="r" :value="colors.rgba.r" @change="inputChange"></ed-in>
</div>
<div class="vc-chrome-field">
<ed-in label="g" :value="colors.rgba.g" @change="inputChange"></ed-in>
</div>
<div class="vc-chrome-field">
<ed-in label="b" :value="colors.rgba.b" @change="inputChange"></ed-in>
</div>
<div class="vc-chrome-field" v-if="!disableAlpha">
<ed-in label="a" :value="colors.a" :arrow-offset="0.01" :max="1" @change="inputChange"></ed-in>
</div>
</div>
<div class="vc-chrome-fields" v-show="fieldsIndex === 2">
<!-- hsla -->
<div class="vc-chrome-field">
<ed-in label="h" :value="hsl.h" @change="inputChange"></ed-in>
</div>
<div class="vc-chrome-field">
<ed-in label="s" :value="hsl.s" @change="inputChange"></ed-in>
</div>
<div class="vc-chrome-field">
<ed-in label="l" :value="hsl.l" @change="inputChange"></ed-in>
</div>
<div class="vc-chrome-field" v-if="!disableAlpha">
<ed-in label="a" :value="colors.a" :arrow-offset="0.01" :max="1" @change="inputChange"></ed-in>
</div>
</div>
<!-- btn -->
<div
class="vc-chrome-toggle-btn"
role="button"
aria-label="Change another color definition"
@click="toggleViews"
>
<div class="vc-chrome-toggle-icon">
<svg
style="width: 24px; height: 24px"
viewBox="0 0 24 24"
@mouseover="showHighlight"
@mouseenter="showHighlight"
@mouseout="hideHighlight"
>
<path
fill="#333"
d="M12,18.17L8.83,15L7.42,16.41L12,21L16.59,16.41L15.17,15M12,5.83L15.17,9L16.58,7.59L12,3L7.41,7.59L8.83,9L12,5.83Z"
/>
</svg>
</div>
<div class="vc-chrome-toggle-icon-highlight" v-show="highlight"></div>
</div>
<!-- btn -->
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import EditableInput from './common/editable-input.vue'
import Saturation from './common/saturation.vue'
import Hue from './common/hue.vue'
import Alpha from './common/alpha.vue'
import Checkboard from './common/checkboard.vue'
import {
ColorResult,
SaturationChangeData,
HueChangeData,
AlphaChangeData,
HEXChangeData,
RGBAChangeData,
HSLChangeData,
ColorChangeValue
} from '../types'
import { getChangeColor, isValidHex } from '../helpers/utils'
export default defineComponent({
name: 'Chrome',
emits: ['update:value'],
components: {
Saturation,
Hue,
Alpha,
'ed-in': EditableInput,
Checkboard
},
props: {
disableAlpha: {
type: Boolean,
default: false
},
disableFields: {
type: Boolean,
default: false
},
value: {
type: [Object, String],
default: () => ({})
}
},
data() {
return {
fieldsIndex: 0,
highlight: false,
val: getChangeColor(this.value),
oldHue: 0
}
},
watch: {
value(newVal) {
this.val = getChangeColor(newVal)
}
},
computed: {
colors: {
get(): ColorResult {
return this.val
},
set(newVal: any) {
this.val = newVal
this.$emit('update:value', newVal)
}
},
hsl(): Record<string, string> {
const { h, s, l } = this.colors.hsl
return {
h: Number(h).toFixed(),
s: `${(s * 100).toFixed()}%`,
l: `${(l * 100).toFixed()}%`
}
},
activeColor(): string {
const rgba = this.colors.rgba
return 'rgba(' + [rgba.r, rgba.g, rgba.b, rgba.a].join(',') + ')'
},
hasAlpha(): boolean {
return this.colors.a < 1
}
},
methods: {
colorChange(data: ColorChangeValue, oldHue?: number) {
this.oldHue = this.colors.hsl.h
this.colors = getChangeColor(data, oldHue || this.oldHue)
this.$emit('update:value', this.colors.hex)
},
simpleCheckForValidColor(data: any) {
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v']
let checked = 0
let passed = 0
for (let i = 0; i < keysToCheck.length; i++) {
const letter = keysToCheck[i]
if (data[letter]) {
checked++
if (!isNaN(data[letter])) {
passed++
}
}
}
if (checked === passed) {
return data
}
},
childChange(data: SaturationChangeData | HueChangeData | AlphaChangeData) {
this.colorChange(data)
},
inputChange(data: Record<string, string>) {
if (!data) {
return
}
if (data.hex) {
if (isValidHex(data.hex)) {
const hexData: HEXChangeData = {
hex: data.hex,
source: 'hex'
}
this.colorChange(hexData)
}
} else if (data.r || data.g || data.b || data.a) {
const rgbaData: RGBAChangeData = {
r: Number(data.r) || this.colors.rgba.r,
g: Number(data.g) || this.colors.rgba.g,
b: Number(data.b) || this.colors.rgba.b,
a: Number(data.a) || this.colors.rgba.a || 1,
source: 'rgba'
}
this.colorChange(rgbaData)
} else if (data.h || data.s || data.l) {
const s = data.s ? Number(data.s.replace('%', '')) / 100 : this.colors.hsl.s
const l = data.l ? Number(data.l.replace('%', '')) / 100 : this.colors.hsl.l
const hslData: HSLChangeData = {
h: Number(data.h) || this.colors.hsl.h,
s,
l,
source: 'hsl'
}
this.colorChange(hslData)
}
},
toggleViews() {
if (this.fieldsIndex >= 2) {
this.fieldsIndex = 0
return
}
this.fieldsIndex++
},
showHighlight() {
this.highlight = true
},
hideHighlight() {
this.highlight = false
}
}
})
</script>
<style>
.vc-chrome {
background: #fff;
border-radius: 2px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.3);
box-sizing: initial;
width: 225px;
font-family: Menlo;
background-color: #fff;
}
.vc-chrome-controls {
display: flex;
}
.vc-chrome-color-wrap {
position: relative;
width: 36px;
}
.vc-chrome-active-color {
position: relative;
width: 30px;
height: 30px;
border-radius: 15px;
overflow: hidden;
z-index: 1;
}
.vc-chrome-color-wrap .vc-checkerboard {
width: 30px;
height: 30px;
border-radius: 15px;
background-size: auto;
}
.vc-chrome-sliders {
flex: 1;
}
.vc-chrome-fields-wrap {
display: flex;
padding-top: 16px;
}
.vc-chrome-fields {
display: flex;
margin-left: -6px;
flex: 1;
}
.vc-chrome-field {
padding-left: 6px;
width: 100%;
}
.vc-chrome-toggle-btn {
width: 32px;
text-align: right;
position: relative;
}
.vc-chrome-toggle-icon {
margin-right: -4px;
margin-top: 12px;
cursor: pointer;
position: relative;
z-index: 2;
}
.vc-chrome-toggle-icon-highlight {
position: absolute;
width: 24px;
height: 28px;
background: #eee;
border-radius: 4px;
top: 10px;
left: 12px;
}
.vc-chrome-hue-wrap {
position: relative;
height: 10px;
margin-bottom: 8px;
}
.vc-chrome-alpha-wrap {
position: relative;
height: 10px;
}
.vc-chrome-hue-wrap .vc-hue {
border-radius: 2px;
}
.vc-chrome-alpha-wrap .vc-alpha-gradient {
border-radius: 2px;
}
.vc-chrome-hue-wrap .vc-hue-picker,
.vc-chrome-alpha-wrap .vc-alpha-picker {
width: 12px;
height: 12px;
border-radius: 6px;
transform: translate(-6px, -2px);
background-color: rgb(248, 248, 248);
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
}
.vc-chrome-body {
padding: 16px 16px 12px;
background-color: #fff;
}
.vc-chrome-saturation-wrap {
width: 100%;
padding-bottom: 55%;
position: relative;
border-radius: 2px 2px 0 0;
overflow: hidden;
}
.vc-chrome-saturation-wrap .vc-saturation-circle {
width: 12px;
height: 12px;
}
.vc-chrome-fields .vc-input__input {
font-size: 11px;
color: #333;
width: 100%;
border-radius: 2px;
border: none;
box-shadow: inset 0 0 0 1px #dadada;
height: 21px;
text-align: center;
}
.vc-chrome-fields .vc-input__label {
text-transform: uppercase;
font-size: 11px;
line-height: 11px;
color: #969696;
text-align: center;
display: block;
margin-top: 12px;
}
.vc-chrome__disable-alpha .vc-chrome-active-color {
width: 18px;
height: 18px;
}
.vc-chrome__disable-alpha .vc-chrome-color-wrap {
width: 30px;
}
.vc-chrome__disable-alpha .vc-chrome-hue-wrap {
margin-top: 4px;
margin-bottom: 4px;
}
</style>
总结
预览地址,账号:lgf@163.com,密码:123456。
color-picker插件来自ant-simple-pro里面,ant-simple-pro有很多用vue3+ts开发的插件。ant-simple-pro简洁,美观,快速上手,支持3大框架