Skip to content

Instantly share code, notes, and snippets.

@hyiromori

hyiromori/README.md

Last active Dec 11, 2020
Embed
What would you like to do?
背景色からコントラスト比を考慮して文字色を判定してみる

背景色からコントラスト比を考慮して文字色を判定してみる

zenn.dev でコメントいただいたので検証してみました。

使い方

テストしてアスペクト比4.5未満の色がないかをチェックします。 (ただし約1677万色をチェックするので少し時間がかかります)

$ node test.js

参考にしたもの

思ったこと

npm でこういったパッケージは沢山あるようなので、こういうのを使うのもありかな。 ただ自作しても大した手間ではないし、カスタマイズもできるから自作でも良いかも。

<!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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment