先让我找找思路
虽然我们有现成的HTML控件,但是有两点可以让我们放弃它。
- 功能无法定制
- 不同浏览器样式不统一
所以因此我们就自己来实现一个吧~
老规矩,没思路找项目,这次我找到的项目叫做Iconfont。
然后再找一个有缘图标。
咳,不要在意图标本身,我们可以发现这里有一个颜色选择器,我们来点开它来看一看。
可以看出这个颜色选择器拥有三个部分
- 颜色存储板
- 颜色选择区
- 输入框and确认按钮
先来颜色选择区是如何实现的,毕竟这是颜色选择器的核心。
可以看出它是通过svg
来实现的,svg
是什么这里就不赘述了。
标记一的颜色块是通过svg
的两个rect
来定义的。
<rect x="0" y="0" width="100%" height="100%" fill="url(#gradient-white)"></rect>
<rect x="0" y="0" width="100%" height="100%" fill="url(#gradient-black)"></rect>
x
、y
、width
、height
这四个参数将这个颜色块设置为整个父元素的宽高。
那么fill="url(#gradient-white)"
这个参数代表着什么呢?
经过我搜索我得出如下结果:
这是为了实现渐变的效果,可以将url(#gradient-white)
对应元素的渐变设置到rect
上。
#gradient-white
是一个svg
中defs
一个叫做linearGradient
的元素。
<defs>
是个嘛玩意:
SVG 允许我们定义以后需要重复使用的图形元素。 建议把所有需要再次使用的引用元素定义在defs元素里面。这样做可以增加SVG内容的易读性和可访问性。 在defs元素中定义的图形元素不会直接呈现。 你可以在你的视口的任意地方利用
<use>
元素呈现这些元素。- MDN
<linearGradient>
是个嘛玩意:
linearGradient元素用来定义线性渐变,用于图形元素的填充或描边。- MDN
在Iconfont
中我们也可以找到对应的元素。
可以看出有两个不同的渐变,一个是从白开始,一个是从黑开始。
这里为什么要有两个渐变?
正常情况下:
将黑色渐变块隐藏掉后:
对比两者后,我们不难发现,黑色渐变块是从上往下,白色渐变块是从左往右,两者重叠就是我们看到的那种效果。
接下来看看它的<linearGradient>
是如何定义的。
<defs>
<!-- 渐变1 -->
<linearGradient id="gradient-black" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#000000" stop-opacity="1"></stop>
<stop offset="100%" stop-color="#CC9A81" stop-opacity="0"></stop>
</linearGradient>
<!-- 渐变2 -->
<linearGradient id="gradient-white" x1="0%" y1="100%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"></stop>
<stop offset="100%" stop-color="#CC9A81" stop-opacity="0"></stop>
</linearGradient>
</defs>
<linearGradient>
标签的 x1、x2、y1、y2 属性可定义渐变的开始和结束位置渐变的颜色范围可由两种或多种颜色组成。每种颜色通过一个
<stop>
标签来规定。offset 属性用来定义渐变的开始和结束位置。 - w3school
元素<stop>
的定义:
一个渐变上的颜色坡度,是用stop元素定义的。stop元素可以是
<linearGradient>
元素或者<radialGradient>
元素的子元素。 - MDN
那么<stop>
上面的stop-color
以及stop-opacity
是什么呢?
其实顾名思义,就是渐变停止处的颜色以及透明度。
那么由此可见的:
- 渐变1
<linearGradient>
定义的x1="0%" y1="100%" x2="0%" y2="0%"
表示从左下到左上的线性渐变- 第一个
<stop>
定义的offset="0%" stop-color="#FFFFFF" stop-opacity="1"
表示开始位置的颜色为#FFFFFF
、透明度为1 - 第二个
<stop>
定义的offset="100%" stop-color="#CC9A81" stop-opacity="0"
表示结束位置的颜色为#CC9A81
、透明度为0
- 渐变2
<linearGradient>
定义的x1="0%" y1="100%" x2="100%" y2="100%"
表示从左下到右下的线性渐变
再看看右边那个长条形的颜色块是怎么实现的:
<linearGradient id="gradient-hsv" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#FF0000" stop-opacity="1"></stop>
<stop offset="13%" stop-color="#FF00FF" stop-opacity="1"></stop>
<stop offset="25%" stop-color="#8000FF" stop-opacity="1"></stop>
<stop offset="38%" stop-color="#0040FF" stop-opacity="1"></stop>
<stop offset="50%" stop-color="#00FFFF" stop-opacity="1"></stop>
<stop offset="63%" stop-color="#00FF40" stop-opacity="1"></stop>
<stop offset="75%" stop-color="#0BED00" stop-opacity="1"></stop>
<stop offset="88%" stop-color="#FFFF00" stop-opacity="1"></stop>
<stop offset="100%" stop-color="#FF0000" stop-opacity="1"></stop>
</linearGradient>
这里的话,运用上面我们学到知识可以很轻松的明白它是个什么意思了。
现在我会画了,该怎样获取指定位置的颜色呢?
通过报错,我得知了它是使用HSV
颜色模型来设置的。
这里值得一提的是,认识阿里大佬的赶紧让他修bug了,颜色块拖动不了,一拖就报错。
那么HSV
颜色模型是什么东西呢?
HSV(Hue, Saturation, Value)是根据颜色的直观特性由A. R. Smith在1978年创建的一种颜色空间, 也称六角锥体模型(Hexcone Model)。
这个模型中颜色的参数分别是:色调(H),饱和度(S),明度(V)。 - 百度百科
- 色调H使用角度度量,取值范围0°~360°
- 饱和度S表示颜色接近光谱色的程度,取值范围0%~100%
- 明度V表示颜色的明亮程度,取值范围0%~100%
由上面的取值范围通过计算就可以得出我们想要的颜色了。
那么颜色该如何计算呢?
我们先抛弃Iconfont
的代码,毕竟经过混淆了,看得有点累。
首先第一步,我们需要将HSV
中的H
色调给整出来,也就是右边那个长条形的颜色块。
这里是一个简单实现:
// index.html
<div class="H">
<svg>
<defs>
<linearGradient id="gradient-hsv" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#FF0000" stop-opacity="1"></stop>
<stop offset="13%" stop-color="#FF00FF" stop-opacity="1"></stop>
<stop offset="25%" stop-color="#8000FF" stop-opacity="1"></stop>
<stop offset="38%" stop-color="#0040FF" stop-opacity="1"></stop>
<stop offset="50%" stop-color="#00FFFF" stop-opacity="1"></stop>
<stop offset="63%" stop-color="#00FF40" stop-opacity="1"></stop>
<stop offset="75%" stop-color="#0BED00" stop-opacity="1"></stop>
<stop offset="88%" stop-color="#FFFF00" stop-opacity="1"></stop>
<stop offset="100%" stop-color="#FF0000" stop-opacity="1"></stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="url(#gradient-hsv)"></rect>
</svg>
<div class="slide"></div>
</div>
<div class="color">
<h2>rgb(102, 81, 192)</h2>
<div class="show" style="background: rgb(102, 81, 192);">
</div>
</div>
// style.css
svg{
width: 100%;
height: 100%;
}
.H{
width: 20px;
height: 200px;
position: relative;
}
.slide{
position: absolute;
left: 4px;
top: -8px;
border: 8px solid transparent;
border-right-color: #888;
width: 0;
height: 0;
pointer-events: none;
}
.show{
width: 300px;
height: 100px;
}
// app.js
let H = document.querySelector('.H');
let HRect = H.querySelector('rect');
let HSlide = H.querySelector('.slide')
let HFlag = false;
let Hval = 0;
let Sval = 100;
let Vval = 100;
HRect.addEventListener('mousedown', () => {
HFlag = true;
})
HRect.addEventListener('mousemove', ev => {
if(!HFlag) return;
// ev.offsetY这个值为鼠标相对于源元素的Y坐标,算出滑块在元素中的定位比例
let offsetY = ev.offsetY / H.offsetHeight;
HSlide.style.top = ev.offsetY - 8 + 'px'
// 因为H值的范围是0~360,乘以比例就可以得出一个颜色值了
Hval = 360 * offsetY;
setHSV();
})
HRect.addEventListener('mouseup', () => {
HFlag = false;
})
let colorEl = document.querySelector('.color');
let colorTitle = colorEl.querySelector('h2');
let colorShow = colorEl.querySelector('.show');
function setHSV(){
// 这里算出对应的RGB值
let color = `
rgb(${hsvtorgb(Hval, Sval, Vval).join(',')})
`
colorTitle.innerHTML = color;
colorShow.style.background = color;
}
setHSV();
还有一个函数hsvtorgb
,因为这个代码是我拷贝别人的,就另外贴出来吧。
function hsvtorgb(h, s, v) {
s = s / 100;
v = v / 100;
var h1 = Math.floor(h / 60) % 6;
var f = h / 60 - h1;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
var r, g, b;
switch (h1) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
实际上我看不懂上面的算法。狗头🐶
上面的代码实现了如下效果:
然后我们实现出HSV
中代表SV
的颜色块
因为不想写太长,我就将修改部分的代码展示出来:
// index.html
<div class="HSV">
<div class="SV">
<svg>
<defs>
<linearGradient id="gradient-black" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#000000" stop-opacity="1"></stop>
<!-- endColor -->
<stop class="endColor" offset="100%" stop-color="#CC9A81" stop-opacity="0"></stop>
</linearGradient>
<linearGradient id="gradient-white" x1="0%" y1="100%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="1"></stop>
<!-- endColor -->
<stop class="endColor" offset="100%" stop-color="#CC9A81" stop-opacity="0"></stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="url(#gradient-white)"></rect>
<rect x="0" y="0" width="100%" height="100%" fill="url(#gradient-black)"></rect>
</svg>
<div class="slide"></div>
</div>
<div class="H">
// ......
</div>
</div>
// style.css
.HSV{
display: flex;
}
.SV{
width: 200px;
margin-right: 10px;
}
// app.js
let SV = document.querySelector('.SV');
// 这里我们选择第二个矩形,这样就不会被另一个矩形给遮住点击事件
let SVRect = SV.querySelector('rect:last-child');
let SVSlide = SV.querySelector('.slide');
let SVFlag = false;
SVRect.addEventListener('mousedown', () => {
SVFlag = true;
})
SVRect.addEventListener('mousemove', ev => {
if(!SVFlag) return;
// 一波换算得出滑块的比例然后乘以100就是对应的SV的值了
Sval = ev.offsetX / SV.offsetWidth * 100;
// 透明度因为方向的问题所以就反转一下
Vval = (1 - ev.offsetY / SV.offsetHeight) * 100;
SVSlide.style.left = ev.offsetX + 3 + 'px';
SVSlide.style.top = ev.offsetY + 3 + 'px';
setHSV();
})
SVRect.addEventListener('mouseup', () => {
SVFlag = false;
})
let endColors = document.querySelectorAll('.endColor');
function setHSV(){
// 这里算出对应的RGB值
// 这个是最终结果的颜色
let color = `
rgb(${hsvtorgb(Hval, Sval, Vval).join(',')})
`;
// 这个是给SV设置的结束颜色
let StopColor = `
rgb(${hsvtorgb(Hval, 100, 100).join(',')})
`;
// 为在页面中的rect设置结束颜色
[...endColors].map(el => el.setAttribute('stop-color', StopColor));
colorTitle.innerHTML = color;
colorShow.style.background = color;
}
由上面的代码,我可以得出以下效果:
实际上,上面的代码还不完全,从下面的图我们可以看出它的两端都是白色。
我找了半天svg
的问题,为啥跟Iconfont
的结果不一样...
然后我发现,原来Iconfont
给整个颜色块有一个纯色的背景色
我们加上去瞅一瞅效果吧。
// app.js
function setHSV(){
// ...
SV.style.background = StopColor;
// ...
}
到这里,颜色选择器的核心功能就大功告成了。
结束了
虽然还有另外两个版块的功能,但是因为不想写太长的文章就到这里结束吧。
虽然这篇文章结束了,但是你还可以继续完善它的功能,比如:
- 完成其他两个版块的功能:颜色存储板、输入框
- 将RGB转换成16进制
- 将它编写成弹出框
- 使用Vue组件化它
最后感谢你的阅读。