目录
计划
步骤1:从画布读取图像颜色
步骤2:找到对比度最小的像素
步骤3:准备颜色混合公式来测试叠加不透明度级别
步骤4:找到达到对比度目标的叠加不透明度
改进和局限性
我在此过程中学习到了一些东西
首页 web前端 css教程 钉在光文和背景图像之间的完美对比度

钉在光文和背景图像之间的完美对比度

Apr 03, 2025 am 09:44 AM

Nailing the Perfect Contrast Between Light Text and a Background Image

您是否遇到过网站上浅色文本叠加在浅色背景图片上的情况?如果遇到过,您就会知道这有多难阅读。避免这种情况的一种常用方法是使用透明叠加层。但这引出一个重要问题:叠加层的透明度究竟应该有多高?我们并非总是处理相同的字体大小、粗细和颜色,当然,不同的图片也会产生不同的对比度。

尝试消除背景图片上文本对比度差的问题,就像玩打地鼠游戏一样。与其猜测,不如使用HTML <canvas></canvas>和一些数学方法来解决这个问题。

就像这样:

我们可以说“问题解决了!”,然后就此结束这篇文章。但这有什么乐趣呢?我想向您展示的是这个工具的工作原理,以便您掌握一种处理这种常见问题的新方法。

计划

首先,让我们明确我们的目标。我们说过,我们想要在背景图片上显示可读的文本,但“可读”究竟意味着什么?就我们的目的而言,我们将使用WCAG对AA级可读性的定义,该定义指出文本和背景颜色之间需要足够的对比度,以便一种颜色比另一种颜色亮4.5倍。

让我们选择一种文本颜色、一张背景图片和一种叠加颜色作为起点。给定这些输入,我们想要找到使文本可读的叠加不透明度级别,而不会使图片隐藏太多,以至于图片也难以看到。为了使事情复杂化一点,我们将使用一张既有深色空间又有浅色空间的图片,并确保叠加层考虑到了这一点。

我们的最终结果将是一个值,我们可以将其应用于叠加层的CSS不透明度属性,该属性使文本比背景亮4.5倍。

为了找到最佳叠加不透明度,我们将执行四个步骤:

  1. 我们将图片放入HTML <canvas></canvas>中,这将使我们能够读取图片中每个像素的颜色。
  2. 我们将找到图片中与文本对比度最小的像素。
  3. 接下来,我们将准备一个颜色混合公式,我们可以使用它来测试不同不透明度级别对该像素颜色的影响。
  4. 最后,我们将调整叠加层的不透明度,直到文本对比度达到可读性目标。这不会是随机猜测——我们将使用二分查找技术来加快此过程。

让我们开始吧!

步骤1:从画布读取图像颜色

Canvas允许我们“读取”图像中包含的颜色。为此,我们需要将图像“绘制”到<canvas></canvas>元素上,然后使用canvas上下文(ctx)的getImageData()方法生成图像颜色的列表。

function getImagePixelColorsUsingCanvas(image, canvas) {
  // canvas的上下文(通常缩写为ctx)是一个包含许多函数的对象,用于控制你的canvas
  const ctx = canvas.getContext('2d');

  // 宽度可以是任何值,所以我选择500,因为它足够大,可以捕捉细节,但又足够小,可以使计算速度加快。
  canvas.width = 500;

  // 确保canvas与我们图像的比例匹配
  canvas.height = (image.height / image.width) * canvas.width;

  // 获取图像和canvas的测量值,以便我们可以在下一步中使用它们
  const sourceImageCoordinates = [0, 0, image.width, image.height];
  const destinationCanvasCoordinates = [0, 0, canvas.width, canvas.height];

  // Canvas的drawImage()的工作原理是将我们图像的测量值映射到我们想要绘制它的canvas上
  ctx.drawImage(
    image,
    ...sourceImageCoordinates,
    ...destinationCanvasCoordinates
  );

  // 请记住,getImageData仅适用于相同来源或启用跨源的图像。
  // https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
  const imagePixelColors = ctx.getImageData(...destinationCanvasCoordinates);
  return imagePixelColors;
}
登录后复制

getImageData()方法为我们提供了一个表示每个像素颜色的数字列表。每个像素由四个数字表示:红色、绿色、蓝色和不透明度(也称为“alpha”)。了解这一点,我们可以遍历像素列表并找到我们需要的任何信息。这在下一步中将非常有用。

