new color selection panel
This commit is contained in:
parent
32cd80145e
commit
d97e34c078
@ -7,7 +7,7 @@
|
||||
<a href="https://coveralls.io/r/sentsin/layui?branch=master"><img alt="Test Coverage" src="https://img.shields.io/coveralls/sentsin/layui/master.svg"></a>
|
||||
</p>
|
||||
|
||||
**[🔶 Read Document](http://layui-vue.pearadmin.com)**
|
||||
**[🔶 Explore the docs »](http://layui-vue.pearadmin.com)**
|
||||
|
||||
An enterprise-class UI components based on Layui and Vue.
|
||||
|
||||
@ -32,6 +32,7 @@ Use npm to install.
|
||||
```bash
|
||||
npm i @layui/layui-vue
|
||||
```
|
||||
|
||||
We have several examples on the [website](http://layui-vue.pearadmin.com). Here is the first one to get you started:
|
||||
|
||||
```
|
||||
|
3
example/src/assets/background.svg
Normal file
3
example/src/assets/background.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="full" width="100%" height="100%" viewBox="0 0 1400 800">
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 131 B |
@ -18,7 +18,7 @@
|
||||
</div>
|
||||
<div class="layui-anim site-desc site-desc-anim">
|
||||
<p class="web-font-desc">layui - vue</p>
|
||||
<cite>layui vue, A component library for Vue 3 base on layui</cite>
|
||||
<cite>An enterprise-class UI components based on Layui and Vue.</cite>
|
||||
</div>
|
||||
<div class="site-download">
|
||||
<router-link class="layui-inline site-down" to="/zh-CN/guide"
|
||||
@ -44,7 +44,7 @@
|
||||
rel="nofollow"
|
||||
class="site-star"
|
||||
>
|
||||
<i class="layui-icon"></i> Star <cite id="getStars">612</cite>
|
||||
<i class="layui-icon"></i> Star <cite id="getStars">657</cite>
|
||||
</a>
|
||||
<a
|
||||
href="https://gitee.com/layui-vue"
|
||||
@ -119,6 +119,9 @@
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
background-color: #393d49;
|
||||
background-image: url(../assets/background.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
.site-banner-bg {
|
||||
background-position: center 0;
|
||||
|
@ -1,113 +0,0 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "ColorBox",
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { Nullable } from "../type";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { HSBToHEX, RGBSTo, RGBToHSB } from "./colorUtil";
|
||||
import ColorPicker from "./ColorPicker.vue";
|
||||
import { usePosition } from "@layui/hooks-vue";
|
||||
|
||||
interface BoxProps {
|
||||
color?: string;
|
||||
size?: Nullable<string>;
|
||||
alpha?: boolean;
|
||||
format?: "hex" | "rgb";
|
||||
predefine?: boolean;
|
||||
colors?: string[];
|
||||
}
|
||||
|
||||
const colorBoxProps = withDefaults(defineProps<BoxProps>(), {
|
||||
color: "",
|
||||
size: () => null,
|
||||
alpha: false,
|
||||
format: "hex",
|
||||
predefine: false,
|
||||
colors: () => [
|
||||
//默认预定义颜色列表
|
||||
"#009688",
|
||||
"#5FB878",
|
||||
"#1E9FFF",
|
||||
"#FF5722",
|
||||
"#FFB800",
|
||||
"#01AAED",
|
||||
"#999",
|
||||
"#c00",
|
||||
"#ff8c00",
|
||||
"#ffd700",
|
||||
"#90ee90",
|
||||
"#00ced1",
|
||||
"#1e90ff",
|
||||
"#c71585",
|
||||
"rgb(0, 186, 189)",
|
||||
"rgb(255, 120, 0)",
|
||||
"rgb(250, 212, 0)",
|
||||
"#393D49",
|
||||
"rgba(0,0,0,.5)",
|
||||
"rgba(255, 69, 0, 0.68)",
|
||||
"rgba(144, 240, 144, 0.5)",
|
||||
"rgba(31, 147, 255, 0.73)",
|
||||
],
|
||||
});
|
||||
|
||||
const triggerSpanStyle = computed(() => {
|
||||
let bgstr = "";
|
||||
if (colorBoxProps.color) {
|
||||
bgstr = colorBoxProps.color;
|
||||
|
||||
if ((colorBoxProps.color.match(/[0-9]{1,3}/g) || []).length > 3) {
|
||||
//需要优化
|
||||
if (!(colorBoxProps.alpha && colorBoxProps.format == "rgb")) {
|
||||
bgstr = "#" + HSBToHEX(RGBToHSB(RGBSTo(colorBoxProps.color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
background: bgstr,
|
||||
};
|
||||
});
|
||||
|
||||
const colorPickerWrapper = computed(() => {
|
||||
return colorBoxProps.size ? `layui-colorpicker-${colorBoxProps.size}` : "";
|
||||
});
|
||||
|
||||
const colorBoxRefEl = ref<HTMLElement | null>(null);
|
||||
const colorPickerRefEl = ref<HTMLElement | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
console.log("colorPickerRefEl =>>>", colorPickerRefEl.value.teleportRefEl);
|
||||
usePosition(colorBoxRefEl.value, colorPickerRefEl.value.teleportRefEl);
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div ref="colorBoxRefEl" class="layui-unselect layui-colorpicker">
|
||||
<span
|
||||
:class="[
|
||||
{
|
||||
'layui-colorpicker-trigger-bgcolor': format == 'rgb' && alpha,
|
||||
},
|
||||
size ? colorPickerWrapper : '',
|
||||
]"
|
||||
>
|
||||
<span class="layui-colorpicker-trigger-span" :style="triggerSpanStyle">
|
||||
<!-- ICON_PICKER_DOWN = 'layui-icon-down', ICON_PICKER_CLOSE = 'layui-icon-close' -->
|
||||
<i
|
||||
:class="[
|
||||
'layui-icon layui-colorpicker-trigger-i',
|
||||
color ? 'layui-icon-down' : 'layui-icon-close',
|
||||
]"
|
||||
></i>
|
||||
</span>
|
||||
</span>
|
||||
<ColorPicker
|
||||
ref="colorPickerRefEl"
|
||||
:visible="true"
|
||||
:alpha="alpha"
|
||||
:predefine="predefine"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="less"></style>
|
@ -1,91 +0,0 @@
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "ColorPicker",
|
||||
};
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
interface CProps {
|
||||
visible: boolean;
|
||||
alpha: boolean;
|
||||
predefine: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<CProps>();
|
||||
|
||||
const domRefEl = ref<HTMLElement | null>(null);
|
||||
defineExpose({
|
||||
teleportRefEl: domRefEl,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-if="visible"
|
||||
ref="domRefEl"
|
||||
class="layui-anim layui-anim-downbit layui-colorpicker-main"
|
||||
>
|
||||
<!-- //颜色面板-->
|
||||
<div class="layui-colorpicker-main-wrapper">
|
||||
<div class="layui-colorpicker-basis">
|
||||
<div class="layui-colorpicker-basis-white"></div>
|
||||
<div class="layui-colorpicker-basis-black"></div>
|
||||
<div class="layui-colorpicker-basis-cursor"></div>
|
||||
</div>
|
||||
<div class="layui-colorpicker-side">
|
||||
<div class="layui-colorpicker-side-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- //透明度条块-->
|
||||
<div
|
||||
:class="[
|
||||
{
|
||||
'layui-colorpicker-main-alpha': true,
|
||||
'layui-show': alpha,
|
||||
},
|
||||
]"
|
||||
>
|
||||
<div class="layui-colorpicker-alpha-bgcolor">
|
||||
<div class="layui-colorpicker-alpha-slider"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- //预设颜色列表-->
|
||||
<div v-if="predefine" class="layui-colorpicker-main-pre">
|
||||
<div
|
||||
v-for="c in colors"
|
||||
:key="c"
|
||||
:class="{
|
||||
'layui-colorpicker-pre': true,
|
||||
'layui-colorpicker-pre-isalpha':
|
||||
(c.match(/[0-9]{1,3}/g) || []).length > 3,
|
||||
}"
|
||||
>
|
||||
<div :style="{ background: c }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- //底部表单元素区域-->
|
||||
<div class="layui-colorpicker-main-input">
|
||||
<div class="layui-inline">
|
||||
<input type="text" class="layui-input" />
|
||||
</div>
|
||||
<div class="layui-btn-container">
|
||||
<button
|
||||
class="layui-btn layui-btn-primary layui-btn-sm"
|
||||
colorpicker-events="clear"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
<button
|
||||
class="layui-btn layui-btn-sm"
|
||||
colorpicker-events="confirm"
|
||||
type="submit"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<style scoped lang="less"></style>
|
@ -1,34 +0,0 @@
|
||||
export interface ColorPickerProps {
|
||||
/**
|
||||
* 默认颜色,不管你是使用 hex、rgb 还是 rgba 的格式输入,最终会以指定的格式显示。
|
||||
* v-model:color
|
||||
*/
|
||||
color: string;
|
||||
/**
|
||||
* 颜色显示/输入格式,可选值: hex、rgb
|
||||
* 若在 rgb 格式下开启了透明度,格式会自动变成 rgba。在没有输入颜色的前提下,组件会默认为 #000 也就是黑色。
|
||||
* default: hex(即 16 进制色值)
|
||||
*/
|
||||
format: "hex" | "rgb";
|
||||
/**
|
||||
* 是否开启透明度,若不开启,则不会显示透明框。开启了透明度选项时,当你的默认颜色为 hex 或 rgb 格式,
|
||||
* 组件会默认加上值为 1 的透明度。相同的,当你没有开启透明度,却以 rgba 格式设置默认颜色时,组件会默认没有透明度。
|
||||
* 注意:该参数必须配合 rgba 颜色值使用
|
||||
* default: false
|
||||
*/
|
||||
alpha: boolean;
|
||||
/**
|
||||
* 预定义颜色是否开启
|
||||
* default: false
|
||||
*/
|
||||
predefine: boolean;
|
||||
/**
|
||||
* 预定义颜色,此参数需配合 predefine: true 使用。
|
||||
* 此处列举一部分:['#ff4500','#1e90ff','rgba(255, 69, 0, 0.68)','rgb(255, 120, 0)']
|
||||
*/
|
||||
colors: string[];
|
||||
/**
|
||||
* 下拉框大小,可以选择:lg、sm、xs。
|
||||
*/
|
||||
size: "lg" | "sm" | "xs";
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
export interface RGB {
|
||||
h: number;
|
||||
s: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
// RGB转HSB
|
||||
export function RGBToHSB(rgb: any) {
|
||||
const hsb = { h: 0, s: 0, b: 0 };
|
||||
const min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
const max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
const delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? (255 * delta) / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) {
|
||||
hsb.h = (rgb.g - rgb.b) / delta;
|
||||
} else if (rgb.g == max) {
|
||||
hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
} else {
|
||||
hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
}
|
||||
} else {
|
||||
hsb.h = -1;
|
||||
}
|
||||
if (max == min) {
|
||||
hsb.h = 0;
|
||||
}
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) {
|
||||
hsb.h += 360;
|
||||
}
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
}
|
||||
|
||||
// HEX转HSB
|
||||
export function HEXToHSB(hex: any) {
|
||||
hex = hex.indexOf("#") > -1 ? hex.substring(1) : hex;
|
||||
if (hex.length == 3) {
|
||||
const num = hex.split("");
|
||||
hex = num[0] + num[0] + num[1] + num[1] + num[2] + num[2];
|
||||
}
|
||||
hex = parseInt(hex, 16);
|
||||
const rgb = { r: hex >> 16, g: (hex & 0x00ff00) >> 8, b: hex & 0x0000ff };
|
||||
return RGBToHSB(rgb);
|
||||
}
|
||||
|
||||
// HSB转RGB
|
||||
export function HSBToRGB(hsb: any) {
|
||||
const rgb: any = {};
|
||||
let h = hsb.h;
|
||||
const s = (hsb.s * 255) / 100;
|
||||
const b = (hsb.b * 255) / 100;
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = b;
|
||||
} else {
|
||||
const t1 = b;
|
||||
const t2 = ((255 - s) * b) / 255;
|
||||
const t3 = ((t1 - t2) * (h % 60)) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return { r: Math.round(rgb.r), g: Math.round(rgb.g), b: Math.round(rgb.b) };
|
||||
}
|
||||
|
||||
// HSB转HEX
|
||||
export function HSBToHEX(hsb: any) {
|
||||
const rgb = HSBToRGB(hsb);
|
||||
const hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
||||
hex.forEach((val, nr) => {
|
||||
if (val.length == 1) {
|
||||
hex[nr] = "0" + val;
|
||||
}
|
||||
});
|
||||
return hex.join("");
|
||||
}
|
||||
|
||||
//转化成所需rgb格式
|
||||
export function RGBSTo(rgbs: any) {
|
||||
const regexp = /[0-9]{1,3}/g;
|
||||
const re = rgbs.match(regexp) || [];
|
||||
return { r: re[0], g: re[1], b: re[2] };
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
.layui-color-picker {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
padding: 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);
|
||||
}
|
||||
|
||||
/* 色调 透明度 */
|
||||
.layui-color-picker-middle {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* 色调滑块条 */
|
||||
.hue-slider {
|
||||
position: relative;
|
||||
margin-bottom: 6px;
|
||||
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);
|
||||
}
|
||||
|
||||
/* 透明度滑块条 */
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 滑块 */
|
||||
.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-diamond {
|
||||
position: relative;
|
||||
margin-left: 5px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 2px;
|
||||
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: 2px;
|
||||
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;
|
||||
}
|
||||
|
||||
/* 预设颜色 */
|
||||
.preset {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 10px 0 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.preset li {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 6px;
|
||||
margin-bottom: 6px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import type { App } from "vue";
|
||||
import Component from "./index.vue";
|
||||
import type { IDefineComponent } from "../../types/index";
|
||||
|
||||
Component.install = (app: App) => {
|
||||
app.component(Component.name || "LayColorPicker", Component);
|
||||
app.component("LayColorPicker", Component);
|
||||
};
|
||||
|
||||
export default Component as IDefineComponent;
|
||||
export default Component;
|
||||
|
@ -1,3 +1,81 @@
|
||||
<template>
|
||||
<div class="layui-color-picker">
|
||||
<div
|
||||
class="saturation-value"
|
||||
ref="saturationValue"
|
||||
@mousedown="mousedownSV"
|
||||
>
|
||||
<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="layui-color-picker-middle">
|
||||
<div style="flex: auto">
|
||||
<div class="hue-slider" ref="hueSlider" @mousedown="mousedownHue">
|
||||
<div class="slider" :style="hueSliderStyle"></div>
|
||||
</div>
|
||||
<div
|
||||
class="alpha-slider"
|
||||
ref="alphaSlider"
|
||||
@mousedown="mousedownAlpha"
|
||||
>
|
||||
<div class="slider" :style="alphaSliderStyle"></div>
|
||||
<div
|
||||
:style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorObj.rgb});width: 100%;height: 100%`"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-diamond">
|
||||
<div
|
||||
:style="`background-color: ${colorObj.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="colorObj.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">
|
||||
<label>
|
||||
<input :value="alpha" @input="alphaChange" />
|
||||
</label>
|
||||
<p>A</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="preset">
|
||||
<li
|
||||
v-for="item in preset"
|
||||
:key="item"
|
||||
:style="`background-color: ${item}`"
|
||||
@click="presetChange(item)"
|
||||
></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "LayColorPicker",
|
||||
@ -5,54 +83,410 @@ export default {
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Nullable } from "/@src/module/type";
|
||||
import ColorBox from "./ColorBox.vue";
|
||||
import "./index.less"
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
|
||||
interface ColorPickerProps {
|
||||
color?: string;
|
||||
size?: Nullable<string>;
|
||||
alpha?: boolean;
|
||||
format?: "hex" | "rgb";
|
||||
predefine?: boolean;
|
||||
colors?: string[];
|
||||
const emit = defineEmits(["update:color"]);
|
||||
|
||||
export interface LayColorPicker {
|
||||
modelValue?: any;
|
||||
preset?: any;
|
||||
}
|
||||
|
||||
const colorPickerProps = withDefaults(defineProps<ColorPickerProps>(), {
|
||||
color: "",
|
||||
size: () => null,
|
||||
alpha: false,
|
||||
format: "hex",
|
||||
predefine: false,
|
||||
colors: () => [
|
||||
//默认预定义颜色列表
|
||||
"#009688",
|
||||
"#5FB878",
|
||||
"#1E9FFF",
|
||||
"#FF5722",
|
||||
"#FFB800",
|
||||
"#01AAED",
|
||||
"#999",
|
||||
"#c00",
|
||||
"#ff8c00",
|
||||
"#ffd700",
|
||||
"#90ee90",
|
||||
"#00ced1",
|
||||
"#1e90ff",
|
||||
"#c71585",
|
||||
"rgb(0, 186, 189)",
|
||||
"rgb(255, 120, 0)",
|
||||
"rgb(250, 212, 0)",
|
||||
"#393D49",
|
||||
"rgba(0,0,0,.5)",
|
||||
"rgba(255, 69, 0, 0.68)",
|
||||
"rgba(144, 240, 144, 0.5)",
|
||||
"rgba(31, 147, 255, 0.73)",
|
||||
const props = withDefaults(defineProps<LayColorPicker>(), {
|
||||
modelValue: { r: 217, g: 128, b: 95, a: 1 },
|
||||
preset: [
|
||||
"#D0021B",
|
||||
"#F5A623",
|
||||
"#F8E71C",
|
||||
"#8B572A",
|
||||
"#7ED321",
|
||||
"#417505",
|
||||
"#BD10E0",
|
||||
"#9013FE",
|
||||
"#4A90E2",
|
||||
"#50E3C2",
|
||||
"#B8E986",
|
||||
"#000000",
|
||||
"#4A4A4A",
|
||||
"#9B9B9B",
|
||||
"#FFFFFF",
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layui-inline'">
|
||||
<ColorBox />
|
||||
</div>
|
||||
</template>
|
||||
const saturationValue = ref<null | HTMLElement>(null);
|
||||
const hueSlider = ref<null | HTMLElement>(null)
|
||||
const alphaSlider = ref<null | HTMLElement>(null)
|
||||
|
||||
let pointStyle = ref("top: 25%;left: 80%;");
|
||||
let hueSliderStyle = ref("left: 0;");
|
||||
let alphaSliderStyle = ref("left: calc(100% - 6px);");
|
||||
|
||||
let hue = ref(0);
|
||||
let saturation = ref(1);
|
||||
let value = ref(1);
|
||||
|
||||
let red = ref(255);
|
||||
let green = ref(0);
|
||||
let blue = ref(0);
|
||||
|
||||
let alpha = ref(1);
|
||||
|
||||
onMounted(() => {
|
||||
let { r, g, b, a } = parseColor(props.modelValue);
|
||||
red.value = r;
|
||||
green.value = g;
|
||||
blue.value = b;
|
||||
alpha.value = a;
|
||||
});
|
||||
|
||||
watch([red, green, blue], (newValue) => {
|
||||
emit("update:color", {
|
||||
r: red.value,
|
||||
g: green.value,
|
||||
b: blue.value,
|
||||
a: alpha.value,
|
||||
});
|
||||
|
||||
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}%;`;
|
||||
});
|
||||
|
||||
watch(alpha, () => {
|
||||
emit("update:color", {
|
||||
r: red.value,
|
||||
g: green.value,
|
||||
b: blue.value,
|
||||
a: alpha.value,
|
||||
});
|
||||
// 移动透明度滑块
|
||||
alphaSliderStyle.value = `left: ${
|
||||
alpha.value >= 1 ? "calc(100% - 6px)" : alpha.value * 100 + "%"
|
||||
};`;
|
||||
});
|
||||
|
||||
let colorObj = 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})`,
|
||||
hsl: ``,
|
||||
};
|
||||
});
|
||||
|
||||
// 输入框值变化,限制输入的值
|
||||
function hexChange(e: any) {
|
||||
let v = e.target.value;
|
||||
if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
|
||||
let { r, g, b, a } = hex2rgba(v);
|
||||
red.value = r;
|
||||
green.value = g;
|
||||
blue.value = b;
|
||||
alpha.value = a;
|
||||
}
|
||||
}
|
||||
|
||||
function redChange(e: any) {
|
||||
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: any) {
|
||||
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: any) {
|
||||
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: any) {
|
||||
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 presetChange(item: any) {
|
||||
if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(item)) {
|
||||
let { r, g, b, a } = hex2rgba(item);
|
||||
red.value = r;
|
||||
green.value = g;
|
||||
blue.value = b;
|
||||
alpha.value = a;
|
||||
}
|
||||
}
|
||||
|
||||
// 饱和度和亮度
|
||||
function handleChangeSV(e: any) {
|
||||
// @ts-ignore
|
||||
let w = saturationValue.value.clientWidth;
|
||||
// @ts-ignore
|
||||
let h = saturationValue.value.clientHeight;
|
||||
// @ts-ignore
|
||||
let x = e.pageX - saturationValue.value.getBoundingClientRect().left;
|
||||
// @ts-ignore
|
||||
let y = e.pageY - saturationValue.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 mousedownSV(e: any) {
|
||||
// 鼠标按下计算饱和度和亮度并添加事件
|
||||
handleChangeSV(e);
|
||||
// 添加整个页面的鼠标事件
|
||||
window.addEventListener("mousemove", handleChangeSV);
|
||||
window.addEventListener("mouseup", mouseupSV);
|
||||
}
|
||||
|
||||
function mouseupSV(e: any) {
|
||||
// 鼠标松开后移除事件
|
||||
window.removeEventListener("mousemove", handleChangeSV);
|
||||
window.removeEventListener("mouseup", mouseupSV);
|
||||
}
|
||||
|
||||
// 色调
|
||||
function handleChangeHue(e: any) {
|
||||
// @ts-ignore
|
||||
let w = hueSlider.value.clientWidth;
|
||||
// @ts-ignore
|
||||
let x = e.pageX - saturationValue.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: any) {
|
||||
handleChangeHue(e);
|
||||
window.addEventListener("mousemove", handleChangeHue);
|
||||
window.addEventListener("mouseup", mouseupHue);
|
||||
}
|
||||
|
||||
function mouseupHue(e: any) {
|
||||
window.removeEventListener("mousemove", handleChangeHue);
|
||||
window.removeEventListener("mouseup", mouseupHue);
|
||||
}
|
||||
|
||||
// 透明度
|
||||
function handleChangeAlpha(e: any) {
|
||||
// @ts-ignore
|
||||
let w = alphaSlider.value.clientWidth;
|
||||
// @ts-ignore
|
||||
let x = e.pageX - saturationValue.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: any) {
|
||||
handleChangeAlpha(e);
|
||||
window.addEventListener("mousemove", handleChangeAlpha);
|
||||
window.addEventListener("mouseup", mouseupAlpha);
|
||||
}
|
||||
|
||||
function mouseupAlpha(e: any) {
|
||||
window.removeEventListener("mousemove", handleChangeAlpha);
|
||||
window.removeEventListener("mouseup", mouseupAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析输入的数据,只能解析hex颜色和rgb对象形式的数据
|
||||
* @param color
|
||||
*/
|
||||
function parseColor(color: any) {
|
||||
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 {
|
||||
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: any, s: any, v: any) {
|
||||
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: any, g: any, b: any) {
|
||||
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;
|
||||
// @ts-ignore
|
||||
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: any, g: any, b: any, a = 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);
|
||||
// @ts-ignore
|
||||
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();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function hex2rgba(s: any) {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
</script>
|
@ -122,9 +122,6 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.layui-container {
|
||||
width: 750px;
|
||||
}
|
||||
|
||||
.layui-hide-sm {
|
||||
display: none !important;
|
||||
@ -146,9 +143,6 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 992px) {
|
||||
.layui-container {
|
||||
width: 970px;
|
||||
}
|
||||
|
||||
.layui-hide-md {
|
||||
display: none !important;
|
||||
@ -170,9 +164,6 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
.layui-container {
|
||||
width: 1170px;
|
||||
}
|
||||
|
||||
.layui-hide-lg {
|
||||
display: none !important;
|
||||
|
Loading…
Reference in New Issue
Block a user