共计 8829 个字符,预计需要花费 23 分钟才能阅读完成。
前言
今天主要介绍一下 web 端颜色拾色器吸管工具的使用。ued 需要在页面中用吸管吸取页面中的颜色,类似 ps 等工具里面的吸管工具,这个在 web 端网页中如何实现呢?
假如需要吸取桌面上面的任何颜色,最好使用原生 api 的方式,下面主要介绍两种原生方式的实现。
EyeDropper api 实现
简单代码实现如下
document.getElementById("start-button").addEventListener("click", () => {const resultElement = document.getElementById("result");
if (!window.EyeDropper) {
resultElement.textContent =
"Your browser does not support the EyeDropper API";
return;
}
const eyeDropper = new EyeDropper();
eyeDropper
.open()
.then((result) => {
resultElement.textContent = result.sRGBHex;
resultElement.style.backgroundColor = result.sRGBHex;
})
.catch((e) => {resultElement.textContent = e;});
});
案例如下:
但是这个是比较新的 api,浏览器兼容性方案,safari 不支持。大家可以试试。
input type=color
input 自带的颜色选择器貌似有吸管工具,可以使用。这个兼容性相对较好,可以尝试使用一下。
如下代码
案例如下:
其他方案
其他方案实现起来相对较麻烦,不太适合。
网上有个方案是利用网页截图 -》绘制 & 获取像素点颜色 -》绘制放大镜和颜色值
源码如下:
/**
* 网页颜色吸管工具【拾色器】* date: 2021.10.31
* author: alanyf
*/
import domtoimage from './dom-to-image.js';
import {drawTooltip, getCanvas, getCanvasRectColor, loadImage, rbgaObjToHex, renderColorInfo} from './helper';
import type {IProps, IRect} from './interface';
export * from './interface';
/**
* 网页拾色器【吸管工具】*/
class ColorPipette {container: any = {};
listener: Record void> = {};
rect: IRect = {x: 0, y: 0, width: 0, height: 0};
canvas: any = {};
ctx: any;
scale = 1;
magnifier: any = null;
colorContainer: any = null;
colors: string[][] = [];
tooltipVisible = true;
useMagnifier = false;
constructor(props: IProps) {
try {const { container, listener, scale = 1, useMagnifier = false} = props;
this.container = container || document.body;
this.listener = listener || {};
this.rect = this.container.getBoundingClientRect();
this.scale = scale > 4 ? 4 : scale;
this.useMagnifier = useMagnifier;
// 去除 noscript 标签,可能会导致
const noscript = document.body.querySelector('noscript');
noscript?.parentNode?.removeChild(noscript);
this.initCanvas();} catch (err) {console.error(err);
this.destroy();}
}
/**
* 初始化 canvas
*/
initCanvas() {const { rect, scale} = this;
const {x, y, width, height} = rect;
const {canvas, ctx} = getCanvas({
width: rect.width,
height: rect.height,
scale,
attrs: {
class: 'color-pipette-canvas-container',
style: `
position: fixed;
left: ${x}px;
top: ${y}px;
z-index: 10000;
cursor: pointer;
width: ${width}px;
height: ${height}px;
`,
},
});
this.canvas = canvas;
this.ctx = ctx;
}
/**
* 开始
*/
async start() {
try {await this.drawCanvas();
document.body.appendChild(this.canvas);
const tooltip = drawTooltip('按 Esc 可退出');
document.body.appendChild(tooltip);
setTimeout(() => tooltip?.parentNode?.removeChild(tooltip), 2000);
// 添加监听
this.canvas.addEventListener('mousemove', this.handleMove);
this.canvas.addEventListener('mousedown', this.handleDown);
document.addEventListener('keydown', this.handleKeyDown);
} catch (err) {console.error(err);
this.destroy();}
}
/**
* 结束销毁 dom,清除事件监听
*/
destroy() {this.canvas.removeEventListener('mousemove', this.handleMove);
this.canvas.removeEventListener('mousedown', this.handleDown);
document.removeEventListener('keydown', this.handleKeyDown);
this.canvas?.parentNode?.removeChild(this.canvas);
this.colorContainer?.parentNode?.removeChild(this.colorContainer);
}
/**
* 将 dom 节点画到 canvas 里
*/
async drawCanvas() {const base64 = await domtoimage.toPng(this.container, { scale: this.scale}).catch(() => '');
if (!base64) {return;}
const img = await loadImage(base64);
if (!img) {return;}
this.ctx.drawImage(img, 0, 0, this.rect.width, this.rect.height);
}
/**
* 处理鼠标移动
*/
handleMove = (e: any) => {const { color, colors} = this.getPointColors(e);
const {onChange = () => '' } = this.listener;
const point = {x: e.pageX + 15, y: e.pageY + 15};
const colorContainer = renderColorInfo({
containerDom: this.colorContainer,
color,
colors,
point,
});
if (!this.colorContainer) {
this.colorContainer = colorContainer;
document.body.appendChild(colorContainer);
}
onChange({color, colors});
}
/**
* 处理鼠标按下
*/
handleDown = (e: any) => {const { onOk = () => '' } = this.listener;
const res = this.getPointColors(e);
console.log(JSON.stringify(res.colors, null, 4));
onOk(res);
this.destroy();}
/**
* 处理键盘按下 Esc 退出拾色
*/
handleKeyDown = (e: KeyboardEvent) => {if (e.code === 'Escape') {this.destroy();
}
};
/**
* 获取鼠标点周围的颜色整列
*/
getPointColors(e: any) {const { ctx, rect, scale} = this;
let {pageX: x, pageY: y} = e;
x -= rect.x;
y -= rect.y;
const color = this.getPointColor(x, y);
const size = 19;
const half = Math.floor(size / 2);
const info = {x: x - half, y: y - half, width: size, height: size};
const colors = getCanvasRectColor(ctx, info, scale);
return {color, colors};
}
/**
* 获取鼠标点的颜色
*/
getPointColor(x: number, y: number) {const { scale} = this;
const {data} = this.ctx.getImageData(x * scale, y * scale, 1, 1);
const r = data[0];
const g = data[1];
const b = data[2];
const a = data[3] / 255;
const rgba = {r, g, b, a};
return rbgaObjToHex(rgba);
}
}
export default ColorPipette;
import type {IColors, IRect, IRgba} from './interface';
/**
* 加载 base64 图片
* @param base64
* @returns
*/
export const loadImage = (base64: string) => {return new Promise((resolve) => {const img = new Image();
img.src = base64;
img.onload = () => resolve(img);
img.onerror = () => resolve(null);
});
};
// 十进制转化为 16 进制
export function hex(n: number){return `0${n.toString(16)}`.slice(-2);
}
/**
* rbga 对象转化为 16 进制颜色字符串
* @param rgba
* @returns
*/
export const rbgaObjToHex = (rgba: IRgba) => {let { r, g, b} = rgba;
const {a} = rgba;
r = Math.floor(r * a);
g = Math.floor(g * a);
b = Math.floor(b * a);
return `#${hex(r)}${hex(g)}${hex(b)}`;
};
/**
* rbga 对象转化为 rgba css 颜色字符串
* @param rgba
* @returns
*/
export const rbgaObjToRgba = (rgba: IRgba) => {const { r, g, b, a} = rgba;
return `rgba(${r},${g},${b},${a})`;
};
/**
* 显示颜色信息,包括放大镜和颜色值
* @param params
* @returns
*/
export const renderColorInfo = (params: any) => {const { containerDom, color, colors, point} = params;
let container = containerDom;
const pos = point;
const n = 7;
const count = colors[0].length;
const size = count * (n + 0) + 2;
if (!container) {const magnifier: any = document.createElement('div');
container = magnifier;
}
if (pos.x + size + 25> window.innerWidth) {pos.x -= size + 25;}
if (pos.y + size + 40> window.innerHeight) {pos.y -= size + 40;}
container.style = `
position: fixed;
left: ${pos.x + 5}px;
top: ${pos.y}px;
z-index: 10001;
pointer-events: none;
`;
container.innerHTML = '';
const pipette = drawPipette(colors, n);
const colorBlock = drawColorBlock(color);
const padding: any = document.createElement('div');
padding.style = 'height: 3px;';
container.appendChild(pipette);
container.appendChild(padding);
container.appendChild(colorBlock);
return container;
}
/**
* 绘制放大镜
* @param colors 颜色二位数组
* @param size 单个像素点显示大小
* @returns
*/
export function drawPipette(colors: IColors, size = 8) {
const scale = 2;
const canvasContainer: any = document.createElement('div');
const canvasContent: any = document.createElement('div');
const pipetteCanvas: any = drawPipetteCanvas(colors, size);
canvasContainer.style = `position: relative;`;
canvasContent.style = `
position: absolute;
top: 0;
left: 0;
width: ${pipetteCanvas.width / scale}px;
height: ${pipetteCanvas.height / scale}px;
border-radius: 50%;
box-shadow: 0 0 10px 10px rgba(150,150,150,0.2) inset;
`;
canvasContainer.appendChild(pipetteCanvas);
canvasContainer.appendChild(canvasContent);
return canvasContainer;
}
/**
* 颜色方块和颜色值显示
* @param color
* @returns
*/
export function drawColorBlock(color: string) {const colorBlock: any = document.createElement('div');
colorBlock.style = `
display: flex;
align-items: center;
background-color: rgba(0,0,0,0.4);
padding: 2px 4px;
border-radius: 3px;
`;
colorBlock.innerHTML = `
${color}
`;
return colorBlock;
}
/**
* 显示提示
* @param content
* @param tooltipVisible
* @returns
*/
export function drawTooltip(content: string, tooltipVisible = true) {const tooltip: any = document.createElement('div');
tooltip.id = 'color-pipette-tooltip-container';
tooltip.innerHTML = content;
tooltip.style = `
position: fixed;
left: 50%;
top: 30%;
z-index: 10002;
display: ${tooltipVisible ? 'flex' : 'none'};
align-items: center;
background-color: rgba(0,0,0,0.4);
padding: 4px 10px;
border-radius: 3px;
color: #fff;
font-size: 20px;
pointer-events: none;
`;
return tooltip;
}
/**
* 绘制放大镜 canvas
* @param colors
* @param size
* @returns
*/
export function drawPipetteCanvas(colors: IColors, size: number) {
const count = colors.length;
const diameter = size * count;
const radius = diameter / 2;
const {canvas, ctx} = getCanvas({
width: diameter,
height: diameter,
scale: 2,
attrs: {style: `border-radius: 50%;`,},
});
if (!ctx) {return;}
// 画像素点
colors.forEach((row, i) => row.forEach((color, j) => {
ctx.fillStyle = color;
ctx.fillRect(j * size, i * size, size, size);
}));
// 画水平线
for (let i = 0; i}) {const canvas: any = document.createElement('canvas');
Object.keys(attrs).forEach((key) => {const value = attrs[key];
canvas.setAttribute(key, value);
});
canvas.setAttribute('width', `${width * scale}`);
canvas.setAttribute('height', `${height * scale}`);
canvas.style = `${attrs.style || ''};width: ${width}px;height: ${height}px;`;
const ctx = canvas.getContext('2d');
ctx?.scale(scale, scale);
return {canvas, ctx};
}
/**
* 获取将 canvas 输出的数据转化为二位数组
* @param data
* @param rect
* @param scale
* @returns
*/
const getImageColor = (data: any[], rect: IRect, scale: number = 1) => {const colors: any[][] = [];
const {width, height} = rect;
for (let row = 0; row {const { x, y, width, height} = rect;
// console.log(x, y, width, height);
const image = ctx.getImageData(x * scale, y * scale, width * scale, height * scale);
const {data} = image;
const colors = getImageColor(data, rect, scale);
return colors;
}
export interface Point {
x: number;
y: number;
}
export interface IRect {
x: number;
y: number;
width: number;
height: number;
}
export type IColors = string[][];
export interface IRgba {
r: number;
g: number;
b: number;
a: number;
}
export interface IProps {
container: any;
listener?: Record void>;
scale?: number;
useMagnifier?: boolean;
}
实验下了,这种方案不是太好,效果不理想。建议采用原生方案来实现!
正文完