前言

最近在逛论坛的时候发现了一个新API:EyeDropper,仅需创立一个实例,而后调用open办法,就能够取到你屏幕内所有能够取到的色彩,惋惜兼容性不太行,只有Chrome,Edge,Opera反对,MDN文档
晓得了这个API后我也有了一个想实现取色器的想法,工作摸鱼期间折腾了几天搞了进去,实现步骤大抵以下几步

  1. 所需页面创立实例,初始化所需属性
  2. 需开启时调用open办法开启取色器,网页截屏生成canvas,初始化监听事件和浮动元素(放大镜)
  3. 鼠标挪动时依据坐标获取色彩数据批改放大镜色彩
  4. 鼠标点击或者按Esc键后销毁

预览

预览地址:https://songlh.top/page-color-picker/
github:https://github.com/LHRUN/page-color-picker

初始化办法

初始化办法没什么说的,就是把须要的属性和办法赋值一遍初始值,而后接管一个鼠标点击时的回调

export class ColorPicker {  canvasContainer: HTMLDivElement | null = null // canvas容器元素  canvas: HTMLCanvasElement | null = null // 截屏canvas  context: CanvasRenderingContext2D | null = null // 截屏canvas[context]  floatContainer: HTMLDivElement | null = null // 鼠标挪动时的浮动容器元素  onChange?: (color: string) => void // 点击鼠标后的回调  color = '' // 色彩值  elementId = '' // 元素惟一id  colorArr: {    el: HTMLDivElement    row: number    col: number  }[] = [] // 放大镜色彩数组  constructor(    onChange?: (color: string) => void // 点击后回调  ) {    this.onChange = onChange  }  // ...}

开启取色器

开启取色器分为4步

  1. 初始化canvas容器
  2. 生成canvas,我应用的是html2canvas
  3. 初始化监听事件
  4. 创立浮动元素
/*** 开启取色器*/open() {  // 获取随机id  this.elementId = getId()  // 初始化canvas容器  this.initContainer()  html2canvas(document.body).then((canvas) => {    if (canvas && this.canvasContainer) {      // 初始化事件      this.initEvent(canvas)      this.canvasContainer.style.display = 'block'      this.canvasContainer.appendChild(canvas)      this.canvas = canvas      this.context = canvas.getContext('2d')      // 创立浮动元素      this.initFloatContainer()    }  })}
  • 初始化canvas容器

    initContainer() {  // 创立元素我封装了一个办法  const canvasContainer = createDocument(    'div',    styleObj.canvasContainer,    document.body  )  this.canvasContainer = canvasContainer  return canvasContainer}/** * 创立元素 * @param elType 元素类型 * @param styleObj 款式对象 * @param parent 父级元素 * @returns element */export const createDocument = <T extends keyof HTMLElementTagNameMap>(  elType: T,  styleObj: Record<string, string | number>,  parent: HTMLElement | DocumentFragment): HTMLElementTagNameMap[T] => {  const el = document.createElement(elType)  Object.keys(styleObj).forEach((key) => {    if (isValidKey(key, styleObj)) {      Reflect.set(el.style, key, styleObj[key])    }  })  parent.appendChild(el)  return el}
  • 初始化事件

    /** * 初始化事件 * @param canvas */initEvent(canvas: HTMLCanvasElement) {  canvas.addEventListener('mousemove', this.canvasMouseMove)  canvas.addEventListener('mousedown', this.canvasMouseDown)  window.addEventListener('keydown', this.onKeyDown)}
  • 创立浮动元素容器

    initFloatCOntainer() {  if (this.canvasContainer) {    // 创立浮动元素容器    const floatContainer = createDocument(      'div',      styleObj.floatContainer,      this.canvasContainer    )    // 创立放大镜的小色彩块    const fragment = document.createDocumentFragment()    for (let i = 1; i <= COLOR_ITEM_SIZE * COLOR_ITEM_SIZE; i++) {      const row = Math.ceil(i / COLOR_ITEM_SIZE)      const col = i - (row - 1) * COLOR_ITEM_SIZE      const style: Record<string, string | number> = {        ...styleObj.colorItem      }      if (row === 6 && col === 6) {        style.borderColor = '#000000'      }      const itemEl = createDocument('div', style, fragment)      itemEl.setAttribute('id', `${this.elementId}${i}`)      this.colorArr.push({        el: itemEl,        row,        col      })    }    floatContainer.appendChild(fragment)    const textEl = createDocument('div', styleObj.text, floatContainer)    textEl.setAttribute('id', `${this.elementId}text`)    this.floatContainer = floatContainer  }}

鼠标挪动

  • 依据鼠标挪动时的坐标,计算须要解决的色彩区域,而后调用CanvasRenderingContext2D.getImageData()办法,这个办法会返回一个ImageData对象,这个对象里就蕴含RGBA数据,而后把这些数据展现到放大镜元素上,就有了放大的成果

    canvasMouseMove = (e: MouseEvent) => {  if (this.context) {    const x = e.pageX * window.devicePixelRatio    const y = e.pageY * window.devicePixelRatio    // 获取放大镜所需区域色彩    const colors = this.getColors(x, y)    if (this.floatContainer && colors) {      // 依据坐标扭转放大镜地位      this.floatContainer.style.transform = `translate(${e.pageX - 82.5}px, ${        e.pageY - 82.5      }px )`      if (this.floatContainer.style.visibility === 'hidden') {        this.floatContainer.style.visibility = 'visible'      }      const textEl = document.getElementById(`${this.elementId}text`)      // 遍历每个色彩块,批改色彩      for (        let i = 0;        i < COLOR_ITEM_SIZE * COLOR_ITEM_SIZE;        i++      ) {        const { el, row, col } = this.colorArr[i]        const [r, g, b, a] = colors[i]        // toHexString rgba转16进制        const hexStr = toHexString({ r, g, b, a: a / 255 })        //  最两头的色彩保存起来        if (row === 6 && col === 6 && textEl) {          textEl.textContent = hexStr          textEl.style.color = hexStr          this.color = hexStr        }        el.style.backgroundColor = hexStr      }    }  }}/** * 获取放大镜所需区域色彩 * @param x * @param y * @returns */getColors(x: number, y: number) {  if (this.context) {    const { data } = this.context.getImageData(      x - 5,      y - 5,      COLOR_ITEM_SIZE,      COLOR_ITEM_SIZE    )    const colors = []    for (let i = 0; i < data.length; i += 4) {      colors.push([data[i], data[i + 1], data[i + 2], data[i + 3]])    }    return colors  }}

鼠标点击

  • 鼠标点击触发回调,销毁元素

    canvasMouseDown = () => {  this?.onChange?.(this.color)  this.destroy()}destroy() {  if (this.canvas) {    this.canvas.removeEventListener('mousemove', this.canvasMouseMove)    this.canvas.removeEventListener('mousedown', this.canvasMouseDown)  }  if (this.canvasContainer) {    document.body.removeChild(this.canvasContainer)  }  window.removeEventListener('keydown', this.onKeyDown)}

总结

有问题欢送探讨