@daformat/contrast-color

中文文档
1.0.0发布于 2026/08/18Zero-Clause BSDTypeScriptES Module零依赖近 30 天下载 9853

将一色移动到可读版本本身:同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同 没有依附关系,没有框架.

中文使用文档
文档目录14 节
  1. 安装
  2. 演示
  3. 使用量
  4. 如何运作
  5. API 密码
  6. contrastShift(color, background, options?)
  7. 阿尔法
  8. 其他颜色格式
  9. 黑白相间的墨水
  10. 计量
  11. 颜色转换
  12. 类型
  13. 关于中央支助系统的说明
  14. 许可证

对比颜色

NPM Version gzipped NPM Downloads
Follow daformat on GitHub Follow daformat on X

黑或白的墨水是比较简单的一半 现在CSS自己回答 contrast-color()越难 一半是取出您选择的颜色, 并找到最近的可读版本 这个: 相同色调,相同染色体,只有 轻而易举 这就是它的作用.

零依赖性,无框架,纯函数超过由三个数字组成的数组. 它运行在节点,德诺,邦和 浏览器,它很便宜,可以调用几百次一个帧,而你拖起一个取色器.

安装

npm install @daformat/contrast-color
yarn add @daformat/contrast-color
pnpm add @daformat/contrast-color
bun add @daformat/contrast-color
deno add npm:@daformat/contrast-color

演示

https://hello-mat.com/design-engineering/contrast-colors (中文(简体) )

使用量

import { contrastShift } from "@daformat/contrast-color";

// WCAG 2.1, body text on a blue background
contrastShift("#1e3a8a", "#3b82f6", { target: 4.5 });
// -> { hex: "#041262", score: 4.5, direction: "darker", reached: true, … }

// APCA, same pair, Lc 60
contrastShift("#1e3a8a", "#3b82f6", { target: 60, metric: "apca" });
// -> { hex: "#eaf1ff", score: 60, direction: "lighter", reached: true, … }

// Omit the target and each metric uses its own default: 4.5 and Lc 75
contrastShift("#777777", "#ffffff");
contrastShift("#777777", "#ffffff", { metric: "apca" });

reached: false 意思是这里没有颜色 清除目标,任何方向。 回来的最好 可用而非错误, 请在装运前检查它 :

const result = contrastShift(fg, bg, { target: 75, metric: "apca" });
if (!result.reached) {
  console.warn("unreachable, best is", result.score);
}

颜色随十六进制字符串进入( X)#rgb, (中文(简体) ), #rgba, (中文(简体) ), #rrggbb, (中文(简体) ), #rrggbbaa, 散列可选)或作为频道 0..1,以及 每个结果都背出两个表:

import { contrastShift, rgb } from "@daformat/contrast-color";

contrastShift(rgb("#1e3a8a"), [0.23, 0.51, 0.96], { target: 4.5 });

Hex是唯一的字符串格式. 还有什么 掷出 而不是猜测,看 其他颜色格式 用于两行转换.

如何运作

颜色转换为 OKLCH, 仅限 L 移动。 色调从未被触动过 所以结果还是像颜色 你选的。 Chroma 保存在 SRGB 游戏允许的地方, 并安装在游戏的边界上 .

得分是单调的 L 在背景的两侧,所以每个方向都可以被分化. 这就是整个 算法:没有高压,没有分步大小来调谐. 两个方向都被搜索, 和一个清理酒吧 更小的动作获胜——对你的颜色最小的改变使其可以读取.

API 密码

contrastShift(color, background, options?)

主入门. 回返a ShiftResult。 。 。 .

选项类型默认它会做什么
targetnumber标准 def得分清净. 4.5 为妇女和儿童保护小组, 75 代表非洲生产能力联盟.
metric"wcag" | "apca""wcag"得分是多少.
preserveChromabooleantruefalse 根据光度的去向 将铬切除成一定比例.

preserveChroma: false 在同一分数上给出一个更柔和,更不饱和的结果. 它不会成为无法达到的目标 可达到的——在固定地点没有任何东西——但当目标无法到达时,往往降落在稍好一点的地方 努力起来.

type ShiftResult = {
  /** The shifted colour, as channels in 0..1. Opaque; its alpha is `alpha`. */
  rgb: Rgb;
  /** The same, as `#rrggbb`, or `#rrggbbaa` when `alpha` is below 1. */
  hex: string;
  /** The alpha carried through from the input, untouched. */
  alpha: number;
  /** What you actually see: `rgb` at `alpha` over the background. */
  composite: Rgb;
  compositeHex: string;
  /** Unsigned, in the units of `metric`: a ratio for WCAG, Lc for APCA. */
  score: number;
  /** The OKLCh lightness landed on, and the one started from. */
  L: number;
  L0: number;
  /** The chroma and hue of the input, in OKLCh. Hue is in radians. */
  C0: number;
  H: number;
  direction: "none" | "lighter" | "darker";
  /** false means nothing at this hue clears the target: this is a best effort. */
  reached: boolean;
  metric: MetricId;
};

阿尔法

半透明前景得到支持:α被固定,光度在它下面移动,每个分数都是 测量 复合 ——你实际上可以看到的背景的像素.

contrastShift("#3b82f6cc", "#ffffff", { target: 4.5 });
// -> { hex: "#0654c4cc", composite: […], compositeHex: "#3776d0", score: 4.5, reached: true }

两种限制,两者都是故意的:

背景必须不透明. 找出半透明背景意味着知道背后是什么,这个 图书馆不带背景。 自己先综合一下:

import { composite, contrastShift, rgb, rgba } from "@daformat/contrast-color";

const onPage = composite(rgba("#ffffff80"), rgb("#f5f5f5"));
contrastShift("#3b82f6", onPage, { target: 4.5 });

阿尔法是一个硬天花板,关于颜色可以走多远. 时 alpha: 0.5 合成器只从中间 黑白的背景,所以很多目标 根本无法在任何光线下实现。 过白:

阿尔法最大可能的WCAG比率
1个21:1 (中文(简体) )
0.8 (简体中文)12.63:1 (中文(简体) )
0.6 (中文(简体) )5.74:1 (韩语)
0.5 (韩语)3.98:1 (中文(简体) )
0.3 (韩语)2.11: 1 (韩语)

因此,4.5:1是无法达到的 alpha: 0.55无论你从哪开始. reached: false 与 α,结果是一种最佳的努力,通常已经倒塌为黑色或白色. 请检查.

图书馆里的其他一切.. contrastRatio, (中文(简体) ), apcaLc, (中文(简体) ), readableInk - 带 不透明 只有颜色。 运行 半透明者通过 composite 先说.

其他颜色格式

仅以Hex为目的:一个CSS色彩剖析器比这个库的其他部分更大,没有天然的截取点. 转换到第一个—— Rgb编号 :Rgba 输入路径表示任何解析器直接跳入.

在浏览器中,平台已经有一个,而画像素是将 sRGB 数字带回它的唯一方法 统一. ctx.fillStyle 单靠自己是不够的: oklch() 财务报告和审定财务报表 color-mix() 回到你没有改变,和 当颜色无效时, 静默地保留其上一个值 .

const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const ctx = canvas.getContext("2d", { willReadFrequently: true })!;

/** Any CSS colour the browser understands -> channels in 0..1, or null. */
const cssToRgba = (css: string): Rgba | null => {
  ctx.fillStyle = "#000";
  ctx.fillStyle = css;
  const first = ctx.fillStyle;
  ctx.fillStyle = "#fff";
  ctx.fillStyle = css;
  if (ctx.fillStyle !== first) {
    return null; // the browser ignored the assignment: not a colour
  }
  ctx.clearRect(0, 0, 1, 1);
  ctx.fillRect(0, 0, 1, 1);
  const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
  return [r / 255, g / 255, b / 255, a / 255];
};

contrastShift(cssToRgba("oklch(0.7 0.1 200)")!, "#ffffff");

那把把柄有颜色, hsl(), (中文(简体) ), lab(), (中文(简体) ), oklch(), (中文(简体) ), color-mix() 财务报告和审定财务报表 color(display-p3 …)广相图 颜色在路上进入 SRGB。 阿尔法幸存下来, 给予或采取 1/255 圆通相像素缓冲.

其它任何地方, 请使用彩色库并直接交出频道 :

import { converter } from "culori";

const toRgb = converter("rgb");
const { r, g, b } = toRgb("oklch(0.7 0.1 200)");
contrastShift([r, g, b], "#ffffff");

黑白相间的墨水

import {
  INK_CROSSOVER,
  readableInk,
  rgb,
  yiqInk,
} from "@daformat/contrast-color";

readableInk(rgb("#fde047")); // "#000000"
  • readableInk(color) ——黑色或白色,以相较者为准. 白胜领带. 这就是CSS contrast-color() 返回,以位数表示.
  • INK_CROSSOVER 黑白对比的光亮: sqrt(0.0525) - 0.05,或 0.1791287847…。使用精确值而不是四舍五入 0.179 您可以看到周围的引用:四舍五入的形式是不一致的 对大约0.03%的SRGB颜色有较大的黑白规则.
  • yiqInk(color) 编号 : yiqLuma(color) NTSC的Luma 统治着网络上的每一个“颜色黑吗?”. 3个计数错误, 导出比较: 上面写着白色 #767676在那里,光亮说黑色.

计量

import {
  apcaLc,
  contrastRatio,
  dualScore,
  luminance,
  METRICS,
  polarity,
  rgb,
} from "@daformat/contrast-color";

contrastRatio(rgb("#000"), rgb("#fff")); // 21
apcaLc(rgb("#000"), rgb("#fff")); // 106.04 — text first, background second
polarity(rgb("#000"), rgb("#fff")); // "dark on light"
dualScore(rgb("#000"), rgb("#fff")); // "21.00:1 · Lc 106.0"
  • luminance(color) WCAG 2.1 相对亮度.
  • contrastRatio(a, b) ——WCAG2.1比,1比21. 对称性,因此辩证令无关紧要.
  • apcaLc(text, background) - APCA光度对比(SA98G, apca-w3 .0.1.9常数). 签名: 阳性 浅色背景上的暗文本,阴性为暗色. 争吵命令在这里很重要.
  • apcaY(color) 编号 : APCA_CONSTANTS ——APCA自己的屏幕亮度及其系数. APCA使用平地2.4 负责而不是SRGB的分件传输,所以 apcaY 财务报告和审定财务报表 luminance 不可互换.
  • polarity(fg, bg) —— 说 "dark on light" 或者说 "light on dark",从标志 APCA 已经计算.
  • dualScore(color, bg) ——两个数字在一个字符串中,作为标签.

METRICS 是接口 contrastShift 分数通过,然后导出,这样您就可以驱动一个度量开关:

type Metric = {
  id: MetricId;
  label: string;
  /** Unsigned, so "higher is better" holds for both metrics. */
  score: (fg: Rgb, bg: Rgb) => number;
  fmt: (value: number) => string;
  /** Named thresholds, for the ticks on a slider. */
  targets: [value: number, label: string][];
  /** The target used when the caller does not pick one. */
  def: number;
};

METRICS.wcag.fmt(4.5); // "4.50:1"
METRICS.apca.fmt(75); // "Lc 75.0"
METRICS.apca.targets; // [[45, "Lc 45 large or bold"], [60, …], [75, …], [90, …]]

颜色转换

换到的每分每分 每分每分每分 每分每分每分每分 每分每分每分每分每分每分每分 每分每分每分每分每分每分每分每分每分每分 每分每分每分每分每分每分.

import {
  clamp01,
  fitChroma,
  hexToRgb,
  inGamut,
  linearToSrgb,
  oklabToRgb,
  oklchToRgb,
  rgb,
  rgbToHex,
  rgbToOklab,
  rgbToOklch,
  srgbToLinear,
} from "@daformat/contrast-color";
  • hexToRgba(hex) —— 说 #rgb, (中文(简体) ), #rgba, (中文(简体) ), #rrggbb 或者说 #rrggbbaa,散列可选,进入频道和一个α在 0..1。 。 。 . 缺少α是 1. 回归 null 而不是扔,因为通常的呼叫器是输入字段,用户是 还在打字的中间.
  • hexToRgb(hex) ——同样,放下α:返回类型无处可放.
  • rgba(hex) 编号 : rgb(hex) ——同一对相,为必有相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相相. 丢出一个 TypeError 打开 任何他们无法分析的东西。 静静地回落在这里 变成一个黑色的按钮 没有人会注意到 直到它上船.
  • rgbToHex(color) - 回来 #rrggbb堵住任何漂出范围的东西.
  • rgbaToHex(color) ——改为: #rrggbbaa,或改为 #rrggbb 当颜色完全不透明时.
  • composite(over, under) 在不透明的颜色上铺上半透明颜色,并获得你会看到的东西。 简单的α 在伽玛编码通道上进行堆肥,这是浏览器所画的,而不是线性光线.
  • rgbToOklch(color) 编号 : oklchToRgb(color) —— sRGB呼叫OKLCH并返回. 色调在 弧度从 Math.atan2。 。 。 .
  • rgbToOklab(color) 编号 : oklabToRgb(color) ——长方形,取自比约恩·奥托松. oklabToRgb 未打开 目的: 境外渠道如何 inGamut 完成它的任务.
  • fitChroma(L, C, H) ——这种光度的颜色和染色体最大的颜色 <= C 留在SRGB内.
  • inGamut(color) ——无论每个通道都坐入SRGB内部,给予还是取出浮点尘.
  • srgbToLinear(channel) 编号 : linearToSrgb(channel) —— sRGB 传输功能及其逆向,每道.
  • clamp01(value) - 那个 0..1 夹住其他东西.

类型

import type {
  ColorInput,
  Metric,
  MetricId,
  Oklab,
  Oklch,
  Rgb,
  Rgba,
  ShiftOptions,
  ShiftResult,
} from "@daformat/contrast-color";
类型形状页:1
Rgb[r: number, g: number, b: number]伽玛编码 sRGB 中 0..1, (中文(简体) ), 没有 0..255。 。 。 .
Oklab[L: number, a: number, b: number]光线在 0..1,然后是两个对手轴.
Oklch[L: number, C: number, H: number]花色为弧度.
MetricId"wcag" | "apca"
ColorInputstring | Rgb什麽 contrastShift 接受两种颜色.

关于中央支助系统的说明

对于黑白问题,首先到达平台:

.card {
  background: var(--bg);
  color: contrast-color(var(--bg));
}

它不花任何代价——没有捆绑,没有主线,在作画前在样式引擎中决定——它回报的正是什么 readableInk 返回。 此库用于另一个问题, CSS不回答: 保持颜色和 移动到可读为止.

许可证

零相机 BSD 马修·朱赫特

版本与发布记录

@daformat/contrast-color 共发布 1 个版本,首次发布于 2026/08/18;过去 12 个月发布了 1 个版本。

版本发布日期距今
1.0.0latest2026/08/182 个月前

关于 @daformat/contrast-color 的常见问题

@daformat/contrast-color 是什么?

将一色移动到可读版本本身:同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同色同 没有依附关系,没有框架.

如何安装 @daformat/contrast-color?

在项目目录运行 npm install @daformat/contrast-color,或使用 pnpm add / yarn add / bun add。

@daformat/contrast-color 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@daformat/contrast-color 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@daformat/contrast-color 可以商用吗?

@daformat/contrast-color 使用 Zero-Clause BSD 许可。声明的许可为 Zero-Clause BSD,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@daformat/contrast-color 的下载量有多少?

根据 npm 官方下载统计,@daformat/contrast-color 在 2026/10/11 – 2026/11/09 这 30 天内被下载 9,853 次,日均约 328 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@daformat/contrast-color 还在维护吗?

最新版本 1.0.0 发布于 2026/08/18(2 个月前);至今共发布 1 个版本,其中过去 12 个月发布了 1 个;首次发布于 2026/08/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。