前言
最近在逛论坛的时候发现了一个新API:EyeDropper
,仅需创立一个实例,而后调用open办法,就能够取到你屏幕内所有能够取到的色彩,惋惜兼容性不太行,只有Chrome,Edge,Opera反对,MDN文档
晓得了这个API后我也有了一个想实现取色器的想法,工作摸鱼期间折腾了几天搞了进去,实现步骤大抵以下几步
- 所需页面创立实例,初始化所需属性
- 需开启时调用open办法开启取色器,网页截屏生成canvas,初始化监听事件和浮动元素(放大镜)
- 鼠标挪动时依据坐标获取色彩数据批改放大镜色彩
- 鼠标点击或者按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步
- 初始化canvas容器
- 生成canvas,我应用的是html2canvas
- 初始化监听事件
- 创立浮动元素
/*** 开启取色器*/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)}
总结
有问题欢送探讨