123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- <template>
- <!-- 取色器 -->
- <div class="color-select">
- <div class="saturation-value" ref="saturation_value" @mousedown="mousedownColorPalette">
- <div :style="`background-color: hsl(${hue}, 100%, 50%);`">
- <div class="point" :style="pointStyle"></div>
- </div>
- <div class="saturation-value-2"></div>
- <div class="saturation-value-3"></div>
- </div>
- <div class="color-select-middle">
- <div class="color-slider">
- <div class="hue-slider slider-item" ref="hue_slider" @mousedown="mousedownHue">
- <div class="slider" :style="hueSliderStyle"></div>
- </div>
- <div class="alpha-slider slider-item" ref="alpha_slider" @mousedown="mousedownAlpha" v-if="props.alpha">
- <div class="slider" :style="alphaSliderStyle"></div>
- <div
- :style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorEnums.rgb});width: 100%;height: 100%`">
- </div>
- </div>
- </div>
- <div class="color-diamond">
- <div
- :style="`background-color: ${colorEnums.rgba};width: 100%;height: 100%;box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15), inset 0 0 4px rgba(0, 0, 0, .25);`">
- </div>
- </div>
- </div>
- <div class="color-value">
- <div class="hex">
- <label>
- <input :value="colorEnums.hex8" @input="hexChange" spellcheck="false" />
- </label>
- <p>Hex</p>
- </div>
- <div class="rgba-r">
- <label>
- <input :value="red" @input="redChange" />
- </label>
- <p>R</p>
- </div>
- <div class="rgba-g">
- <label>
- <input :value="green" @input="greenChange" />
- </label>
- <p>G</p>
- </div>
- <div class="rgba-b">
- <label>
- <input :value="blue" @input="blueChange" />
- </label>
- <p>B</p>
- </div>
- <div class="rgba-a" v-if="props.alpha">
- <label>
- <input :value="alpha" @input="alphaChange" />
- </label>
- <p>A</p>
- </div>
- </div>
- <ul class="predefine">
- <li class="predefine-item" v-for="(item, index) in predefine" :key="index" :style="`background-color: ${item}`"
- @click="predefineChange(item)"></li>
- </ul>
- <div class="color-actions">
- <span class="cancel" @click="emits('close')">取消</span>
- <span class="confirm" @click="handleConfirm">确定</span>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, watch, onMounted } from 'vue'
- const props = defineProps({
- color: {
- type: Object || String,
- default() {
- return {
- r: 217,
- g: 128,
- b: 95,
- a: 1
- }
- }
- },
- predefine: {
- type: Array,
- default() {
- return []
- }
- },
- alpha: {
- type: Boolean,
- default: true
- },
- mode: {
- type: String,
- default: 'hex6' // hex6/hex8/rgb/rgba
- }
- })
- const emits = defineEmits(['update:color', 'close', 'handleConfirm'])
- const saturation_value: any = ref(null)
- const hue_slider: any = ref(null)
- const alpha_slider: any = ref(null)
- let pointStyle: any = ref('top: 25%;left: 80%;')
- let hueSliderStyle: any = ref('left: 0;')
- let alphaSliderStyle: any = ref('left: calc(100% - 6px);')
- let hue: any = ref(0)
- let saturation: any = ref(1)
- let value: any = ref(1)
- let red: any = ref(255)
- let green: any = ref(0)
- let blue: any = ref(0)
- let alpha: any = ref(1)
- onMounted(() => {
- // console.log('parseColor(props.color)', parseColor(props.color))
- let { r, g, b, a }: any = parseColor(props.color)
- red.value = r
- green.value = g
- blue.value = b
- alpha.value = a
- })
- watch(() => props.color, (newValue) => {
- if (newValue) {
- let { r, g, b, a }: any = parseColor(newValue)
- red.value = r
- green.value = g
- blue.value = b
- alpha.value = a
- }
- }, { immediate: true, deep: true })
- watch([red, green, blue], () => {
- let { h, s, v } = rgb2hsv(red.value, green.value, blue.value)
- hue.value = h
- saturation.value = s
- value.value = v
- // 移动背景板圆圈
- pointStyle.value = `top: ${100 - v * 100}%;left: ${s * 100}%;`
- // 移动色调滑块
- hueSliderStyle.value = `left: ${(hue.value / 360) * 100}%;`
- }, { immediate: true, deep: true })
- watch(alpha, () => {
- // 移动透明度滑块
- alphaSliderStyle.value = `left: ${alpha.value >= 1 ? 'calc(100% - 6px)' : alpha.value * 100 + '%'};`
- })
- let colorEnums = computed(() => {
- let r = red.value
- let g = green.value
- let b = blue.value
- let a = alpha.value
- let h = hue.value
- let s = saturation.value
- let v = value.value
- return {
- rgb: `rgba(${r},${g},${b})`,
- rgba: `rgba(${r}, ${g}, ${b}, ${a})`,
- hex6: rgba2hex(r, g, b),
- hex8: rgba2hex(r, g, b, a),
- hsv: `hsv(${h},${s},${v})`
- }
- })
- // 确认选中的颜色值
- const handleConfirm = () => {
- // console.log('props.mode', props.mode)
- // console.log('handleConfirm', (colorEnums.value as any)[props.mode])
- emits('update:color', (colorEnums.value as any)[props.mode])
- emits('handleConfirm', (colorEnums.value as any)[props.mode])
- }
- // 输入框值变化,限制输入的值
- function hexChange(e) {
- let v = e.target.value
- if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
- let { r, g, b, a }: any = hex2rgba(v)
- red.value = r
- green.value = g
- blue.value = b
- alpha.value = a
- }
- }
- function redChange(e) {
- let v = e.target.value
- if (v !== '') {
- v > 255 && (red.value = 255)
- v < 0 && (red.value = 0)
- v >= 0 && v <= 255 && (red.value = parseInt(v))
- }
- }
- function greenChange(e) {
- let v = e.target.value
- if (v !== '') {
- v > 255 && (green.value = 255)
- v < 0 && (green.value = 0)
- v >= 0 && v <= 255 && (green.value = parseInt(v))
- }
- }
- function blueChange(e) {
- let v = e.target.value
- if (v !== '') {
- v > 255 && (blue.value = 255)
- v < 0 && (blue.value = 0)
- v >= 0 && v <= 255 && (blue.value = parseInt(v))
- }
- }
- function alphaChange(e) {
- let v = e.target.value
- if (v !== '') {
- v = parseFloat(v)
- alpha.value = v
- v > 1 && (alpha.value = 1)
- v < 0 && (alpha.value = 0)
- v >= 0 && v <= 1 && (alpha.value = v)
- }
- }
- // 点击预设方块事件
- function predefineChange(item) {
- if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(item)) {
- let { r, g, b, a }: any = hex2rgba(item)
- red.value = r
- green.value = g
- blue.value = b
- alpha.value = a
- }
- }
- // 计算选中点的颜色值
- function handleChangeColorPalette(e) {
- let w = saturation_value.value.clientWidth
- let h = saturation_value.value.clientHeight
- let x = e.pageX - saturation_value.value.getBoundingClientRect().left
- let y = e.pageY - saturation_value.value.getBoundingClientRect().top
- x = x < w && x > 0 ? x : x > w ? w : 0
- y = y < h && y > 0 ? y : y > h ? h : 0
- // 计算饱和度和亮度
- saturation.value = Math.floor((x / w) * 100 + 0.5) / 100
- value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100
- // hsv转化为rgb
- let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value)
- red.value = r
- green.value = g
- blue.value = b
- // 移动背景板圆圈
- pointStyle.value = `top: ${y}px;left: ${x}px;`
- }
- function mousedownColorPalette(e) {
- // 鼠标按下计算饱和度和亮度并添加事件
- handleChangeColorPalette(e)
- // 添加整个页面的鼠标事件
- window.addEventListener('mousemove', handleChangeColorPalette)
- window.addEventListener('mouseup', mouseupColorPalette)
- }
- function mouseupColorPalette() {
- // 鼠标松开后移除事件
- window.removeEventListener('mousemove', handleChangeColorPalette)
- window.removeEventListener('mouseup', mouseupColorPalette)
- }
- // 色调
- function handleChangeHue(e) {
- let w = hue_slider.value.clientWidth
- let x = e.pageX - saturation_value.value.getBoundingClientRect().left
- x = x < w && x > 0 ? x : x > w ? w : 0
- // 计算色调
- hue.value = Math.floor((x / w) * 360 + 0.5)
- // hsv转化为rgb
- let { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value)
- red.value = r
- green.value = g
- blue.value = b
- // 移动滑块
- hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`
- }
- function mousedownHue(e) {
- handleChangeHue(e)
- window.addEventListener('mousemove', handleChangeHue)
- window.addEventListener('mouseup', mouseupHue)
- }
- function mouseupHue() {
- window.removeEventListener('mousemove', handleChangeHue)
- window.removeEventListener('mouseup', mouseupHue)
- }
- // 透明度
- function handleChangeAlpha(e) {
- let w = alpha_slider.value.clientWidth
- let x = e.pageX - saturation_value.value.getBoundingClientRect().left
- x = x < w && x > 0 ? x : x > w ? w : 0
- // 计算透明度
- alpha.value = Math.floor((x / w) * 100 + 0.5) / 100
- // 移动滑块
- alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`
- }
- function mousedownAlpha(e) {
- handleChangeAlpha(e)
- window.addEventListener('mousemove', handleChangeAlpha)
- window.addEventListener('mouseup', mouseupAlpha)
- }
- function mouseupAlpha() {
- window.removeEventListener('mousemove', handleChangeAlpha)
- window.removeEventListener('mouseup', mouseupAlpha)
- }
- /**
- * 解析输入的数据,只能解析hex颜色和rgb对象形式的数据
- * @param color
- */
- function parseColor(color) {
- if (color) {
- let r, g, b, a
- if (typeof color === 'string') {
- if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3}|[0-9a-fA-F]{4})$/.test(color)) {
- return hex2rgba(color)
- } else if (color.includes('linear-gradient')) {
- // console.log('111parseColor111', color)
- let matchColors = color.match(/#[0-9a-fA-F]{6}/g)
- // console.log('matchColors', matchColors)
- let avgColor = getAvgColor(matchColors)
- // console.log('avgColor', avgColor)
- return hex2rgba(avgColor)
- }
- } else {
- r = color.r > 255 ? 255 : color.r < 0 ? 0 : color.r
- g = color.g > 255 ? 255 : color.g < 0 ? 0 : color.g
- b = color.b > 255 ? 255 : color.b < 0 ? 0 : color.b
- a = color.a > 1 ? 1 : color.a < 0 ? 0 : color.a
- return { r, g, b, a }
- }
- } else {
- return null
- }
- }
- function hsv2rgb(h, s, v) {
- h === 360 && (h = 0)
- let i = Math.floor(h / 60) % 6
- let f = h / 60 - i
- let p = v * (1 - s)
- let q = v * (1 - s * f)
- let t = v * (1 - s * (1 - f))
- let r, g, b
- if (i === 0) {
- r = v
- g = t
- b = p
- } else if (i === 1) {
- r = q
- g = v
- b = p
- } else if (i === 2) {
- r = p
- g = v
- b = t
- } else if (i === 3) {
- r = p
- g = q
- b = v
- } else if (i === 4) {
- r = t
- g = p
- b = v
- } else if (i === 5) {
- r = v
- g = p
- b = q
- }
- r = Math.floor(r * 255 + 0.5)
- g = Math.floor(g * 255 + 0.5)
- b = Math.floor(b * 255 + 0.5)
- return { r, g, b }
- }
- function rgb2hsv(r, g, b) {
- let r1 = r / 255
- let g1 = g / 255
- let b1 = b / 255
- let cmax = Math.max(r1, g1, b1)
- let cmin = Math.min(r1, g1, b1)
- let d = cmax - cmin
- let h, s, v
- if (d === 0) {
- h = 0
- } else if (cmax === r1) {
- h = ((60 * (g1 - b1)) / d + 360) % 360
- } else if (cmax === g1) {
- h = 60 * ((b1 - r1) / d + 2)
- } else if (cmax === b1) {
- h = 60 * ((r1 - g1) / d + 4)
- }
- if (cmax === 0) {
- s = 0
- } else {
- s = d / cmax
- }
- v = cmax
- h = Math.floor(h + 0.5)
- s = Math.floor(s * 100 + 0.5) / 100
- v = Math.floor(v * 100 + 0.5) / 100
- return { h, s, v }
- }
- function rgba2hex(r, g, b, a: any = 1) {
- r = parseInt(r)
- let r1 = r.toString(16).length !== 2 ? '0' + r.toString(16) : r.toString(16)
- g = parseInt(g)
- let g1 = g.toString(16).length !== 2 ? '0' + g.toString(16) : g.toString(16)
- b = parseInt(b)
- let b1 = b.toString(16).length !== 2 ? '0' + b.toString(16) : b.toString(16)
- a = parseFloat(a)
- let a1 = ''
- if (a !== 1) {
- let temp = Math.floor(256 * a)
- a1 = temp.toString(16).length !== 2 ? '0' + temp.toString(16) : temp.toString(16)
- }
- return `#${r1}${g1}${b1}${a1}`.toUpperCase()
- }
- function hex2rgba(s) {
- // console.log('111111', s)
- if (/^#?[0-9a-fA-F]{3}$/.test(s)) {
- let b = s.substring(s.length - 1, s.length)
- let g = s.substring(s.length - 2, s.length - 1)
- let r = s.substring(s.length - 3, s.length - 2)
- return hex2rgba(`${r + r}${g + g}${b + b}`)
- }
- if (/^#?[0-9a-fA-F]{4}$/.test(s)) {
- let a = s.substring(s.length - 1, s.length)
- let b = s.substring(s.length - 2, s.length - 1)
- let g = s.substring(s.length - 3, s.length - 2)
- let r = s.substring(s.length - 4, s.length - 3)
- return hex2rgba(`${r + r}${g + g}${b + b}${a + a}`)
- }
- if (/^#?[0-9a-fA-F]{6}$/.test(s)) {
- let b = parseInt('0x' + s.substring(s.length - 2, s.length))
- let g = parseInt('0x' + s.substring(s.length - 4, s.length - 2))
- let r = parseInt('0x' + s.substring(s.length - 6, s.length - 4))
- return { r, g, b, a: 1 }
- }
- if (/^#?[0-9a-fA-F]{8}$/.test(s)) {
- let a = parseInt('0x' + s.substring(s.length - 2, s.length))
- a = a / 255
- let b = parseInt('0x' + s.substring(s.length - 4, s.length - 2))
- let g = parseInt('0x' + s.substring(s.length - 6, s.length - 4))
- let r = parseInt('0x' + s.substring(s.length - 8, s.length - 6))
- return { r, g, b, a }
- }
- }
- function getAvgColor(arr) {
- try {
- let parseColor = function (hexStr) {
- return hexStr.length === 4
- ? hexStr
- .substr(1)
- .split('')
- .map(function (s) {
- return 0x11 * parseInt(s, 16)
- })
- : [hexStr.substr(1, 2), hexStr.substr(3, 2), hexStr.substr(5, 2)].map(function (s) {
- return parseInt(s, 16)
- })
- }
- let pad = function (s) {
- return s.length === 1 ? '0' + s : s
- }
- let gradientColors: any = function (start, end, steps, gamma) {
- let i
- let j
- let ms
- let me
- let output: any = []
- let so: any = []
- gamma = gamma || 1
- let normalize = function (channel) {
- return Math.pow(channel / 255, gamma)
- }
- start = parseColor(start).map(normalize)
- end = parseColor(end).map(normalize)
- for (i = 0; i < steps; i++) {
- ms = i / (steps - 1)
- me = 1 - ms
- for (j = 0; j < 3; j++) {
- so[j] = pad(Math.round(Math.pow(start[j] * me + end[j] * ms, 1 / gamma) * 255).toString(16))
- }
- output.push('#' + so.join(''))
- }
- return output
- }
- return gradientColors(arr[0], arr[1], 3)[1]
- } catch (err) {
- return arr[0]
- }
- }
- </script>
- <style lang="scss" scoped>
- .color-select {
- position: relative;
- user-select: none;
- width: 300px;
- background: #fff;
- padding: 10px;
- /*border: 1px solid #ccc;*/
- /*border-radius: 10px;*/
- }
- /* 饱和度和亮度 */
- .saturation-value {
- cursor: pointer;
- width: 100%;
- height: 200px;
- position: relative;
- margin-bottom: 10px;
- box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
- }
- .saturation-value>div {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
- /* 圆圈 */
- .point {
- box-sizing: border-box;
- width: 6px;
- height: 6px;
- background-color: transparent;
- border: 2px solid #ccc;
- border-radius: 50%;
- transform: translate(-50%, -50%);
- position: absolute;
- z-index: 9;
- }
- .saturation-value-2 {
- background: linear-gradient(to right, white, #ffffff00);
- }
- .saturation-value-3 {
- background: linear-gradient(to top, black, #ffffff00);
- }
- /* 色调 透明度 */
- .color-select-middle {
- width: 100%;
- display: flex;
- margin-bottom: 10px;
- }
- .slider-item+.slider-item {
- margin-top: 6px;
- }
- /* 色调滑块条 */
- .hue-slider {
- position: relative;
- height: 10px;
- background: linear-gradient(90deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red);
- box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
- width: 100%;
- }
- /* 透明度滑块条 */
- .alpha-slider {
- position: relative;
- height: 10px;
- box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
- background: #fff url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==');
- background-size: 10px 10px;
- width: 100%;
- }
- /* 滑块 */
- .slider {
- position: absolute;
- box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
- box-sizing: border-box;
- width: 6px;
- height: 100%;
- background-color: #fff;
- }
- .color-slider {
- flex: auto;
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- }
- /* 颜色方块 */
- .color-diamond {
- position: relative;
- margin-left: 5px;
- width: 26px;
- height: 26px;
- border-radius: 3px;
- overflow: hidden;
- background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==');
- background-size: 10px 10px;
- }
- /* 颜色的值 hex rgba */
- .color-value {
- width: 100%;
- display: flex;
- justify-content: space-between;
- }
- .color-value div {
- padding: 0 3px;
- text-align: center;
- }
- .color-value input {
- font-size: 12px;
- box-sizing: border-box;
- width: 34px;
- height: 24px;
- padding: 0;
- margin: 0;
- outline: none;
- text-align: center;
- border-radius: 3px;
- border: 1px solid #ccc;
- }
- .color-value p {
- font-size: 12px;
- margin: 3px 0 0;
- }
- .color-value .rgba-a {
- padding-right: 0;
- }
- .color-value .hex {
- flex: 1;
- padding-left: 0;
- }
- .color-value .hex input {
- width: 100%;
- height: 24px;
- }
- /* 预设颜色 */
- .predefine {
- width: 100%;
- padding: 0;
- margin: 10px 0 0;
- list-style: none;
- display: flex;
- flex-wrap: wrap;
- justify-content: flex-start;
- }
- .predefine-item {
- width: 20px;
- height: 20px;
- margin-bottom: 6px;
- border: 1px solid #ccc;
- border-radius: 6px;
- }
- .predefine-item+.predefine-item {
- margin-left: 6px;
- }
- .predefine-item:nth-child(12n) {
- margin-left: 0;
- }
- .color-actions {
- font-size: 12px;
- text-align: right;
- }
- .color-actions span {
- cursor: pointer;
- padding: 5px 12px;
- line-height: 12px;
- display: inline-block;
- box-sizing: border-box;
- border: 1px solid transparent;
- }
- .color-actions .cancel:hover {
- background-color: #f5f7fa;
- }
- .color-actions .confirm {
- border-color: #dcdfe6;
- border-radius: 4px;
- margin-left: 10px;
- }
- .color-actions .confirm:hover {
- color: #1677ff;
- border-color: #1677ff;
- }
- </style>
|