southernMD 南山有壶酒
首页
文章
留言板
十年
关于
音乐
作词 : 偽物
分类
- 暂无内容
站点信息
标签云
目录
作词 : 偽物

如何手写一个滑动拾色器
2023/8/12 15:57:48 |
0 |
50
JS
以网易云音乐滑动拾色器为例
效果总览

如图所示
基础代码
我需要两个滑动块,以上方的滑动块为底色,下方的滑动块为灰度产生的颜色交给主题,现在我们称上方的颜色栏为彩色栏,下方的灰色栏为灰度栏
html
复制代码
<template>
<div class="pick-color">
<div class="hue">
<canvas id="canvasHue" width="240" height="4"></canvas>
<div class="btn btnT btnX" ref="btnT" @mousedown="beginMove"></div>
</div>
<div class="mask">
<canvas id="canvasSMask" width="240" height="4"></canvas>
<div class="btn btnB btnX" ref="btnB" @mousedown="beginMove"></div>
</div>
</div>
</template>
css
复制代码
<style scoped lang="less">
@btnT: var(--btnT,0);//颜色控制
@btnB: var(--btnB,0);//颜色控制
.pick-color {
display: flex;
flex-direction: column;
align-self: start;
justify-content: center;
align-items: center;
height: 100vh;
canvas {
position: absolute;
top: 0px;
left: 0px;
border-radius: 2em;
}
.btn {
height: 13px;
width: 13px;
position: absolute;
left: 0;
top: -6px;
border: 1px solid #cacaca;
background-color: #ffffff;
box-shadow: 0px 0px 3px rgba(0, 0, 0, .4);
border-radius: 2em;
cursor: pointer;
}
.btnT{
left: @btnT;
}
.btnB{
left: @btnB;
}
.hue {
position: relative;
width: 240px;
height: 4px;
border-radius: 2em;
margin-top: 5px;
order: 1;
}
.mask {
position: relative;
width: 240px;
height: 4px;
margin-top: 15px;
order: 2;
}
/* #canvasHue与#canvasSMask只做显示用 */
#canvasHue{
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f0f 100%);
}
#canvasSMask{
background: linear-gradient(to right,hsl(0, 0%, 20%),#ff0000);
}
}
</style>

效果如图所示
为按钮添加上滑动事件
然后我们给按钮绑定上滑动事件使其可以在区域内滑动
js
复制代码
let clickX: number;
let _that: HTMLElement;//点击的按钮
let which: HTMLElement; //修改的对象
const widthCanvans = 240;
const widthBtn = 14
const beginMove = (e: MouseEvent) => {
clickX = e.pageX
_that = e.target as HTMLElement //点击的按钮
which = _that?.parentNode as HTMLElement //修改的对象
//绑定鼠标开始按下拖动事件与结束拖动事件
window.addEventListener('mousemove', movingFn)
window.addEventListener('mouseup', endMove)
}
function movingFn(e: MouseEvent) {
//baseT与baseB用于记录上次拖动结束时滑块距离左侧的宽度
//移动距离需要加入原本的宽度
let baseT = Number(localStorage.getItem('baseT'))
let baseB = Number(localStorage.getItem('baseB'))
if (which.classList.contains('hue')) {
let moveDistance = e.pageX - clickX + baseT
if (moveDistance < 0) moveDistance = 0
else if (moveDistance > widthCanvans - widthBtn) moveDistance = widthCanvans - widthBtn
_that.style.left = moveDistance + 'px'
} else if (which.classList.contains('mask')) {
let moveDistance = e.pageX - clickX + baseB
if (moveDistance < 0) moveDistance = 0
else if (moveDistance > widthCanvans - widthBtn) moveDistance = widthCanvans - widthBtn
_that.style.left = moveDistance + 'px'
}
}
function endMove(): void {
let str = _that.style.left
window.removeEventListener('mousemove', movingFn)
window.removeEventListener('mouseup', endMove)
if (which.classList.contains('hue')) {
localStorage.setItem('baseT',str.substring(0, str.length - 2))
} else if (which.classList.contains('mask')) {
localStorage.setItem('baseB',str.substring(0, str.length - 2))
}
}

效果如图所示
初始化canvas颜色
js
复制代码
let Hue: HTMLCanvasElement;
let SMask: HTMLCanvasElement
onMounted(() => {
document.documentElement.style.setProperty(`--btnT`, `${Number(localStorage.getItem('baseT'))}px`)
document.documentElement.style.setProperty(`--btnB`, `${Number(localStorage.getItem('baseB'))}px`)
Hue = document.querySelector('#canvasHue') as HTMLCanvasElement;
SMask = document.querySelector('#canvasSMask') as HTMLCanvasElement;
//上颜色
let ctx = Hue.getContext('2d') as CanvasRenderingContext2D;
let linearGradient = ctx.createLinearGradient(0, 0, 240, 0) //创建线性渐变
// 颜色断点
// background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f0f 100%);
linearGradient.addColorStop(0, '#ff0000');
linearGradient.addColorStop(.17, '#ffff00');
linearGradient.addColorStop(.33, '#0f0');
linearGradient.addColorStop(.5, '#0ff');
linearGradient.addColorStop(.67, '#00f');
linearGradient.addColorStop(.8, '#f0f');
linearGradient.addColorStop(.9, '#f0f');
linearGradient.addColorStop(1, '#ff0000');
//设置渐变
ctx.fillStyle = linearGradient;
//绘制矩形
ctx.fillRect(0, 0, 240, 4);
let imageData = ctx.getImageData(0, 0, Hue.width, Hue.height)
let color = getPxColor(imageData, Number(localStorage.getItem('baseT')), 2);
document.documentElement.style.setProperty(`--bottomLinearColor`, `hsl(${hsl(color)[0]}, 100%, 70%)`)
//下颜色
ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
linearGradient = ctx.createLinearGradient(0, 0, 240, 0) //创建线性渐变
linearGradient.addColorStop(0, 'hsl(0, 0%, 20%)');
let t = getComputedStyle(document.documentElement).getPropertyValue('--bottomLinearColor');
linearGradient.addColorStop(1, t);
ctx.fillStyle = linearGradient;
ctx.fillRect(0, 0, 240, 4);
let imageData2 = ctx.getImageData(0, 0, SMask.width, SMask.height)
let color2 = getPxColor(imageData2, Number(localStorage.getItem('baseB')), 2);
document.documentElement.style.setProperty(`--primaryColor`, `rgb(${color2[0]}, ${color2[1]}, ${color2[2]})`)
})
getPxColor函数可以将指定canvas图形坐标的颜色返回为rgb,hsl函数可以将rgb函数模型转换为hsl模型,在初始化时根据上彩色栏的颜色设置为下灰度栏颜色的基本值然后初始化下部分灰度栏
js
复制代码
export function getPxColor(imageData: ImageData, x: number, y: number) {
let color: any = [];
let width = imageData.width;
let index = (y * width + x) * 4; // 计算像素在 data 数组中的索引
color[0] = imageData.data[index]; // Red
color[1] = imageData.data[index + 1]; // Green
color[2] = imageData.data[index + 2]; // Blue
return color;
}
js
复制代码
export function hsl (rgb:Array<number>) {
const r:number = rgb[0] / 255;
const g:number = rgb[1] / 255;
const b:number = rgb[2] / 255;
const min:number = Math.min(r, g, b);
const max:number = Math.max(r, g, b);
const delta:number= max - min;
let h:number = 0;
let s:number;
if (r === max) {
h = (g - b) / delta;
} else if (g === max) {
h = 2 + (b - r) / delta;
} else if (b === max) {
h = 4 + (r - g) / delta;
}
h = Math.min(h * 60, 360);
if (h < 0) {
h += 360;
}
const l:number = (min + max) / 2;
if (max === min) {
s = 0;
} else if (l <= 0.5) {
s = delta / (max + min);
} else {
s = delta / (2 - max - min);
}
return [h, s * 100, l * 100];
};
然后我们需要注释掉
css
复制代码
/* #canvasHue{
background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f0f 100%);
}
#canvasSMask{
background: linear-gradient(to right,hsl(0, 0%, 20%),#ff0000);
}*/
滑动修改
现在我们修改movingFn函数使得其可以在滑动的同时修改灰度颜色与提取主题颜色,这里分两种情况,如果拖动的是上方的彩色栏,则先对下方灰度栏进行重绘然后重新提取主题色;如果拖动的为灰度栏则直接提取颜色
js
复制代码
function movingFn(e: MouseEvent) {
let baseT = Number(localStorage.getItem('baseT'))
let baseB = Number(localStorage.getItem('baseB'))
if (which.classList.contains('hue')) {
let moveDistance = e.pageX - clickX + baseT
if (moveDistance < 0) moveDistance = 0
else if (moveDistance > widthCanvans - widthBtn) moveDistance = widthCanvans - widthBtn
_that.style.left = moveDistance + 'px'
//上侧取出颜色
let ctx = Hue.getContext('2d') as CanvasRenderingContext2D;
let imageData = ctx.getImageData(0, 0, Hue.width, Hue.height)
let color = getPxColor(imageData, moveDistance, 2);
document.documentElement.style.setProperty(`--bottomLinearColor`, `hsl(${hsl(color)[0]}, 100%, 70%)`)
//取出下侧颜色 重画canvas
ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
let linearGradient = ctx.createLinearGradient(0, 0, 240, 0) //创建线性渐变
linearGradient.addColorStop(0, 'hsl(0, 0%, 20%)');
let t = getComputedStyle(document.documentElement).getPropertyValue('--bottomLinearColor');
linearGradient.addColorStop(1, t);
ctx.fillStyle = linearGradient;
ctx.fillRect(0, 0, 240, 4);
ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
imageData = ctx.getImageData(0, 0, SMask.width, SMask.height)
color = getPxColor(imageData, baseB, 2);
document.documentElement.style.setProperty(`--primaryColor`, `rgb(${color[0]}, ${color[1]}, ${color[2]})`)
} else if (which.classList.contains('mask')) {
let moveDistance = e.pageX - clickX + baseB
if (moveDistance < 0) moveDistance = 0
else if (moveDistance > widthCanvans - widthBtn) moveDistance = widthCanvans - widthBtn
_that.style.left = moveDistance + 'px'
let ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
let imageData = ctx.getImageData(0, 0, SMask.width, SMask.height)
let color = getPxColor(imageData, moveDistance, 2);
document.documentElement.style.setProperty(`--primaryColor`, `rgb(${color[0]}, ${color[1]}, ${color[2]})`)
}
}
最后
我们写一个背景框进行预览
html
复制代码
<div style="width: 20px; height: 20px; background-color: var(--primaryColor);"></div>
完成

完整代码
js
复制代码
<template>
<div class="pick-color">
<div class="hue">
<canvas id="canvasHue" width="240" height="4"></canvas>
<div class="btn btnT btnX" ref="btnT" @mousedown="beginMove"></div>
</div>
<div class="mask">
<canvas id="canvasSMask" width="240" height="4"></canvas>
<div class="btn btnB btnX" ref="btnB" @mousedown="beginMove"></div>
</div>
<div style="width: 20px; height: 20px; background-color: var(--primaryColor);"></div>
</div>
</template>
<script setup lang="ts">
import {onMounted} from 'vue'
import {getPxColor} from '@/utils/getCanvasColor'
import {hsl} from '@/utils/hsl'
let clickX: number;
let _that: HTMLElement;//点击的按钮
let which: HTMLElement; //修改的对象
const widthCanvans = 240;
const widthBtn = 14
const beginMove = (e: MouseEvent) => {
clickX = e.pageX
_that = e.target as HTMLElement //点击的按钮
which = _that?.parentNode as HTMLElement //修改的对象
window.addEventListener('mousemove', movingFn)
window.addEventListener('mouseup', endMove)
}
function movingFn(e: MouseEvent) {
let baseT = Number(localStorage.getItem('baseT'))
let baseB = Number(localStorage.getItem('baseB'))
if (which.classList.contains('hue')) {
let moveDistance = e.pageX - clickX + baseT
if (moveDistance < 0) moveDistance = 0
else if (moveDistance > widthCanvans - widthBtn) moveDistance = widthCanvans - widthBtn
_that.style.left = moveDistance + 'px'
//上侧取出颜色
let ctx = Hue.getContext('2d') as CanvasRenderingContext2D;
let imageData = ctx.getImageData(0, 0, Hue.width, Hue.height)
let color = getPxColor(imageData, moveDistance, 2);
document.documentElement.style.setProperty(`--bottomLinearColor`, `hsl(${hsl(color)[0]}, 100%, 70%)`)
//取出下侧颜色 重画canvas
ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
let linearGradient = ctx.createLinearGradient(0, 0, 240, 0) //创建线性渐变
linearGradient.addColorStop(0, 'hsl(0, 0%, 20%)');
let t = getComputedStyle(document.documentElement).getPropertyValue('--bottomLinearColor');
linearGradient.addColorStop(1, t);
ctx.fillStyle = linearGradient;
ctx.fillRect(0, 0, 240, 4);
ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
imageData = ctx.getImageData(0, 0, SMask.width, SMask.height)
color = getPxColor(imageData, baseB, 2);
document.documentElement.style.setProperty(`--primaryColor`, `rgb(${color[0]}, ${color[1]}, ${color[2]})`)
} else if (which.classList.contains('mask')) {
let moveDistance = e.pageX - clickX + baseB
if (moveDistance < 0) moveDistance = 0
else if (moveDistance > widthCanvans - widthBtn) moveDistance = widthCanvans - widthBtn
_that.style.left = moveDistance + 'px'
let ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
let imageData = ctx.getImageData(0, 0, SMask.width, SMask.height)
let color = getPxColor(imageData, moveDistance, 2);
document.documentElement.style.setProperty(`--primaryColor`, `rgb(${color[0]}, ${color[1]}, ${color[2]})`)
}
}
function endMove(): void {
let str = _that.style.left
window.removeEventListener('mousemove', movingFn)
window.removeEventListener('mouseup', endMove)
if (which.classList.contains('hue')) {
localStorage.setItem('baseT',str.substring(0, str.length - 2))
} else if (which.classList.contains('mask')) {
localStorage.setItem('baseB',str.substring(0, str.length - 2))
}
}
let Hue: HTMLCanvasElement;
let SMask: HTMLCanvasElement
onMounted(() => {
document.documentElement.style.setProperty(`--btnT`, `${Number(localStorage.getItem('baseT'))}px`)
document.documentElement.style.setProperty(`--btnB`, `${Number(localStorage.getItem('baseB'))}px`)
Hue = document.querySelector('#canvasHue') as HTMLCanvasElement;
SMask = document.querySelector('#canvasSMask') as HTMLCanvasElement;
//上颜色
let ctx = Hue.getContext('2d') as CanvasRenderingContext2D;
let linearGradient = ctx.createLinearGradient(0, 0, 240, 0) //创建线性渐变
// 颜色断点
// background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f0f 100%);
linearGradient.addColorStop(0, '#ff0000');
linearGradient.addColorStop(.17, '#ffff00');
linearGradient.addColorStop(.33, '#0f0');
linearGradient.addColorStop(.5, '#0ff');
linearGradient.addColorStop(.67, '#00f');
linearGradient.addColorStop(.8, '#f0f');
linearGradient.addColorStop(.9, '#f0f');
linearGradient.addColorStop(1, '#ff0000');
//设置渐变
ctx.fillStyle = linearGradient;
//绘制矩形
ctx.fillRect(0, 0, 240, 4);
let imageData = ctx.getImageData(0, 0, Hue.width, Hue.height)
let color = getPxColor(imageData, Number(localStorage.getItem('baseT')), 2);
document.documentElement.style.setProperty(`--bottomLinearColor`, `hsl(${hsl(color)[0]}, 100%, 70%)`)
//下颜色
ctx = SMask.getContext('2d') as CanvasRenderingContext2D;
linearGradient = ctx.createLinearGradient(0, 0, 240, 0) //创建线性渐变
linearGradient.addColorStop(0, 'hsl(0, 0%, 20%)');
let t = getComputedStyle(document.documentElement).getPropertyValue('--bottomLinearColor');
linearGradient.addColorStop(1, t);
ctx.fillStyle = linearGradient;
ctx.fillRect(0, 0, 240, 4);
let imageData2 = ctx.getImageData(0, 0, SMask.width, SMask.height)
let color2 = getPxColor(imageData2, Number(localStorage.getItem('baseB')), 2);
document.documentElement.style.setProperty(`--primaryColor`, `rgb(${color2[0]}, ${color2[1]}, ${color2[2]})`)
})
</script>
<style scoped lang="less">
@btnT: var(--btnT,0);//颜色控制
@btnB: var(--btnB,0);//颜色控制
.pick-color {
display: flex;
flex-direction: column;
align-self: start;
justify-content: center;
align-items: center;
height: 100vh;
canvas {
position: absolute;
top: 0px;
left: 0px;
border-radius: 2em;
}
.btn {
height: 13px;
width: 13px;
position: absolute;
left: 0;
top: -6px;
border: 1px solid #cacaca;
background-color: #ffffff;
box-shadow: 0px 0px 3px rgba(0, 0, 0, .4);
border-radius: 2em;
cursor: pointer;
}
.btnT{
left: @btnT;
}
.btnB{
left: @btnB;
}
.hue {
position: relative;
width: 240px;
height: 4px;
border-radius: 2em;
margin-top: 5px;
order: 1;
}
.mask {
/* 饱和度x轴实现:从左到右,纯白 >>> 透明的渐变 */
position: relative;
width: 240px;
height: 4px;
margin-top: 15px;
order: 2;
}
// #canvasHue{
// background: linear-gradient(to right, #ff0000 0%, #ffff00 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f0f 100%);
// }
// #canvasSMask{
// background: linear-gradient(to right,hsl(0, 0%, 20%),#ff0000);
// }
}
</style>
其他
如何使用js提取css变量
js
复制代码
//设置
document.documentElement.style.setProperty(`--primaryColor`, `#66ccff`)
//读取
getComputedStyle(document.documentElement).getPropertyValue('--primaryColor')
评论
评论列表(0)
移至左侧
回到顶部
日间模式
开启音乐
隐藏面板
a