Skip to content

Instantly share code, notes, and snippets.

@mcamiano
Created January 13, 2012 14:14
Show Gist options
  • Save mcamiano/1606408 to your computer and use it in GitHub Desktop.
Save mcamiano/1606408 to your computer and use it in GitHub Desktop.
An example of: jQuery+CDN+plugin+QUnit for the WCAG 2.0 contrast algorithm
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>jQuery Contrast Checking Plugin</title>
<style type="text/css">
article, aside, figure, footer, header, hgroup,
menu, nav, section { display: block; }
</style>
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen" />
</head>
<body style="background-color: #FFEEFF;">
<h1 id="qunit-header">jQuery Contrast Checking Plugin</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</ol>
<div class="albatross">The code inside the header was written so I could check out the contrast algorithm of WCAG 2.0. In general I found it is not a straightforward task to determine the background and foreground colors, considering positioning and transparency. This code guesses that the nearest DOM parent with an explicit background-color property determines the background. I haven't tied it to a GUI event handler yet, but anyway, there has got to be a better strategy than plugging color values into a form field.</div>
<blockquote id="foo" style="background-color: #FFFFFF;">
<p id="hello" style="color: #EEEEEE;">This is poorly contrasting text.</p>
</blockquote>
<div class="albatross" id="blackness" style="color: #444444; background-color: #888888;">View the source to see all the code.</div>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script src="http://code.jquery.com/qunit/git/qunit.js"></script>
<script>
//<![CDATA[
$.computeLuminance = function(rgb) {
var colorRE = /rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/;
// what should be done with rgba(r,g,b,a) ?
var colorsStrArr = rgb.match(colorRE);
var RsRGB, GsRGB, BsRGB, R, G, B;
// The colorStrArr components should be numerals from 0 to 255
RsRGB = parseInt(colorsStrArr[1], 10) / 255;
GsRGB = parseInt(colorsStrArr[2], 10) / 255;
BsRGB = parseInt(colorsStrArr[3], 10) / 255;
if (RsRGB <= 0.03928) {
R = RsRGB / 12.92;
} else {
R = Math.pow((RsRGB + 0.055) / 1.055, 2.4);
}
if (GsRGB <= 0.03928) {
G = GsRGB / 12.92;
} else {
G = Math.pow((GsRGB + 0.055) / 1.055, 2.4);
}
if (BsRGB <= 0.03928) {
B = BsRGB / 12.92;
} else {
B = Math.pow((BsRGB + 0.055) / 1.055, 2.4);
}
var result = (0.2126 * R + 0.7152 * G + 0.0722 * B);
if (result > 1) {
alert('Error: Luminance Greater Than 1. RGB was: ' + rgb + '. None of the following should be greater than 1: ' + R + ' ' + G + ' ' + B);
}
return result;
}
// Calculate the contrast ratio.
// L1 is the relative luminance of the lighter of the foreground or background colors, and
// L2 is the relative luminance of the darker of the foreground or background colors.
$.computeContrast = function(L1, L2) {
return ( Math.max(L1, L2) + 0.05) / ( Math.min(L1, L2) + 0.05);
}
// Check that the contrast ratio is equal to or greater than 5:1
$.contrastAgainst = function(textobject, backgroundobject, modifydom) {
var textobj = $(textobject);
var bgobj = $(backgroundobject ? backgroundobject : textobject);
// assuming the object we're checking has a foreground color
var C1 = $.computeLuminance(textobj.css('color'));
// find the first ancestor with an rgb background color (not empty, not transparent...)
var bgobj = bgobj.css('background-color').match(/^rgb.*/)
? bgobj
: bgobj.parentsUntil().filter(function(index) { return $(this).css('background-color').match(/^rgb.*/); }).filter(':first');
bgcolr = bgobj ? bgobj.css('background-color') : 'rbg(128, 128, 128)'; // guess gray if no background is set
var C2 = $.computeLuminance(bgcolr);
var ctrst = $.computeContrast(C1, C2);
// contrasts less than 4.5 are bad
// the label will also determine a class to attach: LowContrast or GoodContrast
var classlbl = (ctrst < 4.5)
? 'Low Contrast'
: 'Good Contrast';
if (modifydom) { // modify the DOM tree to make the status readable
var bgelem = bgobj.get(0);
var bgeleminfo = (
bgelem.id
? '#' + bgelem.id
: bgelem.tagName
) + (
bgobj.attr('class')
? '.' + bgobj.attr('class')
: '' );
var linkid = "contrasty_" + (textobj.get(0).id || Math.floor(Math.random()*100000) ); // not all DOM nodes will have ids
textobj.before(
'<p class="contrasty_warning"><a name="' + linkid + '" title="' + C2 + ': ' + ctrst + '">'
+ '(' + textobj.selector + ', ' + bgeleminfo + '): ' + classlbl + ': ' + ctrst
+ '</a></p>'
);
}
// attach a new class to illustrate the status visually
textobj.addClass( "contrasty_"+classlbl.replace(/\s/,"") );
return textobj;
};
$.clearContrasty = function() {
$(".contrasty_warning").remove();
$(".contrasty_LowContrast").removeClass("contrasty_LowContrast");
$(".contrasty_GoodContrast").removeClass("contrasty_GoodContrast");
return this;
};
$(document).ready(function() {
var contrastypairs = [
{foreground: "#hello", background: "#foo"},
{foreground: "div.albatross", background: "body"},
{foreground: "#blackness", background: "#blackness"}
];
$.each(contrastypairs, function(idx, pair) { $.contrastAgainst(pair.foreground, pair.background, 10000); });
setTimeout( $.clearContrasty, 10000 ) ; // remove the effects after a few seconds
module("Unit Tests");
test("Check that contrast is never less than 0", function()
{
expect(1); // one test assertion expected
$.contrastAgainst(contrastypairs[0].foreground, contrastypairs[0].background);
equals( $(".contrasty_LowContrast").filter("#hello").length,
1,
'Expected 1');
});
test("Check that one low contrast text object is detected", function()
{
expect(1); // one test assertion expected
$.contrastAgainst(contrastypairs[0].foreground, contrastypairs[0].background);
equals( $(".contrasty_LowContrast").filter("#hello").length,
1,
'Expected 1');
});
});
//]]>
</script>
</body>
</html>
@mcamiano
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment