最近浏览 product hunt 时, 发现一个有趣的网站 color copy paste: 在首页的图片上, 可以直接点击取色. 这又勾起了我的好奇心: 在网页上如何取到图片像素点的颜色呢?

一番搜索后, 发现了一个神奇的API: Canvas 的 context.getImageData(x, y, width, height) 可用于获取图片的像素信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 假设页面存在ID为 target-image 的图片
const image = document.querySelector('#target-image')
// 使用图片的原始宽高作为画布的宽高
canvas.width = image.naturalWidth
canvas.height = image.naturalHeight
context.drawImage(image, 0, 0)

// 获取图片左上角第一个像素的信息
const imageData = context.getImageData(0, 0, 1, 1)
console.log(imageData)
// 以下为示例输出结果
// {
// data: [255, 0, 0, 255], // 像素信息
// height: 1,
// width: 1
// }

由上述例子可见, 一个像素点由四个数字描述, 分别为 r g b a, a 为 255 则表示完全不透明即 alpha 通道值为 255/255 = 1.

如何计算图片平均颜色

有时候我们需要知道图片的平均颜色, 用于产生对应的占位缩略图, 抑或有其他目的. 计算平均颜色的思路很简单: 分别统计每个像素点的 r g b a 的总值, 再除以像素个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接上边的示例代码
...
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
const imagePixelData = imageData.data

const pixelCount = imagePixelData.length / 4
const averRgba = {r: 0, g: 0, b: 0, a: 0}

for (let i = 0; i < pixelCount; ++i) {
let idx = i * 4
averRgba.r += imagePixelData[idx]
averRgba.g += imagePixelData[idx + 1]
averRgba.b += imagePixelData[idx + 2]
averRgba.a += imagePixelData[idx + 3]
}

averRgba.r = Math.floor(averRgba.r / pixelCount)
averRgba.g = Math.floor(averRgba.g / pixelCount)
averRgba.b = Math.floor(averRgba.b / pixelCount)
averRgba.a = Math.floor(averRgba.a / pixelCount)

// css 颜色值
const cssColor = `rgba(${averRgba.r}, ${averRgba.g}, ${averRgba.b}, ${averRgba.a / 255})`

如何获取跨域图片的颜色信息

由于浏览器的安全策略, 在不经过特殊处理时, canvas 是无法加载跨域图片资源的. 图片资源所在的服务器必须允许跨域请求方可被 canvas 加载

使用前端技术获取网页上任意位置的颜色

我想到两个思路, 但都不完美:

  1. 使用库html2canvas 将网页转化为画布, 再使用上述方法取颜色值. 但这个库对于复杂网页转化有问题, 会出现转换出来的画布样式错误, 视频标签不支持, 图片跨域问题也无法解决
  2. 使用 document.elementFromPoint(x, y) 获取坐标对应的dom元素, 在使用 window.getComputedStyle(element) 获取元素实际渲染的样式, 再取背景色或文字颜色. 这个方法就错更远了: 首先 elementFromPoint 取到的可能是透明蒙层元素; 再次 getComputedStyle 拿到样式后也无法确定应该取文字颜色还是背景色, 遇到颜色渐变的束手无策; 最后 对 图片、视频、画布等复杂标签同样无法处理