步骤2:找到对比度最小的像素

在此之前,我们需要知道如何计算对比度。我们将编写一个名为getContrast()的函数,该函数接收两种颜色并输出一个数字,表示这两种颜色之间的对比度级别。数字越高,对比度越好,可读性也越好。

当我开始研究这个项目的颜色时,我期望找到一个简单的公式。结果发现有多个步骤。

要计算两种颜色之间的对比度,我们需要知道它们的亮度级别,这本质上就是亮度(Stacie Arellano对亮度进行了深入探讨,值得一看)。

感谢W3C,我们知道使用亮度计算对比度的公式:

const contrast = (lighterColorLuminance   0.05) / (darkerColorLuminance   0.05);
登录后复制

获取颜色的亮度意味着我们必须将颜色从网络上使用的常规8位RGB值(其中每种颜色为0-255)转换为所谓的线性RGB。我们需要这样做是因为亮度不会随着颜色的变化而均匀增加。我们需要将颜色转换为亮度随颜色变化均匀变化的格式。这使我们能够正确计算亮度。同样,W3C在这里也提供了帮助:

const luminance = (0.2126 * getLinearRGB(r)   0.7152 * getLinearRGB(g)   0.0722 * getLinearRGB(b));
登录后复制

但是,等等,还有更多!为了将8位RGB(0到255)转换为线性RGB,我们需要经历所谓的标准RGB(也称为sRGB),其比例为0到1。

因此,过程如下:

<code>8位RGB → 标准RGB → 线性RGB → 亮度</code>
登录后复制

一旦我们有了想要比较的两种颜色的亮度,我们就可以将亮度值代入公式中,得到它们各自颜色之间的对比度。

// getContrast是我们唯一需要直接交互的函数。
// 其余函数是中间辅助步骤。
function getContrast(color1, color2) {
  const color1_luminance = getLuminance(color1);
  const color2_luminance = getLuminance(color2);
  const lighterColorLuminance = Math.max(color1_luminance, color2_luminance);
  const darkerColorLuminance = Math.min(color1_luminance, color2_luminance);
  const contrast = (lighterColorLuminance   0.05) / (darkerColorLuminance   0.05);
  return contrast;
}

