跳转到内容

如何根据背景图片来“动态”改变字体颜色?

摘要

当背景颜色较“深”的时候,我们通常会使用白色字体;背景颜色较“浅”的时候,使用黑色字体。这是为了在背景不确定的情况下,获得更好的阅读体验。

正文

要实现这种需求,就必须知道背景的“明暗”程度,然后给定一个阈值来判断应该使用“白色”还是“黑色”字体。

这要分两种情况:

  • 背景是“颜色”时
  • 背景是“图片”时

背景是“颜色”

这种情况相对就比较简单,我们只需要知道当前背景的颜色值,然后计算出颜色的明暗度,就可以了。

假定有一个颜色为#4DB6AC的背景,我们来看具体代码实现:

ts
// #4DB6AC
// 1. 需要先将色值转成 rgb 格式
const rgb = getComputedStyle(bgEl).backgroundColor; // 'rgb(77, 182, 172)'
const [r, g, b] = rgb.match(/\d+(\.\d+)?/g).map(Number);

// 2. 计算背景亮度
const bgL = 0.299 * r + 0.587 * g + 0.114 * b;

// 3. 根据亮度值确定颜色
const textColor = bgL > 128 ? '#000' : '#fff';

现在我们逐步进行分析:

1. 转换成 RGB 格式

getComputedStyle是浏览器的API,从DOM上获取到的颜色会自动转成 RGB 格式,然后通过正则表达式分别提取rgb三原色的数值。

2. 计算背景亮度

我们需要用到一个公式:

$$Y=0.299R+0.587G+0.114B$$

前面的系数表示每个颜色通道的权重,可以发现它们之和就是1。这些数值是根据ITU-R BT.601-7 标准文档中来的,人眼对三原色的亮度感知大约是:

  • 红色:30%左右
  • 绿色:59%左右
  • 蓝色:11%左右

最后就会得到一个 0~255 之间的数值,值越大表示越亮。

3. 计算对比度确定颜色

然后再根据实际的视觉效果定义一个阈值,比如:128

当背景亮度大于128时,说明比较亮,我们就使用“黑色”字体,否则就使用“白色”字体。

这里的“黑色”和“白色”表示较暗和较亮的颜色,具体色值根据实际情况而定,阈值128也是一样。

你也可以根据哪个颜色的亮度与背景颜色亮度差异更大就用哪个

代码中我们计算出的背景亮度为149.465,大于 128,应该使用黑色字体,效果如下:

从感官上来说,黑色在辨识度上确实要大一些,但我个人其实更偏向于白色字体。所以我们就可以根据实际情况来调整这个阈值,比如设为 150

背景是“图片”

当背景是图片时,问题就会变得更复杂一些。

我们需要先获取图片的像素点,然后根据像素点的色值来计算图片的平均亮度

假如有这样一张背景图片:

从人的感官上来说应该使用白色字体会更好一点,我们用代码来计算一下:

ts
// 图片地址 https://cdn.jsdelivr.net/gh/moohng/bucket@oss/images/articles/1757749025610-baby-8797092_640.png

function loadImage() {
  const img = new Image();
  img.crossOrigin = 'Anonymous';
  img.src = 'https://cdn.jsdelivr.net/gh/moohng/bucket@oss/images/articles/1757749025610-baby-8797092_640.png';
  img.onload = () => {
    // 1. 使用 canvas 绘制图片
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = img.width;
    canvas.height = img.height;
    ctx.drawImage(img, 0, 0);
    
    // 2. 获取图片的像素点
    const data = ctx.getImageData(0, 0, img.width, img.height).data;
    let r = 0, g = 0, b = 0;
    let count = 0;
    
    for (let i = 0, len = data.length; i < len; i += 4 * 100) { // 控制采样频率
      r += data[i];
      g += data[i + 1];
      b += data[i + 2];
      count++;
    }
    
    // 3. 计算平均亮度
    r = r / count;
    g = g / count;
    b = b / count;
    const bgL = r * 0.299 + g * 0.587 + b * 0.114;
    
    // 4. 确定颜色
    const textColor = bgL > 128 ? '#000' : '#fff';
  };
}

1. 加载并绘制图片

首先使用ImageAPI来加载图片,然后将图片绘制到Canvas中。

2. 获取像素点

使用getImageDataAPI可以获取到图片的所有像素点的颜色值,并且每4个一组表示一个像素点,如下所示:

然后通过遍历数组分别对rgb三个色值进行累加,并记录累加次数count,用于计算平均值。

3. 计算亮度

这里跟之前一样,先对三原色进行权重计算,从而确定亮度,然后就能够确定字体颜色了。

根据代码计算,最后结果为“白色字体”,这是颜色对比图:

很明显白色字体更符合视觉辨识度。

感谢阅读