<section id="color-contrast">
<h1>Color Contrast Checker (C3)</h1>
<div class="character">
<div id="jill">
<svg width="250" height="250" viewBox="0 0 250 250" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
<title id="title">Teacher</title>
<desc id="desc">Cartoon of a Caucasian woman smiling, and wearing black glasses and a purple shirt with white collar.</desc>
<defs>
<clipPath id="scene">
<circle cx="125" cy="125" r="115"/>
</clipPath>
<clipPath id="lips">
<path d="M 106,132 C 113,127 125,128 125,132 125,128 137,127 144,132 141,142 134,146 125,146 116,146 109,142 106,132 Z" />
</clipPath>
</defs>
<g stroke="none" stroke-width="0" clip-path="url(#scene)">
<rect x="0" y="0" width="250" height="250" fill="#b0d2e5" />
<g id="head">
<path fill="none" stroke="#111111" stroke-width="2" d="M 68,103 83,103.5" />
<path class="hair" d="M 67,90 67,169 78,164 89,169 100,164 112,169 125,164 138,169 150,164 161,169 172,164 183,169 183,90 Z" />
<circle cx="125" cy="100" r="55" class="skin" />
<ellipse cx="102" cy="107" rx="5" ry="5" class="eyes" id="eye-left" />
<ellipse cx="148" cy="107" rx="5" ry="5" class="eyes" id="eye-right" />
<rect x="119" y="140" width="12" height="40" class="skin" />
<path class="line eyebrow" d="M 90,98 C 93,90 103,89 110,94" id="eyebrow-left" />
<path class="line eyebrow" d="M 160,98 C 157,90 147,89 140,94" id="eyebrow-right"/>
<path stroke="#111111" stroke-width="4" d="M 68,103 83,102.5" />
<path stroke="#111111" stroke-width="4" d="M 182,103 167,102.5" />
<path stroke="#050505" stroke-width="3" fill="none" d="M 119,102 C 123,99 127,99 131,102" />
<path fill="#050505" d="M 92,97 C 85,97 79,98 80,101 81,104 84,104 85,102" />
<path fill="#050505" d="M 158,97 C 165,97 171,98 170,101 169,104 166,104 165,102" />
<path stroke="#050505" stroke-width="3" fill="rgba(240,240,255,0.4)" d="M 119,102 C 118,111 115,119 98,117 85,115 84,108 84,104 84,97 94,96 105,97 112,98 117,98 119,102 Z" />
<path stroke="#050505" stroke-width="3" fill="rgba(240,240,255,0.4)" d="M 131,102 C 132,111 135,119 152,117 165,115 166,108 166,104 166,97 156,96 145,97 138,98 133,98 131,102 Z" />
<path class="hair" d="M 60,109 C 59,39 118,40 129,40 139,40 187,43 189,99 135,98 115,67 115,67 115,67 108,90 80,109 86,101 91,92 92,87 85,99 65,108 60,109" />
<path id="mouth" fill="#d73e3e" d="M 106,132 C 113,127 125,128 125,132 125,128 137,127 144,132 141,142 134,149 125,149 116,149 109,142 106,132 Z" />
<path id="smile" fill="white" d="M125,144 C 140,144 143,132 143,132 143,132 125,133 125,133 125,133 106.5,132 106.5,132 106.5,132 110,144 125,144 Z" />
</g>
<g id="shirt">
<path fill="#8665c2" d="M 132,170 C 146,170 154,200 154,200 154,200 158,245 158,245 158,245 92,245 92,245 92,245 96,200 96,200 96,200 104,170 118,170 118,170 125,172 125,172 125,172 132,170 132,170 Z"/>
<path id="arm-left" class="arm" stroke="#8665c2" fill="none" stroke-width="14" d="M 118,178 C 94,179 66,220 65,254" />
<path id="arm-right" class="arm" stroke="#8665c2" fill="none" stroke-width="14" d="M 132,178 C 156,179 184,220 185,254" />
<path fill="white" d="M 117,166 C 117,166 125,172 125,172 125,182 115,182 109,170 Z" />
<path fill="white" d="M 133,166 C 133,166 125,172 125,172 125,182 135,182 141,170 Z" />
<circle cx="125" cy="188" r="4" fill="#5a487b" />
<circle cx="125" cy="202" r="4" fill="#5a487b" />
<circle cx="125" cy="216" r="4" fill="#5a487b" />
<circle cx="125" cy="230" r="4" fill="#5a487b" />
<circle cx="125" cy="244" r="4" fill="#5a487b" />
<path stroke="#daa37f" stroke-width="1" class="skin hand" id="hand-left" d="M 51,270 C 46,263 60,243 63,246 65,247 66,248 61,255 72,243 76,238 79,240 82,243 72,254 69,257 72,254 82,241 86,244 89,247 75,261 73,263 77,258 84,251 86,253 89,256 70,287 59,278" />
<path stroke="#daa37f" stroke-width="1" class="skin hand" id="hand-right" d="M 199,270 C 204,263 190,243 187,246 185,247 184,248 189,255 178,243 174,238 171,240 168,243 178,254 181,257 178,254 168,241 164,244 161,247 175,261 177,263 173,258 166,251 164,253 161,256 180,287 191,278"/>
</g>
</g>
</svg>
</div>
</div>
<div id="boxes">
<div id="aa-large">
<span>Large Text</span>
<span>WCAG AA</span>
</div>
<div id="aa-normal">
<span>Normal Text</span>
<span>WCAG AA</span>
</div>
<div id="aaa-large">
<span>Large Text</span>
<span>WCAG AAA</span>
</div>
<div id="aaa-normal">
<span>Normal Text</span>
<span>WCAG AAA</span>
</div>
</div>
<div id="sample">
<div contenteditable id="sample-text" style="color:rgb(0,0,0);background:rgb(255,255,255)">
Click to change demo text
</div>
</div>
<div id="bars">
<div>
<h2>Foreground</h2>
<div>
<label for="color-1-r" class="red">R</label>
<input id="color-1-r" type="range" min="0" max="255" value="0" />
<input id="number-1-r" type="number" min="0" max="255" value="0" />
</div>
<div>
<label for="color-1-g" class="green">G</label>
<input id="color-1-g" type="range" min="0" max="255" value="0" />
<input id="number-1-g" type="number" min="0" max="255" value="0" />
</div>
<div>
<label for="color-1-b" class="blue">B</label>
<input id="color-1-b" type="range" min="0" max="255" value="0" />
<input id="number-1-b" type="number" min="0" max="255" value="0" />
</div>
<input id="color-1-hex" data-target="1" type="text" value="#000000" maxlength="7" />
</div>
<div>
<h2>Background</h2>
<div>
<label for="color-2-r" class="red">R</label>
<input id="color-2-r" type="range" min="0" max="255" value="255" />
<input id="number-2-r" type="number" min="0" max="255" value="255" />
</div>
<div>
<label for="color-2-g" class="green">G</label>
<input id="color-2-g" type="range" min="0" max="255" value="255" />
<input id="number-2-g" type="number"min="0" max="255" value="255" />
</div>
<div>
<label for="color-2-b" class="blue">B</label>
<input id="color-2-b" type="range" min="0" max="255" value="255" />
<input id="number-2-b" type="number" min="0" max="255" value="255" />
</div>
<input id="color-2-hex" data-target="2" type="text" value="#FFFFFF" maxlength="7" />
</div>
</div>
</section>
/* Generic */
@import url('https://fonts.googleapis.com/css?family=Ubuntu');
html, body {
background: #ddd;
font-family: Ubuntu, sans-serif;
font-size: 16px;
}
h1, h2 {
text-align: center;
display: block;
margin-top: 0;
}
/* Tool general styles */
#color-contrast {
background: #fff;
box-shadow: 0 0 1rem rgba(0,0,0,0.25);
border-radius: 2px;
width: 500px;
margin: auto auto;
box-sizing: border-box;
padding: 2rem;
/*position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);*/
}
/* character */
#jill {
width: 250px;
height: 250px;
margin: auto auto;
border-radius: 100%;
}
/* SVG */
@-moz-keyframes blink {
0% { ry: 5; }
49% { ry: 5; }
50% { ry: 0; }
51% { ry: 5; }
}
@-webkit-keyframes blink {
0% { ry: 5; }
49% { ry: 5; }
50% { ry: 0; }
51% { ry: 5; }
}
@keyframes blink {
0% { ry: 5; }
49% { ry: 5; }
50% { ry: 0; }
51% { ry: 5; }
}
.skin { fill: #eab38f; }
.eyes { fill: #1f1f1f; animation: blink 10s alternate infinite; -webkit-animation: blink 10s alternate infinite; -moz-animation: blink 10s alternate infinite;}
.hair { fill: #2f1b0d; }
.line { fill: none; stroke: #2f1b0d; stroke-width:2px; }
.arm, #mouth, #smile, .eyes, .hand, .eyebrow { transition: all 0.25s; }
/* boxes */
#boxes {
display: flex;
margin-top: 1.5rem;
}
#boxes > div {
flex: 1;
text-align: center;
background: #081;
color: white;
padding: 0.5rem 0;
margin-left: 10px;
border-radius: 3px;
transition: background 0.5s;
position: relative;
}
#boxes > div::after {
content: "✓ Pass";
display: block;
position: absolute;
color: #000;
text-align:center;
width: 100%;
margin-top: 10px;
}
#boxes > div.fail {
background: #d32;
}
#boxes > div.fail::after {
content: "✕ Fail";
}
#boxes > div:first-child {
margin-left: 0;
}
#boxes span:first-child {
font-size: 0.75em;
display: inline-block;
margin-bottom: 0.5em;
}
/* demo */
#sample-text {
margin: 2.5rem 0 1.6rem 0;
padding: 1rem;
background: #fff;
color: #000;
text-align: center;
font-size: 20px;
border-radius: 2px;
}
/* sliders */
#bars {
display: flex;
flex-direction: row;
}
#bars > div {
flex: 1;
display: flex;
flex-direction: column;
}
#bars > div:first-child {
margin-right: 0.5rem;
}
#bars > div:last-child {
margin-left: 0.5rem;
}
#bars > div > div {
display: flex;
margin-bottom: 0.5rem;
}
#bars label {
display: inline-block;
color: white;
width: 2rem;
height: 2rem;
line-height: 2rem;
font-size: 1rem;
text-align: center;
border-radius: 2px;
box-sizing: border-box;
}
label.red { background: #d32; }
label.green { background: #081; }
label.blue { background: #26e; }
#bars input[type="range"] {
flex: 1;
}
#bars input[type="number"] {
text-align: center;
width: 3rem;
border-radius: 2px;
border: 1px solid #ccc;
font-size: 0.75rem;
height: 2rem;
box-sizing:border-box;
line-height: 2rem;
}
#bars input[type="text"] {
border-radius: 2px;
border: 1px solid #ccc;
text-align: center;
font-size: 1rem;
height: 2rem;
box-sizing:border-box;
line-height: 2rem;
}
function updateDemoColors(colorFront, colorBack) {
const demo = document.querySelector("#sample-text");
demo.style.color = `rgb(${colorFront.toString()})`;
demo.style.backgroundColor = `rgb(${colorBack.toString()})`;
}
/* the following functions are adapted from https://stackoverflow.com/a/9733420/3695983 */
function luminanace(r, g, b) {
var a = [r, g, b].map(function (v) {
v /= 255;
return v <= 0.03928
? v / 12.92
: Math.pow( (v + 0.055) / 1.055, 2.4 );
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
function contrast(rgb1, rgb2) {
const luminanceFront = luminanace(rgb1[0], rgb1[1], rgb1[2]);
const luminanceBack = luminanace(rgb2[0], rgb2[1], rgb2[2]);
return luminanceBack > luminanceFront
? ((luminanceFront + 0.05) / (luminanceBack + 0.05))
: ((luminanceBack + 0.05) / (luminanceFront + 0.05));
}
function updateBoxesColors(colorFront, colorBack) {
const ratio = contrast(colorFront, colorBack);
document.querySelector("#aa-normal").className = ratio < 0.22222 ? "" : "fail";
document.querySelector("#aa-large").className = ratio < 0.33333 ? "" : "fail";
document.querySelector("#aaa-normal").className = ratio < 0.14285 ? "" : "fail";
document.querySelector("#aaa-large").className = ratio < 0.22222 ? "" : "fail";
const totalWrong = document.querySelectorAll(".fail").length;
let mouth = document.querySelector("#mouth");
let smile = document.querySelector("#smile");
switch(totalWrong) {
case 0:
mouth.setAttribute("d", "M 106,132 C 113,127 125,128 125,132 125,128 137,127 144,132 141,142 134,149 125,149 116,149 109,142 106,132 Z");
smile.setAttribute("d", "M125,144 C 140,144 143,132 143,132 143,132 125,133 125,133 125,133 106.5,132 106.5,132 106.5,132 110,144 125,144 Z");
break;
case 1:
case 2:
mouth.setAttribute("d", "M 106,132 C 113,127 125,128 125,132 125,128 137,127 144,132 141,142 134,146 125,146 116,146 109,142 106,132 Z");
smile.setAttribute("d", "M125,141 C 140,141 143,132 143,132 143,132 125,133 125,133 125,133 106.5,132 106.5,132 106.5,132 110,141 125,141 Z");
break;
case 3:
mouth.setAttribute("d", "M 106,132 C 113,127 125,128 125,132 125,128 137,127 144,132 141,138 140,143 125,143 110,143 109,138 106,132 Z");
smile.setAttribute("d", "M125,138 C 140,138 143.5,132 143.5,132 143.5,132 125,133 125,133 125,133 106.5,132 106.5,132 106.5,132 110,138 125,138 Z");
break;
case 4:
mouth.setAttribute("d", "M 106,132 C 113,127 125,128 125,132 125,128 137,127 144,132 141,138 134,142 125,142 116,142 109,138 106,132 Z");
smile.setAttribute("d", "M125,135 C 140,135 143,132 143,132 143,135 125,136 125,136 125,136 106.5,135 106.5,132 106.5,132 110,135 125,135 Z");
break;
}
}
function updateHex(colorFront, colorBack) {
const colorFrontHex = colorFront.map(function(el) { return Number(el).toString(16).padStart(2, "0").toUpperCase(); });
const colorBackHex = colorBack.map(function(el) { return Number(el).toString(16).padStart(2, "0").toUpperCase(); });
document.querySelector("#color-1-hex").value = `#${colorFrontHex.join('')}`;
document.querySelector("#color-2-hex").value = `#${colorBackHex.join('')}`
}
function updateColors() {
const colorFront = [
document.querySelector("#color-1-r").value,
document.querySelector("#color-1-g").value,
document.querySelector("#color-1-b").value
];
const colorBack = [
document.querySelector("#color-2-r").value,
document.querySelector("#color-2-g").value,
document.querySelector("#color-2-b").value
];
updateDemoColors(colorFront, colorBack);
updateBoxesColors(colorFront, colorBack);
updateHex(colorFront, colorBack);
}
document.querySelectorAll("input[type='number'], input[type='range']").forEach(function(el) {
el.addEventListener("input", function() {
if (this.type === "range") {
this.nextElementSibling.value = this.value;
} else if (this.type === "number") {
this.previousElementSibling.value = this.value;
}
updateColors();
});
});
document.querySelectorAll("input[type='text']").forEach(function(el) {
el.addEventListener("blur", function() {
let val = this.value;
let wrongValue = false;
if (val[0] === "#") val = val.substring(1);
if (val.length === 3 || val.length === 6) {
if (val.length === 3) {
val = `${val[0]}${val[0]}${val[1]}${val[1]}${val[2]}${val[2]}`;
}
if (val.match(/[0-9A-Fa-f]{6}/)) {
const red = parseInt(`${val[0]}${val[1]}`, 16);
const green = parseInt(`${val[2]}${val[3]}`, 16);
const blue = parseInt(`${val[4]}${val[5]}`, 16);
const target = this.dataset.target;
document.getElementById(`number-${target}-r`).value = red;
document.getElementById(`number-${target}-g`).value = green;
document.getElementById(`number-${target}-b`).value = blue;
document.getElementById(`color-${target}-r`).value = red;
document.getElementById(`color-${target}-g`).value = green;
document.getElementById(`color-${target}-b`).value = blue;
updateColors();
} else {
wrongValue = true;
}
} else {
wrongValue = true;
}
if (wrongValue){
const colorFront = [
document.querySelector("#color-1-r").value,
document.querySelector("#color-1-g").value,
document.querySelector("#color-1-b").value
];
const colorBack = [
document.querySelector("#color-2-r").value,
document.querySelector("#color-2-g").value,
document.querySelector("#color-2-b").value
];
updateHex(colorFront, colorBack)
}
});
})
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.