function getLuminance({r,g,b}) {
  return (0.2126 * getLinearRGB(r)   0.7152 * getLinearRGB(g)   0.0722 * getLinearRGB(b));
}
function getLinearRGB(primaryColor_8bit) {
  // 首先从8位rgb(0-255)转换为标准RGB(0-1)
  const primaryColor_sRGB = convert_8bit_RGB_to_standard_RGB(primaryColor_8bit);

  // 然后从sRGB转换为线性RGB,以便我们可以使用它来计算亮度
  const primaryColor_RGB_linear = convert_standard_RGB_to_linear_RGB(primaryColor_sRGB);
  return primaryColor_RGB_linear;
}
function convert_8bit_RGB_to_standard_RGB(primaryColor_8bit) {
  return primaryColor_8bit / 255;
}
function convert_standard_RGB_to_linear_RGB(primaryColor_sRGB) {
  const primaryColor_linear = primaryColor_sRGB 
<p>现在我们可以计算对比度了,我们需要查看上一步中的图像,并遍历每个像素,比较该像素的颜色与前景文本颜色之间的对比度。当我们遍历图像的像素时,我们将跟踪到目前为止最差(最低)的对比度,当我们到达循环的末尾时,我们将知道图像中对比度最差的颜色。</p>
<pre class="brush:php;toolbar:false">function getWorstContrastColorInImage(textColor, imagePixelColors) {
  let worstContrastColorInImage;
  let worstContrast = Infinity; // 这保证我们不会从太低的值开始
  for (let i = 0; i 
<p></p><h3 id="步骤-准备颜色混合公式来测试叠加不透明度级别">步骤3:准备颜色混合公式来测试叠加不透明度级别</h3>
<p></p><p>现在我们知道了图像中对比度最差的颜色,下一步是确定叠加层的透明度应该有多高,并查看这将如何改变与文本的对比度。</p>
<p></p><p>当我第一次实现这一点时,我使用了单独的画布来混合颜色并读取结果。但是,感谢Ana Tudor关于透明度的文章,我现在知道有一个方便的公式可以计算将基本颜色与透明叠加层混合后的结果颜色。</p>
<p></p><p>对于每个颜色通道(红色、绿色和蓝色),我们将应用此公式来获取混合颜色:</p>
<p>混合颜色 = 基本颜色   (叠加颜色 - 基本颜色) * 叠加不透明度</p>
<p></p><p>因此,在代码中,这将如下所示:</p>
<pre class="brush:php;toolbar:false">function mixColors(baseColor, overlayColor, overlayOpacity) {
  const mixedColor = {
    r: baseColor.r   (overlayColor.r - baseColor.r) * overlayOpacity,
    g: baseColor.g   (overlayColor.g - baseColor.g) * overlayOpacity,
    b: baseColor.b   (overlayColor.b - baseColor.b) * overlayOpacity,
  }
  return mixedColor;
}
登录后复制

现在我们可以混合颜色了,我们可以测试应用叠加不透明度值时的对比度。

function getTextContrastWithImagePlusOverlay({textColor, overlayColor, imagePixelColor, overlayOpacity}) {
  const colorOfImagePixelPlusOverlay = mixColors(imagePixelColor, overlayColor, overlayOpacity);
  const contrast = getContrast(textColor, colorOfImagePixelPlusOverlay);
  return contrast;
}
登录后复制

有了这个,我们就拥有了找到最佳叠加不透明度所需的所有工具!

步骤4:找到达到对比度目标的叠加不透明度

我们可以测试叠加层的不透明度,并查看这将如何影响文本和图像之间的对比度。我们将尝试许多不同的不透明度级别,直到我们找到达到目标对比度的值,其中文本比背景亮4.5倍。这听起来可能很疯狂,但别担心;我们不会随机猜测。我们将使用二分查找,这是一个让我们能够快速缩小可能的答案集直到得到精确结果的过程。

以下是二分查找的工作原理:

<code>- 在中间猜测。
- 如果猜测过高,我们将消除答案的上半部分。太低了吗?我们将改为消除下半部分。
- 在新的范围中间猜测。
- 重复此过程,直到我们得到一个值。

我碰巧有一个工具可以展示它是如何工作的:

在这种情况下,我们试图猜测一个介于0和1之间的不透明度值。因此,我们将从中间猜测,测试结果对比度是太高还是太低,消除一半的选项,然后再次猜测。如果我们将二分查找限制为八次猜测,我们将立即得到一个精确的答案。

在我们开始搜索之前,我们需要一种方法来检查是否根本需要叠加层。我们根本不需要优化我们不需要的叠加层!

```javascript
function isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast) {
  const contrastWithoutOverlay = getContrast(textColor, worstContrastColorInImage);
  return contrastWithoutOverlay </code>
登录后复制

现在我们可以使用二分查找来查找最佳叠加不透明度:

function findOptimalOverlayOpacity(textColor, overlayColor, worstContrastColorInImage, desiredContrast) {
  // 如果对比度已经足够好,我们不需要叠加层,
  // 因此我们可以跳过其余部分。
  const isOverlayNecessary = isOverlayNecessary(textColor, worstContrastColorInImage, desiredContrast);
  if (!isOverlayNecessary) {
    return 0;
  }

  const opacityGuessRange = {
    lowerBound: 0,
    midpoint: 0.5,
    upperBound: 1,
  };
  let numberOfGuesses = 0;
  const maxGuesses = 8;

  // 如果没有解决方案,不透明度猜测将接近1,
  // 因此我们可以将其作为上限来检查无解的情况。
  const opacityLimit = 0.99;

  // 此循环重复缩小我们的猜测,直到我们得到结果
  while (numberOfGuesses  desiredContrast;

    if (isGuessTooLow) {
      opacityGuessRange.lowerBound = currentGuess;
    }
    else if (isGuessTooHigh) {
      opacityGuessRange.upperBound = currentGuess;
    }

    const newMidpoint = ((opacityGuessRange.upperBound - opacityGuessRange.lowerBound) / 2)   opacityGuessRange.lowerBound;
    opacityGuessRange.midpoint = newMidpoint;
  }

  const optimalOpacity = opacityGuessRange.midpoint;
  const hasNoSolution = optimalOpacity > opacityLimit;

  if (hasNoSolution) {
    console.log('No solution'); // 根据需要处理无解的情况
    return opacityLimit;
  }
  return optimalOpacity;
}
登录后复制

实验完成后,我们现在确切地知道叠加层的透明度需要多高才能使文本可读,而不会隐藏过多的背景图像。

我们做到了!

改进和局限性

我们介绍的方法只有在文本颜色和叠加颜色本身具有足够的对比度时才有效。例如,如果您选择与叠加层相同的文本颜色,除非图像根本不需要叠加层,否则将不会有最佳解决方案。

此外,即使对比度在数学上是可以接受的,但这并不总是保证它看起来很棒。对于具有浅色叠加层和繁忙背景图像的深色文本尤其如此。图像的各个部分可能会分散对文本的注意力,即使对比度在数值上很好,也可能难以阅读。这就是为什么流行的建议是在深色背景上使用浅色文本。

我们也没有考虑像素的位置或每种颜色的像素数量。这样做的一个缺点是,角落中的像素可能会对结果产生过大的影响。但是,好处是,我们不必担心图像的颜色是如何分布的或文本在哪里,因为只要我们处理了对比度最少的地方,我们就可以在其他任何地方都安全。

我在此过程中学习到了一些东西

在这个实验之后,我有一些收获,我想与你们分享:

<code>- **明确目标非常有帮助!**我们从一个模糊的目标开始,即想要在图像上显示可读的文本,最终得到了一个我们可以努力达到的特定对比度级别。
- **明确术语非常重要。**例如,标准RGB并非我所期望的。我了解到,我认为的“常规”RGB(0到255)正式称为8位RGB。此外,我认为我研究的方程式中的“L”表示“亮度”,但它实际上表示“亮度”,这不能与“光度”混淆。澄清术语有助于我们编写代码以及讨论最终结果。
- **复杂并不意味着无法解决。**听起来很困难的问题可以分解成更小、更容易管理的部分。
- **当你走过这条路时,你会发现捷径。**对于白色文本在黑色透明叠加层上的常见情况,您永远不需要超过0.54的不透明度即可达到WCAG AA级可读性。

### 总结…

您现在有了一种方法可以在背景图像上使文本可读,而不会牺牲过多的图像。如果您已经读到这里,我希望我已经能够让您大致了解其工作原理。

我最初开始这个项目是因为我看到(并制作了)太多网站横幅,其中文本在背景图像上难以阅读,或者背景图像被叠加层过度遮挡。我想做些什么,我想给其他人提供一种同样的方法。我写这篇文章是为了希望你们能够更好地理解网络上的可读性。我希望你们也学习了一些很酷的canvas技巧。

如果您在可读性或canvas方面做了一些有趣的事情,我很乐意在评论中听到您的想法!</code>
登录后复制

以上是钉在光文和背景图像之间的完美对比度的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1248
24
如何使用HTML,CSS和JavaScript创建动画倒计时计时器 如何使用HTML,CSS和JavaScript创建动画倒计时计时器 Apr 11, 2025 am 11:29 AM

您是否曾经在项目上需要一个倒计时计时器?对于这样的东西,可以自然访问插件,但实际上更多

HTML数据属性指南 HTML数据属性指南 Apr 11, 2025 am 11:50 AM

您想了解的有关HTML,CSS和JavaScript中数据属性的所有信息。

使Sass更快的概念证明 使Sass更快的概念证明 Apr 16, 2025 am 10:38 AM

在一个新项目开始时,Sass汇编发生在眼睛的眨眼中。感觉很棒,尤其是当它与browsersync配对时,它重新加载

当您看上去时,CSS梯度变得更好 当您看上去时,CSS梯度变得更好 Apr 11, 2025 am 09:16 AM

我关注的一件事是Lea Verou&#039; s conic-Gradient()Polyfill的功能列表是最后一项:

如何在WordPress主题中构建VUE组件 如何在WordPress主题中构建VUE组件 Apr 11, 2025 am 11:03 AM

内联式模板指令使我们能够将丰富的VUE组件构建为对现有WordPress标记的逐步增强。

静态表单提供商的比较 静态表单提供商的比较 Apr 16, 2025 am 11:20 AM

让我们尝试在这里造成一个术语:“静态表单提供商”。你带上html

php是A-OK用于模板 php是A-OK用于模板 Apr 11, 2025 am 11:04 AM

PHP模板通常会因促进Subpar代码而变得不良说唱,但这并不是这样的情况。让我们看一下PHP项目如何执行基本的

三种代码 三种代码 Apr 11, 2025 pm 12:02 PM

每次启动一个新项目时,我都会将我正在查看的代码分为三种类型,或者如果您愿意的话。我认为这些类型可以应用于

See all articles