关于前端:颜色选择器的纯JS实现

41次阅读

共计 4446 个字符,预计需要花费 12 分钟才能阅读完成。

前言

最近在逛论坛的时候发现了一个新 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)
    }

总结

有问题欢送探讨👻

正文完
 0