Last active
February 27, 2020 05:22
-
-
Save dcollien/ea03932cc7e9310d0eb122b2bc6d6c27 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const ContrastTools = { | |
relLuminance(rgba) { | |
// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef | |
var rgb = rgba.slice(); | |
for (var i = 0; i < 3; i++) { | |
var channel = rgb[i] / 255; | |
rgb[i] = channel < .03928 ? channel / 12.92 : Math.pow((channel + .055) / 1.055, 2.4); | |
} | |
return .2126 * rgb[0] + .7152 * rgb[1] + 0.0722 * rgb[2]; | |
}, | |
overlay(background, foreground) { | |
// overlay alpha channels | |
const overlaid = foreground.slice(); | |
const alpha = foreground[3]; | |
if (alpha >= 1) { | |
return overlaid; | |
} | |
for (var i = 0; i < 3; i++) { | |
overlaid[i] = overlaid[i] * alpha + background[i] * background[3] * (1 - alpha); | |
} | |
overlaid[3] = alpha + background[3] * (1 - alpha); | |
return overlaid; | |
}, | |
contrastRatio(background, foreground) { | |
// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef | |
const backAlpha = background[3]; | |
const foreAlpha = foreground[3]; | |
let ratio, ratioMin, ratioMax; | |
if (backAlpha < 1) { | |
// background is semi-transparent | |
// overlay onto black and white to get the min/max contrast ratios | |
const onBlack = ContrastTools.overlay([0, 0, 0, 1], background); | |
const onWhite = ContrastTools.overlay([255, 255, 255, 1], background); | |
const contrastBlack = ContrastTools.contrastRatio(onBlack, foreground); | |
const contrastWhite = ContrastTools.contrastRatio(onWhite, foreground); | |
const fgL = ContrastTools.relLuminance(foreground); | |
ratioMax = Math.max(contrastBlack.ratio, contrastWhite.ratio); | |
ratioMin = 1; | |
if (ContrastTools.relLuminance(onBlack) > fgL) { | |
ratioMin = contrastBlack.ratio; | |
} else if (ContrastTools.relLuminance(onWhite) < fgL) { | |
ratioMin = contrastWhite.ratio; | |
} | |
ratio = Math.round((contrastBlack.min + contrastBlack.max) / 2, 2); | |
} else { | |
// background is solid | |
if (foreAlpha < 1) { | |
// foreground is semi-transparent | |
foreground = ContrastTools.overlay(background, foreground); | |
} | |
const fgL = ContrastTools.relLuminance(foreground); | |
const bgL = ContrastTools.relLuminance(background); | |
ratio = Math.round((fgL > bgL) ? (fgL + 0.05) / bgL : (bgL + 0.05) / fgL, 1); | |
ratioMin = ratio; | |
ratioMax = ratio; | |
} | |
return { | |
ratio: ratio, | |
error: Math.round((ratioMax - ratioMin) / 2, 2), | |
min: ratioMin, | |
max: ratioMax, | |
isAA: (fontPt, isBold) => ContrastTools.testAA(ratio, fontPt, isBold), | |
isAAA: (fontPt, isBold) => ContrastTools.testAAA(ratio, fontPt, isBold) | |
}; | |
}, | |
testAA(ratio, fontPt, isBold) { | |
const isLargeScale = (fontPt >= 18) || (isBold && fontPt >= 14); | |
return isLargeScale ? (ratio >= 3) : (ratio >= 4.5); | |
}, | |
testAAA(ratio, fontPt, isBold) { | |
const isLargeScale = (fontPt >= 18) || (isBold && fontPt >= 14); | |
return isLargeScale ? (ratio >= 4.5) : (ratio >= 7); | |
} | |
}; | |
export default ContrastTools; | |
/* | |
e.g. | |
const foreground = [255, 255, 255, 1]; | |
const background = [255, 0, 0, 0.8]; | |
const ratio = ContrastTools.contrastRatio(foreground, background); | |
ratio.isAA(14, true); // is this contrast ratio WGAC2.0 AA compliant for 14pt bold text? | |
ratio.isAAA(12, false); // is this contrast ratio WGAC2.0 AAA compliant for 12pt text? | |
// for alpha blending (if alpha channel is semi-transparent) | |
// +/- this amount depending on what this background sits on top of | |
console.log(ratio.error); | |
// test if the worst case is AA compliant for 14pt font | |
console.log(ContrastTools.testAA(ratio.min, 14, true)); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment