我是正在努力学习前端的Mancuoj,欢迎关注互相交流!
那天在codepen看到了别人做的color picker
就突发奇想自己复刻一个VS Code的调色板,我也是刚开始学前端嘛,看见啥都想试着写一个。
说干就干,花了几个小时写了一个,bug贼多,就是一个想法,也懒得改了,菜鸡一个,大佬勿喷。
我们先来看看原版长啥样(颜色过多的话gif会失真,大概看看):
可以看到上方用来显示颜色和色值,点击可以切换三种色彩显示方式,分别是rgba,hsla和hex8;然后下方左侧一个色谱来选择颜色,右侧两个滑块选择透明度和色相。
右上角的显示原来的颜色和最下方的文字提示这次就先不写了
然后看看我几个小时复刻的结果(在线预览):
HTML没啥好说的,就div套div。
我大概画了一下整体布局:
这背景的黑白格子上来就给我整蒙了,还得是万能的搜索啊,最后用线性渐变解决了:
background: linear-gradient(
45deg,
rgba(0, 0, 0, 0.4) 25%,
transparent 25%,
transparent 75%,
rgba(0, 0, 0, 0.4) 75%,
rgba(0, 0, 0, 0.4)
),
linear-gradient(
45deg,
rgba(0, 0, 0, 0.4) 25%,
transparent 25%,
transparent 75%,
rgba(0, 0, 0, 0.4) 75%,
rgba(0, 0, 0, 0.4)
);
background-size: 10px 10px;
background-position: 0 0, 5px 5px;
透明度和色相选择部分都用线性渐变解决:
// 透明度
background: linear-gradient(to bottom, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
// 色相
background: linear-gradient(to bottom,
hsl(0, 100%, 50%),
hsl(60, 100%, 50%),
hsl(120, 100%, 50%),
hsl(180, 100%, 50%),
hsl(240, 100%, 50%),
hsl(300, 100%, 50%),
hsl(360, 100%, 50%)
);
滑块左右要各超出去1px:
.slide {
width: 27px;
height: 5px;
border: 1px solid #eee;
position: absolute;
left: -2px;
top: 0;
}
CSS写完后就是这个样子:
先把要用到的变量都定义出来:
const container = document.querySelector('.container')
const value = document.querySelector('.value')
const opacity = document.querySelector('.opacity')
const hue = document.querySelector('.hue')
const opaSlide = document.querySelector('#opa-slide')
const hueSlide = document.querySelector('#hue-slide')
const spectrum = document.querySelector('.spectrum')
const cursor = document.querySelector('.color-cursor')
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const speRect = canvas.getBoundingClientRect()
let bg_color = {
h: 0,
s: "100%",
l: "50%",
a: 1
};
canvas也就是色谱部分用两个黑白透明渐变覆盖画出来的:
function draw(color) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!color) color = '#f00';
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
let whiteGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
whiteGradient.addColorStop(0, "#fff");
whiteGradient.addColorStop(1, "transparent");
ctx.fillStyle = whiteGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
let blackGradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
blackGradient.addColorStop(0, "transparent");
blackGradient.addColorStop(1, "#000");
ctx.fillStyle = blackGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
draw();
检测鼠标按下、移动、抬起事件,然后更改信息:
let isDraw = false;
container.onmousedown = () => (isDraw = true);
container.onmousemove = (e) => {
if (!isDraw) return;
if (检测到鼠标移动到目标区域) {
更改显示位置;
更改颜色;
}
更改全部颜色及文本信息;
};
container.onmouseup = () => (isDraw = false);
// 避免一些bug,但这样影响效果
canvas.onmouseout = () => (isDraw = false);
通过鼠标位置计算滑块位置,然后计算比例修改透明度和色相值,用到了TinyColor来解析颜色。
let bg_color = {
h: 0,
s: "100%",
l: "50%",
a: 1
};
container.onmousemove = (e) => {
if (!isDraw) return;
if (e.target === opacity || e.target.parentNode === opacity) {
let y = e.pageY - opacity.getBoundingClientRect().top;
if (y < 0) y = 0.1;
if (y > 150) y = 145;
opaSlide.style.top = y + "px";
bg_color.a = 1 - y / opacity.offsetHeight;
}
if (e.target === hue || e.target.parentNode === hue) {
let y = e.pageY - hue.getBoundingClientRect().top;
if (y < 0) y = 0.1;
if (y > 150) y = 145;
hueSlide.style.top = y + "px";
bg_color.h = (y / hue.offsetHeight) * 360;
draw(tinycolor(bg_color));
}
};
在stackoverflow上找到了一个公式,通过x,y在画布上的比例计算颜色的亮度和饱和度:
if (e.target === spectrum || e.target.parentNode === spectrum) {
let x = e.pageX - speRect.left - cursor.offsetWidth / 2;
let y = e.pageY - speRect.top - cursor.offsetHeight / 2;
let r = cursor.offsetHeight;
if (x < 0) x = 0;
if (x > speRect.width - r) x = speRect.width - r;
if (y < 0) y = 0.1;
if (y > speRect.height - r) y = speRect.height - r;
cursor.style.left = x + "px";
cursor.style.top = y + "px";
// from stackoverflow
let hsvValue = 1 - y / speRect.height;
let hsvSaturation = x / speRect.width;
let lightness = (hsvValue / 2) * (2 - hsvSaturation);
let saturation =
(hsvValue * hsvSaturation) / (1 - Math.abs(2 * lightness - 1));
bg_color.l = lightness * 100 + "%";
bg_color.s = saturation * 100 + "%";
}
这里就是tinycolor的用法,可以看一下官方文档。
let method = "toRgbString";
function changeBg() {
let color = tinycolor(bg_color)[method]();
value.style.background = color;
value.style.color = parseInt(bg_color.l) >= 50 ? "black" : "white";
value.innerHTML = color;
document.body.style.background = `linear-gradient(to top, transparent, ${color})`
cursor.style.background = color;
}
let index = 0;
function changeType() {
switch (index % 3) {
case 0:
method = "toRgbString";
break;
case 1:
method = "toHex8String";
break;
case 2:
method = "toHslString";
break;
}
index++;
}
value.onclick = () => {
changeType();
changeBg();
};
bug巨多,太菜了,源码放到了codepen上,想看的话可以看看
Color Picker (codepen.io)
canvas挺好用,线性渐变更好用,但我太菜了。。。
我真的是有拖延症,早就写完代码了,博客拖了几天也没写,bug也不想再改了,代码拉就拉了吧,大伙随便看看,还是有一点参考作用的。
最后求个三连!!!