Created
January 13, 2012 14:14
-
-
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
This file contains 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
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo at http://mitch.amiano.agilemarkup.com/explorations/contrasty/jQueryContrasty.html