- commit
- 3df4fa022c59427e3be3ba538b1b333aefee5659
- parent
- afce56b362b813d36aa200eb812ab9b6618df14f
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2022-07-15 12:45
add tool
Diffstat
| M | README.md | 2 | +- |
| A | tool/index.html | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
| A | tool/style.css | 119 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | tool/tool.js | 155 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 317 insertions, 1 deletions
diff --git a/README.md b/README.md
@@ -11,7 +11,7 @@ APCA was created by Andrew Somers (Myndex) and is currently being proposed for 11 11 the next major version of the [W3C Accessibility Guidelines 12 12 (WCAG)](https://www.w3.org/TR/wcag-3.0/). 13 1314 -1 An interactive demo is available at <https://xi.github.io/apca-introduction/>.-1 14 An interactive demo is available at <https://xi.github.io/apca-introduction/tool/>. 15 15 16 16 ## Algorithm 17 17
diff --git a/tool/index.html b/tool/index.html
@@ -0,0 +1,42 @@ -1 1 <!DOCTYPE html> -1 2 <html lang="en"> -1 3 <head> -1 4 <meta charset="utf-8" /> -1 5 <title>APCA Contrast</title> -1 6 <link rel="stylesheet" href="style.css" /> -1 7 <link rel="shortcut icon" type="image/png" href="about:blank" /> -1 8 </head> -1 9 <body> -1 10 <div id="form"> -1 11 <label> -1 12 <span>Background</span> -1 13 <input id="bgInput" value="hsla(200,0%,0%,0.8)" autofocus> -1 14 </label> -1 15 <label> -1 16 <span>Text color</span> -1 17 <input id="fgInput" value="orangered"> -1 18 </label> -1 19 <button id="swap">Swap colors</button> -1 20 </div> -1 21 -1 22 <div id="output"> -1 23 <label> -1 24 <span class="gradient" id="apcaGradient"></span> -1 25 <abbr title="Accessible Perceptual Contrast Algorithm">APCA</abbr>: -1 26 <output id="apcaOutput" for="bgInput fgInput" aria-live="polite"></output> -1 27 </label> -1 28 <label> -1 29 <span class="gradient" id="wcag2Gradient"></span> -1 30 <abbr title="Web Content Accessibility Guidelines Version 2">WCAG 2.x</abbr>: -1 31 <output id="wcag2Output" for="bgInput fgInput" aria-live="polite"></output> -1 32 </label> -1 33 <a href="https://github.com/xi/apca-introduction">About APCA</a> -1 34 </div> -1 35 -1 36 <div id="display"> -1 37 <p>This sample text attempts to visually demonstrate how readable this color combination is, for normal, <i>italic</i>, <b>bold</b>, or <small>small</small> text.</p> -1 38 </div> -1 39 -1 40 <script src="tool.js" type="module"></script> -1 41 </body> -1 42 </html>
diff --git a/tool/style.css b/tool/style.css
@@ -0,0 +1,119 @@
-1 1 /* Inspired by https://contrast-ratio.com */
-1 2
-1 3 * {
-1 4 box-sizing: border-box;
-1 5 }
-1 6
-1 7 :root {
-1 8 font-size: 150%;
-1 9 line-height: 1.3;
-1 10 }
-1 11
-1 12 output {
-1 13 font-weight: bold;
-1 14 }
-1 15
-1 16 button {
-1 17 font-size: 80%;
-1 18 padding: 0.4em 0.6em;
-1 19 white-space: nowrap;
-1 20 }
-1 21
-1 22 html,
-1 23 body {
-1 24 margin: 0;
-1 25 padding: 0;
-1 26 }
-1 27
-1 28 html {
-1 29 background: linear-gradient(45deg, currentColor 25%, transparent 25%, transparent 75%, currentColor 75%, currentColor),
-1 30 linear-gradient(45deg, currentColor 25%, transparent 25%, transparent 75%, currentColor 75%, currentColor) 0.5rem 0.5rem;
-1 31 background-color: #eee;
-1 32 background-size: 1rem 1rem;
-1 33 }
-1 34
-1 35 body {
-1 36 display: grid;
-1 37 grid-template-rows: min-content 1fr min-content;
-1 38 min-height: 100vh;
-1 39 }
-1 40
-1 41 #form {
-1 42 display: flex;
-1 43 padding: 0.5em;
-1 44 gap: 0.5em;
-1 45 flex-direction: column;
-1 46 align-items: center;
-1 47 justify-content: center;
-1 48 text-align: center;
-1 49 }
-1 50
-1 51 #form label span {
-1 52 display: inline-block;
-1 53 margin: 0 0.8rem;
-1 54 padding: 0.1em 0.4em;
-1 55 white-space: nowrap;
-1 56 background-color: #666;
-1 57 color: #fff;
-1 58 font-size: 70%;
-1 59 font-weight: bold;
-1 60 }
-1 61
-1 62 #form input {
-1 63 display: block;
-1 64 width: 22ch; /* to fit rgba(255,255,255,0.5) */
-1 65 margin-top: -1px;
-1 66 padding: 0.2em 0.5ch;
-1 67 font-family: monospace;
-1 68 font-size: 150%;
-1 69 text-align: inherit;
-1 70 box-shadow: 0.05em 0.1em 0.2em rgba(0,0,0,.4) inset;
-1 71 background: #eee;
-1 72 color: #000;
-1 73 border-radius: 0.3em;
-1 74 }
-1 75
-1 76 #form label + label {
-1 77 order: 2;
-1 78 }
-1 79
-1 80 #display {
-1 81 grid-row: 2;
-1 82 }
-1 83
-1 84 #display p {
-1 85 max-width: 40em;
-1 86 margin: 0 auto;
-1 87 padding: 1em;
-1 88 }
-1 89
-1 90 #output {
-1 91 grid-row: 3;
-1 92 padding: 0.5em;
-1 93 background: #eee;
-1 94 color: #000;
-1 95 text-align: center;
-1 96 }
-1 97
-1 98 #output label {
-1 99 display: inline-block;
-1 100 margin: 0 1em;
-1 101 }
-1 102
-1 103 .gradient {
-1 104 display: inline-block;
-1 105 vertical-align: -0.35em;
-1 106 width: 1.5em;
-1 107 height: 1.5em;
-1 108 border-radius: 50%;
-1 109 background-color: #ccc;
-1 110 }
-1 111
-1 112 @media (min-width: 70em) {
-1 113 #form {
-1 114 flex-direction: row;
-1 115 }
-1 116 #form button {
-1 117 margin-top: 1.2rem;
-1 118 }
-1 119 }
diff --git a/tool/tool.js b/tool/tool.js
@@ -0,0 +1,155 @@
-1 1 import * as apca from '../apca.js';
-1 2 import * as wcag2 from '../wcag2.js';
-1 3
-1 4 var fgInput = document.querySelector('#fgInput');
-1 5 var bgInput = document.querySelector('#bgInput');
-1 6 var swapButton = document.querySelector('#swap');
-1 7 var apcaGradient = document.querySelector('#apcaGradient');
-1 8 var apcaOutput = document.querySelector('#apcaOutput');
-1 9 var wcag2Gradient = document.querySelector('#wcag2Gradient');
-1 10 var wcag2Output = document.querySelector('#wcag2Output');
-1 11
-1 12 var alphaBlend = function(fg, bg) {
-1 13 return [
-1 14 fg[0] * fg[3] + bg[0] * (1 - fg[3]),
-1 15 fg[1] * fg[3] + bg[1] * (1 - fg[3]),
-1 16 fg[2] * fg[3] + bg[2] * (1 - fg[3]),
-1 17 ];
-1 18 };
-1 19
-1 20 var contrastAlpha = function(afg, abg, contrast) {
-1 21 var bgBlack = alphaBlend(abg, [0, 0, 0]);
-1 22 var fgBlack = alphaBlend(afg, bgBlack);
-1 23 var cBlack = contrast(fgBlack, bgBlack);
-1 24
-1 25 var bgWhite = alphaBlend(abg, [255, 255, 255]);
-1 26 var fgWhite = alphaBlend(afg, bgWhite);
-1 27 var cWhite = contrast(fgWhite, bgWhite);
-1 28
-1 29 return [
-1 30 Math.min(cBlack, cWhite),
-1 31 Math.max(cBlack, cWhite),
-1 32 ];
-1 33 };
-1 34
-1 35 var score = function(range, levels) {
-1 36 var biggerThan = function(t) {
-1 37 if (range[0] > t) {
-1 38 return 1;
-1 39 } else if (range[1] > t) {
-1 40 return (range[1] - t) / (range[1] - range[0]);
-1 41 } else {
-1 42 return 0;
-1 43 }
-1 44 };
-1 45
-1 46 var result = [];
-1 47 var sum = 0;
-1 48 levels.forEach(level => {
-1 49 var v = biggerThan(-level) - biggerThan(level);
-1 50 result.push(v - sum);
-1 51 sum = v;
-1 52 });
-1 53 result.push(1 - sum);
-1 54
-1 55 return result;
-1 56 };
-1 57
-1 58 var makeGradient = function(scores) {
-1 59 const colors = [
-1 60 'hsl(0, 100%, 40%)',
-1 61 'hsl(40, 100%, 45%)',
-1 62 'hsl(80, 60%, 45%)',
-1 63 'hsl(95, 60%, 41%)',
-1 64 'hsl(-70, 80%, 40%)',
-1 65 ];
-1 66
-1 67 var stops = [];
-1 68 var prevScore = 0;
-1 69 var scale = x => x * 70 + 15; // compensate for border radius
-1 70
-1 71 for (var i = 0; i < scores.length; i++) {
-1 72 if (scores[i] > 0) {
-1 73 var newScore = prevScore + scores[i];
-1 74 stops.push(`${colors[i]} ${scale(prevScore)}%`, `${colors[i]} ${scale(newScore)}%`);
-1 75 prevScore = newScore;
-1 76 }
-1 77 }
-1 78
-1 79 return `linear-gradient(135deg, ${stops.join(', ')})`;
-1 80 };
-1 81
-1 82 var parseColor = function(s) {
-1 83 var rgba = s.match(/rgba?\(([\d.]+), ([\d.]+), ([\d.]+)(?:, ([\d.]+))?\)/);
-1 84 if (!rgba) {
-1 85 return null;
-1 86 }
-1 87 rgba.shift();
-1 88 if (rgba[3] === undefined) {
-1 89 rgba[3] = 1;
-1 90 }
-1 91 rgba = rgba.map(x => parseFloat(x, 10));
-1 92 return rgba;
-1 93 };
-1 94
-1 95 var setColor = function(input, key) {
-1 96 var old = getComputedStyle(document.body)[key];
-1 97 document.body.style[key] = input.value;
-1 98 var value = getComputedStyle(document.body)[key];
-1 99 return value !== old;
-1 100 };
-1 101
-1 102 var formatRange = function(range, places) {
-1 103 var avg = (range[0] + range[1]) / 2;
-1 104 var delta = avg - range[0];
-1 105 if (delta.toFixed(places) === (0).toFixed(places)) {
-1 106 return `${avg.toFixed(places)}`;
-1 107 } else {
-1 108 return `${avg.toFixed(places)} ±${delta.toFixed(places)}`;
-1 109 }
-1 110 };
-1 111
-1 112 var oninput = function() {
-1 113 // NOTE: | to prevent lazy evaluation
-1 114 if (setColor(bgInput, 'backgroundColor') | setColor(fgInput, 'color')) {
-1 115 var fgUrl = encodeURIComponent(fgInput.value);
-1 116 var bgUrl = encodeURIComponent(bgInput.value);
-1 117 location.hash = `${fgUrl}-on-${bgUrl}`;
-1 118
-1 119 var bg = parseColor(getComputedStyle(document.body).backgroundColor);
-1 120 var fg = parseColor(getComputedStyle(document.body).color);
-1 121
-1 122 var apcaRange = contrastAlpha(fg, bg, apca.contrast);
-1 123 apcaGradient.style.backgroundImage = makeGradient(score(apcaRange, apca.levels));
-1 124 apcaOutput.textContent = formatRange(apcaRange, 0);
-1 125
-1 126 var wcag2Range = contrastAlpha(fg, bg, wcag2.contrast);
-1 127 wcag2Gradient.style.backgroundImage = makeGradient(score(wcag2Range.map(Math.log), wcag2.levels.map(Math.log)));
-1 128 wcag2Output.textContent = formatRange(wcag2.getAbsRange(wcag2Range), 1);
-1 129 }
-1 130 };
-1 131
-1 132 var onhashchange = function() {
-1 133 var colors = location.hash.slice(1).split('-on-');
-1 134 fgInput.value = decodeURIComponent(colors[0]);
-1 135 bgInput.value = decodeURIComponent(colors[1]);
-1 136 oninput();
-1 137 };
-1 138
-1 139 var onswap = function() {
-1 140 var tmp = bgInput.value;
-1 141 bgInput.value = fgInput.value;
-1 142 fgInput.value = tmp;
-1 143 oninput();
-1 144 };
-1 145
-1 146 fgInput.addEventListener('input', oninput);
-1 147 bgInput.addEventListener('input', oninput);
-1 148 swapButton.addEventListener('click', onswap);
-1 149 window.addEventListener('hashchange', onhashchange);
-1 150
-1 151 if (location.hash) {
-1 152 onhashchange();
-1 153 } else {
-1 154 oninput();
-1 155 }