feat: add color contrast calculation utility with WCAG standards

This commit is contained in:
张翔
2026-03-06 20:11:32 +08:00
parent c9715ee7d0
commit 940598c5cc
2 changed files with 82 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
interface ColorRGB {
r: number;
g: number;
b: number;
}
interface ContrastResult {
passes: boolean;
ratio: number;
requiredRatio: number;
}
function hexToRgb(hex: string): ColorRGB {
const cleanHex = hex.replace('#', '');
const r = parseInt(cleanHex.substring(0, 2), 16);
const g = parseInt(cleanHex.substring(2, 4), 16);
const b = parseInt(cleanHex.substring(4, 6), 16);
return { r, g, b };
}
function getLuminance(rgb: ColorRGB): number {
const { r, g, b } = rgb;
const a = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
export function calculateContrastRatio(foreground: string, background: string): number {
const fgRgb = hexToRgb(foreground);
const bgRgb = hexToRgb(background);
const fgLuminance = getLuminance(fgRgb);
const bgLuminance = getLuminance(bgRgb);
const lighter = Math.max(fgLuminance, bgLuminance);
const darker = Math.min(fgLuminance, bgLuminance);
return (lighter + 0.05) / (darker + 0.05);
}
export function meetsWCAGStandard(
foreground: string,
background: string,
level: 'AA' | 'AAA',
textSize: 'normal' | 'large'
): ContrastResult {
const ratio = calculateContrastRatio(foreground, background);
let requiredRatio: number;
if (level === 'AA') {
requiredRatio = textSize === 'normal' ? 4.5 : 3;
} else {
requiredRatio = textSize === 'normal' ? 7 : 4.5;
}
return {
passes: ratio >= requiredRatio,
ratio,
requiredRatio
};
}
+19
View File
@@ -0,0 +1,19 @@
import { test, expect } from '@playwright/test';
import { calculateContrastRatio, meetsWCAGStandard } from '@/lib/color-contrast';
test('should calculate correct contrast ratio for black on white', () => {
const ratio = calculateContrastRatio('#000000', '#FFFFFF');
expect(ratio).toBeCloseTo(21, 1);
});
test('should identify WCAG AA compliance for normal text', () => {
const result = meetsWCAGStandard('#000000', '#FFFFFF', 'AA', 'normal');
expect(result.passes).toBe(true);
expect(result.ratio).toBeGreaterThan(4.5);
});
test('should fail WCAG AA for low contrast colors', () => {
const result = meetsWCAGStandard('#808080', '#FFFFFF', 'AA', 'normal');
expect(result.passes).toBe(false);
expect(result.ratio).toBeLessThan(4.5);
});