describe

Color-picker is a common form component, especially when changing background colors. Ant-design-vue doesn’t have a built-in color-picker. The community is more useful vuE-color, but currently does not support VUE3, here will be modified source code, so that it can be used in the VUE3 environment.

Technology stack

  • vue3
  • typescript
  • lodash
  • tinycolor2

The official start of the

First develop a few basic components, behind the different types of color pickers are combined in the basic components.

Based on the component

The partition of components is shown in the figure below

checkboard

A simple color palette, checkerboard by default

<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>Copy the code

alpha

Transparency, the main function according to the incoming color to generate gradient background, when the mouse moves the slider, calculated transparency

<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>Copy the code

hue

Color, display the default color plate, according to the incoming color to calculate the position of the slider, you can directly move the slider to change the current color

<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>Copy the code

saturation

Saturation, the main function of adjusting the light and dark color

<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>Copy the code

editable-input

Input box, display and change the input value, used to manually enter the exact color value

<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>Copy the code

chrome

Chrome color picker is usually touch more, you can see in the Chrome browser debug panel. Chrome is also a more complete component, using all of the above basic components.

<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>
Copy the code

Tinycolor is used for JS color control and conversion

conclusion

Preview address, account: [email protected], password: 123456.

Ts version source address

Js version of the source address

Color-picker uses documents

The color-picker plugin comes from ant-simple-Pro, which has many plug-ins developed with vue3+ TS. Ant-simple-pro is simple, beautiful and quick to use, supporting 3 big frames