zenn.dev でコメントいただいたので検証してみました。
テストしてアスペクト比4.5未満の色がないかをチェックします。 (ただし約1677万色をチェックするので少し時間がかかります)
$ node test.jsnpm でこういったパッケージは沢山あるようなので、こういうのを使うのもありかな。 ただ自作しても大した手間ではないし、カスタマイズもできるから自作でも良いかも。
zenn.dev でコメントいただいたので検証してみました。
テストしてアスペクト比4.5未満の色がないかをチェックします。 (ただし約1677万色をチェックするので少し時間がかかります)
$ node test.jsnpm でこういったパッケージは沢山あるようなので、こういうのを使うのもありかな。 ただ自作しても大した手間ではないし、カスタマイズもできるから自作でも良いかも。
| <!doctype html> | |
| <html lang="ja"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, user-scalable=no"> | |
| <meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
| <title>背景色をベースに文字色を判定してみる</title> | |
| <style type="text/css"> | |
| span { | |
| display: inline-block; | |
| font-family: Menlo, Monaco, Consolas, "Courier New", monospace; | |
| margin: 0.5rem; | |
| padding: 1rem 2rem; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <p>※CR: Contrast Ratio</p> | |
| <div id="colors"></div> | |
| <script type="text/javascript"> | |
| const BLACK = {red: 0, green: 0, blue: 0} | |
| const WHITE = {red: 255, green: 255, blue: 255} | |
| const getRGBForCalculateLuminance = (_color) => { | |
| const color = _color / 255 | |
| if (color <= 0.03928) { | |
| return color / 12.92; | |
| } else { | |
| return Math.pow(((color + 0.055) / 1.055), 2.4); | |
| } | |
| } | |
| const getRelativeLuminance = (color) => { | |
| const {red, green, blue} = color | |
| let R = getRGBForCalculateLuminance(red); | |
| let G = getRGBForCalculateLuminance(green); | |
| let B = getRGBForCalculateLuminance(blue); | |
| return 0.2126 * R + 0.7152 * G + 0.0722 * B; | |
| } | |
| const getContrastRatio = (color1, color2) => { | |
| const luminance1 = getRelativeLuminance(color1); | |
| const luminance2 = getRelativeLuminance(color2); | |
| const bright = Math.max(luminance1, luminance2); | |
| const dark = Math.min(luminance1, luminance2); | |
| return (bright + 0.05) / (dark + 0.05); | |
| } | |
| const getFontColor = (color) => { | |
| // 元の計算方法 | |
| // const brightness = (color.red * 0.299) + (color.green * 0.587) + (color.blue * 0.114) | |
| // return brightness >= 140 ? BLACK : WHITE | |
| const whiteRatio = getContrastRatio(color, WHITE) | |
| const blackRatio = getContrastRatio(color, BLACK) | |
| return whiteRatio > blackRatio ? WHITE : BLACK | |
| } | |
| const toHexColor = ({red, green, blue}) => ([ | |
| `0${Number(red).toString(16)}`.slice(-2), | |
| `0${Number(green).toString(16)}`.slice(-2), | |
| `0${Number(blue).toString(16)}`.slice(-2), | |
| ]).join('') | |
| const bases = ['00', '11', '22', '33', '44', '55', '66', '77', '88', '99', 'AA', 'BB', 'CC', 'DD', 'EE', 'FF']; | |
| const colors = []; | |
| bases.forEach((red) => { | |
| bases.forEach((green) => { | |
| bases.forEach((blue) => { | |
| const backgroundColor = {red: parseInt(red, 16), green: parseInt(green, 16), blue: parseInt(blue, 16)} | |
| const color = getFontColor(backgroundColor) | |
| const contrast = getContrastRatio(backgroundColor, color) | |
| colors.push({background: `${red}${green}${blue}`, color: toHexColor(color), contrast}) | |
| }) | |
| }) | |
| }) | |
| document.getElementById("colors").innerHTML = colors.map(({background, color, contrast}) => { | |
| return `<span style="background-color: #${background}; color: #${color}">#${background} (CR:${Math.round(contrast * 100) / 100})</span>` | |
| }).join('') | |
| </script> | |
| </body> | |
| </html> |
| const BLACK = {red: 0, green: 0, blue: 0} | |
| const WHITE = {red: 255, green: 255, blue: 255} | |
| const getRGBForCalculateLuminance = (_color) => { | |
| const color = _color / 255 | |
| if (color <= 0.03928) { | |
| return color / 12.92; | |
| } else { | |
| return Math.pow(((color + 0.055) / 1.055), 2.4); | |
| } | |
| } | |
| const getRelativeLuminance = (color) => { | |
| const {red, green, blue} = color | |
| let R = getRGBForCalculateLuminance(red); | |
| let G = getRGBForCalculateLuminance(green); | |
| let B = getRGBForCalculateLuminance(blue); | |
| return 0.2126 * R + 0.7152 * G + 0.0722 * B; | |
| } | |
| const getContrastRatio = (color1, color2) => { | |
| const luminance1 = getRelativeLuminance(color1); | |
| const luminance2 = getRelativeLuminance(color2); | |
| const bright = Math.max(luminance1, luminance2); | |
| const dark = Math.min(luminance1, luminance2); | |
| return (bright + 0.05) / (dark + 0.05); | |
| } | |
| const getFontColor = (color) => { | |
| // 元の計算方法 | |
| // const brightness = (color.red * 0.299) + (color.green * 0.587) + (color.blue * 0.114) | |
| // return brightness >= 140 ? BLACK : WHITE | |
| const whiteRatio = getContrastRatio(color, WHITE) | |
| const blackRatio = getContrastRatio(color, BLACK) | |
| return whiteRatio > blackRatio ? WHITE : BLACK | |
| } | |
| const toHexColor = ({red, green, blue}) => ([ | |
| `0${Number(red).toString(16)}`.slice(-2), | |
| `0${Number(green).toString(16)}`.slice(-2), | |
| `0${Number(blue).toString(16)}`.slice(-2), | |
| ]).join('') | |
| module.exports = { | |
| getFontColor, | |
| getContrastRatio, | |
| toHexColor, | |
| } |
| const {getFontColor, getContrastRatio, toHexColor} = require('./index'); | |
| const STEP = 1 | |
| const THRESHOLD = 4.5 | |
| const SHOW_RESULT_DETAILS = false | |
| const test = () => { | |
| const backgroundColors = []; | |
| for (let red = 0; red <= 255; red += STEP) { | |
| for (let green = 0; green <= 255; green += STEP) { | |
| for (let blue = 0; blue <= 255; blue += STEP) { | |
| backgroundColors.push({red, green, blue}) | |
| } | |
| } | |
| } | |
| const invalidColors = []; | |
| let minContrastRatio = Number.MAX_SAFE_INTEGER; | |
| backgroundColors.forEach((bgColor) => { | |
| const fontColor = getFontColor(bgColor) | |
| const ratio = getContrastRatio(bgColor, fontColor) | |
| if (ratio < minContrastRatio) { | |
| minContrastRatio = ratio | |
| } | |
| if (ratio < THRESHOLD) { | |
| invalidColors.push(bgColor) | |
| } | |
| }) | |
| console.log('') | |
| console.log(`\u001b[31m[コントラスト比が${THRESHOLD}未満の色]\u001b[0m`) | |
| console.log(`${invalidColors.length}/${backgroundColors.length}色 (${Math.round((invalidColors.length / backgroundColors.length) * 100)}%)`) | |
| if (SHOW_RESULT_DETAILS) { | |
| invalidColors.forEach((invalidColor) => { | |
| console.log(`* ${toHexColor(invalidColor)} (コントラスト比: ${getContrastRatio(invalidColor, getFontColor(invalidColor))})`) | |
| }) | |
| } | |
| console.log('') | |
| console.log("[最小コントラスト比]") | |
| console.log(minContrastRatio) | |
| console.log('') | |
| } | |
| test() |