Skip to content

Instantly share code, notes, and snippets.

@dcollien
Last active February 27, 2020 05:22
Show Gist options
  • Save dcollien/ea03932cc7e9310d0eb122b2bc6d6c27 to your computer and use it in GitHub Desktop.
Save dcollien/ea03932cc7e9310d0eb122b2bc6d6c27 to your computer and use it in GitHub Desktop.
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