web浏览器颜色吸管工具实现方案

22,627次阅读
没有评论

共计 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; }

实验下了,这种方案不是太好,效果不理想。建议采用原生方案来实现!

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计8829字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)