在网上找的各种颜色的相互转换函数,参考了elementUI的 color-picker组件
function FindRGB(q1, q2, hue) {
if (hue > 360) hue = hue - 360;
if (hue < 0) hue = hue + 360;
if (hue < 60) return (q1 + (q2 - q1) * hue / 60);
else if (hue < 180) return (q2);
else if (hue < 240) return (q1 + (q2 - q1) * (240 - hue) / 60);
else return (q1);
}
function HSLtoRGB(H, S, L) {
var p1, p2;
var tempRGB = { R: 0, G: 0, B: 0 }
L /= 100;
S /= 100;
if (L <= 0.5) p2 = L * (1 + S);
else p2 = L + S - (L * S);
p1 = 2 * L - p2;
if (S == 0) {
tempRGB.R = L;
tempRGB.G = L;
tempRGB.B = L;
}
else {
tempRGB.R = FindRGB(p1, p2, H + 120);
tempRGB.G = FindRGB(p1, p2, H);
tempRGB.B = FindRGB(p1, p2, H - 120);
}
tempRGB.R *= 255;
tempRGB.G *= 255;
tempRGB.B *= 255;
tempRGB.R = Math.round(tempRGB.R);
tempRGB.G = Math.round(tempRGB.G);
tempRGB.B = Math.round(tempRGB.B);
return tempRGB
}
function RGBtoHSL(r, g, b) {
var Min = 0;
var Max = 0;
const HSL = { H: 0, S: 0, L: 0 }
r = (eval(r) / 51) * .2;
g = (eval(g) / 51) * .2;
b = (eval(b) / 51) * .2;
if (eval(r) >= eval(g))
Max = eval(r);
else
Max = eval(g);
if (eval(b) > eval(Max))
Max = eval(b);
if (eval(r) <= eval(g))
Min = eval(r);
else
Min = eval(g);
if (eval(b) < eval(Min))
Min = eval(b);
HSL.L = (eval(Max) + eval(Min)) / 2;
if (eval(Max) == eval(Min)) {
HSL.S = 0;
HSL.H = 0;
}
else {
if (HSL.L < .5)
HSL.S = (eval(Max) - eval(Min)) / (eval(Max) + eval(Min));
if (HSL.L >= .5)
HSL.S = (eval(Max) - eval(Min)) / (2 - eval(Max) - eval(Min));
if (r == Max)
HSL.H = (eval(g) - eval(b)) / (eval(Max) - eval(Min));
if (g == Max)
HSL.H = 2 + ((eval(b) - eval(r)) / (eval(Max) - eval(Min)));
if (b == Max)
HSL.H = 4 + ((eval(r) - eval(g)) / (eval(Max) - eval(Min)));
}
HSL.H = Math.round(HSL.H * 60);
if (HSL.H < 0) HSL.H += 360;
if (HSL.H >= 360) HSL.H -= 360;
HSL.S = Math.round(HSL.S * 100);
HSL.L = Math.round(HSL.L * 100);
return HSL
}
//计算辅助色
function getSecColor(r, g, b) {
const { H, S, L } = RGBtoHSL(r, g, g)
if (H > 120 && H < 290) {//cold
if (Math.abs(210 - (H + 30)) < Math.abs(210 - (H - 30))) {
return HSLtoRGB(H + 30, S, L)
} else {
return HSLtoRGB(H + 30, S, L)
}
} else {
if (Math.abs(30 - (H + 30)) < Math.abs(30 - (H - 30))) {
return HSLtoRGB(H + 30, S, L)
} else {
return HSLtoRGB(H + 30, S, L)
}
}
}
function getContrastColor(r, g, b) {
const hsl = RGBtoHSL(r, g, b)
hsl.H = (hsl.H + 180) % 360
return HSLtoRGB(hsl.H, hsl.S, hsl.L)
}
function getLightenColor(r, g, b) {
const { H, S, L } = RGBtoHSL(r, g, b)
return HSLtoRGB(H, S, Math.min(100, L + 5))
}
function getDarkcolor(r, g, b) {
const { H, S, L } = RGBtoHSL(r, g, b)
return HSLtoRGB(H, S, Math.max(0, L - 5))
}
function setvar(key, value) {
document.body.style.setProperty(key, value)
}
function rgb2hex(rgb) {
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2).toUpperCase();
}
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
}
function rgba2hex(r, g, b, a) {
function hex(x) {
return ("0" + parseInt(x).toString(16)).slice(-2).toUpperCase();
}
return "#" + hex(r) + hex(g) + hex(b)+hex(256*a-1);
}
function hsv2hsl(hue, sat, val) {
return [
hue,
(sat * val / ((hue = (2 - sat) * val) < 1 ? hue : 2 - hue)) || 0,
hue / 2
];
};
// Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
// <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
const isOnePointZero = function (n) {
return typeof n === 'string' && n.indexOf('.') !== -1 && parseFloat(n) === 1;
};
const isPercentage = function (n) {
return typeof n === 'string' && n.indexOf('%') !== -1;
};
// Take input from [0, n] and return it as [0, 1]
const bound01 = function (value, max) {
if (isOnePointZero(value)) value = '100%';
const processPercent = isPercentage(value);
value = Math.min(max, Math.max(0, parseFloat(value)));
// Automatically convert percentage into number
if (processPercent) {
value = parseInt(value * max, 10) / 100;
}
// Handle floating point rounding errors
if ((Math.abs(value - max) < 0.000001)) {
return 1;
}
// Convert into [0, 1] range if it isn't already
return (value % max) / parseFloat(max);
};
const INT_HEX_MAP = { 10: 'A', 11: 'B', 12: 'C', 13: 'D', 14: 'E', 15: 'F' };
const toHex = function ({ r, g, b }) {
const hexOne = function (value) {
value = Math.min(Math.round(value), 255);
const high = Math.floor(value / 16);
const low = value % 16;
return '' + (INT_HEX_MAP[high] || high) + (INT_HEX_MAP[low] || low);
};
if (isNaN(r) || isNaN(g) || isNaN(b)) return '';
return '#' + hexOne(r) + hexOne(g) + hexOne(b);
};
const HEX_INT_MAP = { A: 10, B: 11, C: 12, D: 13, E: 14, F: 15 };
const parseHexChannel = function (hex) {
if (hex.length === 2) {
return (HEX_INT_MAP[hex[0].toUpperCase()] || +hex[0]) * 16 + (HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1]);
}
return HEX_INT_MAP[hex[1].toUpperCase()] || +hex[1];
};
const hsl2hsv = function (hue, sat, light) {
sat = sat / 100;
light = light / 100;
let smin = sat;
const lmin = Math.max(light, 0.01);
let sv;
let v;
light *= 2;
sat *= (light <= 1) ? light : 2 - light;
smin *= lmin <= 1 ? lmin : 2 - lmin;
v = (light + sat) / 2;
sv = light === 0 ? (2 * smin) / (lmin + smin) : (2 * sat) / (light + sat);
return {
h: hue,
s: sv * 100,
v: v * 100
};
};
// `rgbToHsv`
// Converts an RGB color value to HSV
// *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
// *Returns:* { h, s, v } in [0,1]
const rgb2hsv = function (r, g, b) {
r = bound01(r, 255);
g = bound01(g, 255);
b = bound01(b, 255);
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s;
let v = max;
const d = max - min;
s = max === 0 ? 0 : d / max;
if (max === min) {
h = 0; // achromatic
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return { h: h * 360, s: s * 100, v: v * 100 };
};
// `hsvToRgb`
// Converts an HSV color value to RGB.
// *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
// *Returns:* { r, g, b } in the set [0, 255]
const hsv2rgb = function (h, s, v) {
h = bound01(h, 360) * 6;
s = bound01(s, 100);
v = bound01(v, 100);
const i = Math.floor(h);
const f = h - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
const mod = i % 6;
const r = [v, q, p, p, t, v][mod];
const g = [t, v, v, q, p, p][mod];
const b = [p, p, t, v, v, q][mod];
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
};
export default {
FindRGB,
HSLtoRGB,
RGBtoHSL,
getSecColor,
getContrastColor,
getLightenColor,
getDarkcolor,
setvar,
rgb2hex,
hsv2hsl,
toHex,
parseHexChannel,
hsl2hsv,
rgb2hsv,
hsv2rgb,
rgba2hex
}
组件的实现 w-theme.vue
<script setup>
import { computed, onMounted, reactive, ref, toRefs, useAttrs, watch } from 'vue';
import color from './color';
import Clipboard from 'clipboard'
const showBox = ref(false)
const HSV = reactive({ H: 0, S: 100, V: 50 })
const RGBA = reactive({ R: 0, G: 0, B: 0, A: 1 })
function fn_button_click() {
showBox.value = !showBox.value
}
const hubBall = ref(null)
const opacityBall = ref(null)
const plateBall = ref(null)
function asyncBallPosition(r, g, b) {
const hsv = color.rgb2hsv(r, g, b)
setBallPosition(hubBall.value, hsv.h * 100 / 360 + "%")
setBallPosition(opacityBall.value, "100%")
setBallPosition(plateBall.value, hsv.s + "%", (100 - hsv.v) + "%")
}
function setHSV(r, g, b) {
const hsv = color.rgb2hsv(r, g, b)
HSV.H = hsv.h
HSV.S = hsv.s
HSV.V = hsv.v
}
function setBallPosition(ball, x, y) {
const ballRect = ball.getBoundingClientRect()
ball.style.left = `calc( ${x} - ${ballRect.width / 2}px )`
if (y) {
ball.style.top = `calc( ${y} - ${ballRect.height / 2}px )`
}
}
function fn_handler(e, type) {
// type plate || hubBar || opacityBar
const boxReact = e.target.getBoundingClientRect()
const ball = e.target.children.item(0)
const offsetX = e.clientX - boxReact.x, offsetY = e.clientY - boxReact.y
let percentageX = offsetX / boxReact.width
if (percentageX > 1) { percentageX = 1 }
if (percentageX < 0) { percentageX = 0 }
if (type == 'opacityBar') {//设置A
RGBA.A = +percentageX.toFixed(2)
setBallPosition(ball, percentageX * 100 + "%")
return
}
if (type == 'hubBar') {//设置H
HSV.H = +(percentageX * 360).toFixed(2)
setBallPosition(ball, percentageX * 100 + "%")
}
if (type == "plate") {//设置 S、V
HSV.S = +(percentageX * 100).toFixed(2)
let percentageY = offsetY / boxReact.height
if (percentageY > 1) { percentageY = 1 }
if (percentageY < 0) { percentageY = 0 }
HSV.V = +(100 - percentageY * 100).toFixed(2)
setBallPosition(ball, percentageX * 100 + "%", percentageY * 100 + "%")
}
const rgbColor = color.hsv2rgb(HSV.H, HSV.S, HSV.V)
RGBA.R = rgbColor.r
RGBA.G = rgbColor.g
RGBA.B = rgbColor.b
}
const hex = computed(() => {
const opacity = parseInt(RGBA.A * 255).toString(16).toUpperCase().padStart(2, '0')
return color.toHex({ r: RGBA.R, g: RGBA.G, b: RGBA.B }) + opacity
})
const hsla = computed(() => {
const hsl = color.RGBtoHSL(RGBA.R, RGBA.G, RGBA.B)
const alpha = +(RGBA.A * 100).toFixed(2)
return `hsla(${hsl.H},${hsl.S}%,${hsl.L}%,${alpha}%)`
})
const exampleColors = computed(() =>
["#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3", "#03A9F4", "#00BCD4", "#00BCD4", "#4CAF50", "#8BC34A", "#CDDC39", "#FFEB3B", "#FFC107", "#FF9800", "#FF5722", "#795548", "#9E9E9E", "#607D8B", "#B0BEC5"]
)
watch(RGBA, () => {
localStorage.setItem("sampleColor", hex.value)
color.setvar('--main', hex.value)
const dmain = color.getDarkcolor(RGBA.R, RGBA.G, RGBA.B)
color.setvar('--dmain', color.rgba2hex(dmain.R, dmain.G, dmain.B, RGBA.A))
const lmain = color.getLightenColor(RGBA.R, RGBA.G, RGBA.B)
color.setvar('--lmain', color.rgba2hex(lmain.R, lmain.G, lmain.B, RGBA.A))
const sec = color.getSecColor(dmain.R, dmain.G, dmain.B, RGBA.A)
color.setvar('--sec', color.rgba2hex(sec.R, sec.G, sec.B, RGBA.A))
const contrast = color.getContrastColor(RGBA.R, RGBA.G, RGBA.B)
color.setvar('--contrast', color.rgba2hex(contrast.R, contrast.G, contrast.B, RGBA.A))
})
function fn_example_item_click(sample) {
RGBA.B = ("0x" + sample.substr(-2)) / 1
RGBA.G = ("0x" + sample.substr(-4, 2)) / 1
RGBA.R = ("0x" + sample.substr(-6, 2)) / 1
RGBA.A = 1
asyncBallPosition(RGBA.R, RGBA.G, RGBA.B)
setHSV(RGBA.R, RGBA.G, RGBA.B)
}
const button = ref(null)
const bx = ref(0), by = ref(0)
const box = ref(null)
const trangleDom = ref(null)
function setBoxPostion() {
if (!showBox.value) {
return
}
const buttonRect = button.value.getBoundingClientRect()
const bodyRect = document.body.getBoundingClientRect()
// width: 270px;
// height: 370px;
box.value.style.height = "370px"
box.value.style.width = "270px"
if (bodyRect.height - buttonRect.bottom > 375) {
//按钮下方的空间可以容纳弹窗。
by.value = buttonRect.bottom + 5;
trangleDom.value.className = "trangle trangle-up"
} else if (buttonRect.top > 375) {
by.value = buttonRect.top - 375;
trangleDom.value.className = "trangle trangle-down"
} else {
trangleDom.value.className = "trangle trangle-up"
by.value = buttonRect.bottom + 5;
box.value.style.height = bodyRect.height - buttonRect.bottom - 5 + "px"
box.value.style.overflow = 'auto'
}
let left = buttonRect.left + buttonRect.width / 2 - 270 / 2
if ((left + 270) > bodyRect.width) {
left = bodyRect.width - 270 - 10
} else if (left < 5) {
left = 5
}
bx.value = left
let trangleDomLeft = buttonRect.left - left + buttonRect.width / 2
if (trangleDomLeft < 5) {
trangleDomLeft = 5
} else if (trangleDomLeft > 258) {
trangleDomLeft = 258
}
trangleDom.value.style.left = trangleDomLeft + 'px'
}
watch(showBox, setBoxPostion)
function setClipboard() {
new Clipboard('.res-item').on('success', e => {
e.trigger.querySelector('.tips').innerHTML = "Copied"
setTimeout(() => {
e.trigger.querySelector('.tips').innerHTML = "COPY"
}, 2000)
})
}
const props = defineProps({
color: {
type: String,
default: ''
}
})
onMounted(() => {
if (props.color) {
fn_example_item_click(props.color.substring(0, 7))
} else {
const sampleColor = localStorage.getItem("sampleColor") || "#448aff"
fn_example_item_click(sampleColor.substring(0, 7))
}
setClipboard()
setBoxPostion()
window.addEventListener('resize', setBoxPostion)
})
</script>
<template>
<div class="w-theme-button" ref="button" @click="fn_button_click" v-bind="useAttrs()"></div>
<div
class="w-theme-box--mask"
v-show="showBox"
@scroll="showBox = false"
@click="showBox = false"
></div>
<transition name="fade">
<div class="box" v-show="showBox" ref="box" :style="{ top: by + 'px', left: bx + 'px' }">
<div
class="plate"
:style="`background-color:hsl(${HSV.H}deg,100%,50%)`"
@mousedown="fn_handler($event, 'plate')"
>
<div class="ball" ref="plateBall"></div>
<div class="bg bg1"></div>
<div class="bg bg2"></div>
</div>
<div class="bar">
<div class="target">
<div class="target-inner"></div>
</div>
<div class="bar-inner-right">
<div class="color-bar control-bar" @mousedown="fn_handler($event, 'hubBar')">
<div class="ball" ref="hubBall"></div>
</div>
<div
class="opacity-bar control-bar"
@mousedown="fn_handler($event, 'opacityBar')"
>
<div class="ball" ref="opacityBall"></div>
<div
class="opacity-inner-bar"
:style="`background-image:linear-gradient(to right,transparent,rgb(${RGBA.R},${RGBA.G},${RGBA.B}))`"
></div>
</div>
</div>
</div>
<div class="res">
<div class="res-item" :data-clipboard-text="hex">
{{ hex }}
<div class="tips">COPY</div>
</div>
<div class="res-item" :data-clipboard-text="hsla">
{{ hsla }}
<div class="tips">COPY</div>
</div>
</div>
<div class="example">
<div
class="example-item"
v-for="color in exampleColors"
:key="color"
:style="{ background: color }"
@click="fn_example_item_click(color)"
:data-color="color"
></div>
</div>
<div class="example-panel">
<div class="example-panel-item"></div>
<div class="example-panel-item"></div>
<div class="example-panel-item"></div>
<div class="example-panel-item"></div>
<div class="example-panel-item"></div>
</div>
<div class="trangle" ref="trangleDom"></div>
</div>
</transition>
</template>
<style lang="less">
@transpartBG: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==";
.border-radius {
border-radius: 3px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 500ms ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.w-theme-button {
background-color: var(--main);
width: 16px;
height: 16px;
border: 1px solid rgb(125, 26, 148);
border-radius: 4px;
display: inline-block;
cursor: pointer;
}
.w-theme-box--mask {
position: fixed;
z-index: 9998;
width: 100vw;
height: 100vh;
background-color: #62634f1f;
}
.box {
position: fixed;
// top: 20px;
// left: 180px;
width: 270px;
height: 370px;
background-color: #fff;
box-shadow: 0px 0px 10px 4px #181b1e33;
transition: opacity 200ms;
user-select: none;
z-index: 9999;
.border-radius;
.trangle {
position: absolute;
width: 0;
height: 0;
transform: translateX(-50%);
&.trangle-up {
bottom: 100%;
border-bottom: 5px solid #fff;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
&.trangle-down {
top: 100%;
border-top: 5px solid #fff;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
}
}
.example-panel {
display: flex;
height: 50px;
justify-content: space-evenly;
align-items: center;
.example-panel-item {
width: 15%;
height: 60%;
background-image: url(@transpartBG);
border-radius: 4px;
position: relative;
&:after {
content: "";
position: absolute;
display: inline-block;
width: 100%;
height: 100%;
border-radius: 4px;
}
&:nth-child(1):after {
background-color: var(--main);
}
&:nth-child(2):after {
background-color: var(--lmain);
}
&:nth-child(3):after {
background-color: var(--dmain);
}
&:nth-child(4):after {
background-color: var(--sec);
}
&:nth-child(5):after {
background-color: var(--contrast);
}
}
}
.example {
display: flex;
flex-wrap: wrap;
padding-bottom: 10px;
.border-radius;
.example-item {
width: 16px;
height: 16px;
border-radius: 4px;
margin-top: 10px;
margin-left: 10px;
cursor: pointer;
position: relative;
&:hover {
&:after {
content: attr(data-color);
position: absolute;
left: 10px;
background: #9e9e9ebf;
padding: 4px;
border-radius: 6px;
z-index: 1;
transform: translate(-50%, -50%);
bottom: calc(100% - 5px);
font-size: 12px;
color: white;
}
&::before {
content: "";
width: 0;
height: 0;
border-style: solid;
border-color: transparent;
border-left-width: 5px;
border-right-width: 5px;
border-top-color: #9e9e9ebf;
border-top-width: 5px;
border-bottom-width: 0;
display: inline-block;
position: absolute;
top: -5px;
z-index: -1;
transform: translateX(-50%);
left: 50%;
}
}
}
}
.res {
display: flex;
height: 40px;
border-bottom: 1px solid #eee;
justify-content: space-evenly;
.res-item {
// flex: 1;
line-height: 40px;
text-align: center;
font-size: 10px;
padding-left: 10px;
padding-right: 10px;
position: relative;
cursor: pointer;
.tips {
cursor: pointer;
opacity: 0;
position: absolute;
top: 0;
right: 0;
// width: 40px;
padding: 0 4px;
height: 20px;
background: #5e6cb9;
transition: opacity 400ms;
pointer-events: none;
line-height: 20px;
font-weight: bold;
color: #b9ac5f;
}
&:hover {
.tips {
opacity: 1;
}
}
}
}
.plate {
position: relative;
height: 145px;
.border-radius;
.bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
pointer-events: none;
&.bg1 {
background-image: linear-gradient(to right, #fff, #ffffff00);
}
&.bg2 {
background-image: linear-gradient(to bottom, transparent, #000);
}
}
.ball {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid #eee;
pointer-events: none;
box-shadow: inset 0 0 1px 1px #1a2a3a73;
z-index: 1;
}
.img {
width: 100%;
height: 100%;
// background-color: var(--mainColor);
pointer-events: none;
}
}
.bar {
height: 70px;
background: #efefef;
display: flex;
justify-content: space-evenly;
align-items: center;
.target {
width: 40px;
height: 40px;
// border: 1px solid #eeeeee33;
box-shadow: 0 0 6px 1px #2e262657;
border-radius: 50%;
background-image: url(@transpartBG);
overflow: hidden;
.target-inner {
background-color: var(--main);
width: 100%;
height: 100%;
}
}
.bar-inner-right {
width: 170px;
justify-self: flex-end;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: space-evenly;
.control-bar {
position: relative;
height: 15px;
width: 100%;
border-radius: 4px;
&.color-bar {
background-image: linear-gradient(
to right,
#ff0000,
#ff3300,
#ff6600,
#ff9900,
#ffcc00,
#ffff00,
#ccff00,
#99ff00,
#66ff00,
#33ff00,
#00ff00,
#00ff33,
#00ff66,
#00ff99,
#00ffcc,
#00ffff,
#00ccff,
#0099ff,
#0066ff,
#0033ff,
#0000ff,
#3300ff,
#6600ff,
#9900ff,
#cc00ff,
#ff00ff,
#ff00cc,
#ff0099,
#ff0066,
#ff0033
);
}
&.opacity-bar {
background: url(@transpartBG);
.opacity-inner-bar {
pointer-events: none;
width: 100%;
height: 100%;
border-radius: 4px;
}
.ball {
right: -8px;
}
}
.ball {
position: absolute;
top: -1px;
width: 17px;
height: 17px;
background-color: #f8f8f8;
border-radius: 50%;
opacity: 0.9;
pointer-events: none;
cursor: pointer;
box-shadow: inset 0 0 1px 1px #1a2a3a73;
}
}
}
}
}
.sample {
display: inline-flex;
background: #fff;
.block {
width: 90px;
height: 50px;
border: 1px solid;
margin: 6px;
}
}
.hex-codes {
display: flex;
width: 14px * 18;
flex-wrap: wrap;
.inner {
width: 14px;
height: 14px;
border-right: 1px solid;
border-bottom: 1px solid;
box-sizing: border-box;
cursor: pointer;
position: relative;
transition: 200ms transform;
&:hover {
box-shadow: inset 0px 0px 2px 1px #000000b0;
transform: scale(1.3);
z-index: 1;
&:after {
content: attr(color);
display: inline-block;
position: absolute;
background-color: #1a2a3a;
padding: 6px;
border-radius: 8px;
z-index: 1;
color: #fff;
font-size: 12px;
transform: translate(-50%);
bottom: calc(100% + 5px);
left: 50%;
}
}
}
}
</style>
使用方法。
import wTheme from './components/w-theme.vue';
<w-theme class="theme" color="#3F51B5FF" />
版权声明:本文为wgl758原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。