- commit
- 576ecafe14bb97c13a746fa88e7c785755cbc774
- parent
- 05aeed9667df1fd74a1f81a46a62f98377b2e091
- Author
- Tobias Bengfort <tobias.bengfort@posteo.de>
- Date
- 2023-05-04 16:25
init
Diffstat
| A | contrast.js | 127 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | favicon.js | 20 | ++++++++++++++++++++ |
| A | index.html | 33 | +++++++++++++++++++++++++++++++++ |
| A | style.css | 108 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | wcag2.js | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
5 files changed, 345 insertions, 0 deletions
diff --git a/contrast.js b/contrast.js
@@ -0,0 +1,127 @@
-1 1 import * as wcag2 from './wcag2.js';
-1 2 import * as favicon from './favicon.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 output = document.querySelector('#output');
-1 8
-1 9 var score = function(range, levels) {
-1 10 var biggerThan = function(t) {
-1 11 if (range[0] > t) {
-1 12 return 1;
-1 13 } else if (range[1] > t) {
-1 14 return (range[1] - t) / (range[1] - range[0]);
-1 15 } else {
-1 16 return 0;
-1 17 }
-1 18 };
-1 19
-1 20 var result = [];
-1 21 var sum = 0;
-1 22 levels.forEach(level => {
-1 23 var v = biggerThan(-level) - biggerThan(level);
-1 24 result.push(v - sum);
-1 25 sum = v;
-1 26 });
-1 27 result.push(1 - sum);
-1 28
-1 29 return result;
-1 30 };
-1 31
-1 32 var makeGradient = function(scores) {
-1 33 const colors = [
-1 34 'hsl(0, 100%, 40%)',
-1 35 'hsl(40, 100%, 45%)',
-1 36 'hsl(80, 60%, 45%)',
-1 37 'hsl(95, 60%, 41%)',
-1 38 ];
-1 39
-1 40 var stops = [];
-1 41 var prevScore = 0;
-1 42 var scale = x => x * 70 + 15; // compensate for border radius
-1 43
-1 44 for (var i = 0; i < scores.length; i++) {
-1 45 if (scores[i] > 0) {
-1 46 var newScore = prevScore + scores[i];
-1 47 stops.push(`${colors[i]} ${scale(prevScore)}%`, `${colors[i]} ${scale(newScore)}%`);
-1 48 prevScore = newScore;
-1 49 }
-1 50 }
-1 51
-1 52 return `linear-gradient(135deg, ${stops.join(', ')})`;
-1 53 };
-1 54
-1 55 var parseColor = function(s) {
-1 56 var rgba = s.match(/rgba?\(([\d.]+), ([\d.]+), ([\d.]+)(?:, ([\d.]+))?\)/);
-1 57 if (!rgba) {
-1 58 return null;
-1 59 }
-1 60 rgba.shift();
-1 61 if (rgba[3] === undefined) {
-1 62 rgba[3] = 1;
-1 63 }
-1 64 rgba = rgba.map(x => parseFloat(x, 10));
-1 65 return rgba;
-1 66 };
-1 67
-1 68 var setColor = function(input, key) {
-1 69 var old = getComputedStyle(document.body)[key];
-1 70 document.body.style[key] = input.value;
-1 71 var value = getComputedStyle(document.body)[key];
-1 72 return value !== old;
-1 73 };
-1 74
-1 75 var formatRange = function(range, places) {
-1 76 var avg = (range[0] + range[1]) / 2;
-1 77 var delta = avg - range[0];
-1 78 if (delta.toFixed(places) === (0).toFixed(places)) {
-1 79 return `${avg.toFixed(places)}`;
-1 80 } else {
-1 81 return `${avg.toFixed(places)} ±${delta.toFixed(places)}`;
-1 82 }
-1 83 };
-1 84
-1 85 var oninput = function() {
-1 86 // NOTE: | to prevent lazy evaluation
-1 87 if (setColor(bgInput, 'backgroundColor') | setColor(fgInput, 'color')) {
-1 88 var fgUrl = encodeURIComponent(fgInput.value);
-1 89 var bgUrl = encodeURIComponent(bgInput.value);
-1 90 location.hash = `${fgUrl}-on-${bgUrl}`;
-1 91
-1 92 favicon.setFavicon(bgInput.value, fgInput.value);
-1 93
-1 94 var computed = getComputedStyle(document.body);
-1 95 var bg = parseColor(computed.backgroundColor);
-1 96 var fg = parseColor(computed.color);
-1 97
-1 98 var wcag2Range = wcag2.contrastAlpha(fg, bg, wcag2.contrast);
-1 99 output.style.backgroundImage = makeGradient(score(wcag2Range.map(Math.log), wcag2.levels.map(Math.log)));
-1 100 output.textContent = formatRange(wcag2.getAbsRange(wcag2Range), 2);
-1 101 }
-1 102 };
-1 103
-1 104 var onhashchange = function() {
-1 105 var colors = location.hash.slice(1).split('-on-');
-1 106 fgInput.value = decodeURIComponent(colors[0]);
-1 107 bgInput.value = decodeURIComponent(colors[1]);
-1 108 oninput();
-1 109 };
-1 110
-1 111 var onswap = function() {
-1 112 var tmp = bgInput.value;
-1 113 bgInput.value = fgInput.value;
-1 114 fgInput.value = tmp;
-1 115 oninput();
-1 116 };
-1 117
-1 118 fgInput.addEventListener('input', oninput);
-1 119 bgInput.addEventListener('input', oninput);
-1 120 swapButton.addEventListener('click', onswap);
-1 121 window.addEventListener('hashchange', onhashchange);
-1 122
-1 123 if (location.hash) {
-1 124 onhashchange();
-1 125 } else {
-1 126 oninput();
-1 127 }
diff --git a/favicon.js b/favicon.js
@@ -0,0 +1,20 @@
-1 1 var favicon = document.querySelector('link[rel="shortcut icon"]');
-1 2
-1 3 var canvas = document.createElement('canvas');
-1 4 var ctx = canvas.getContext('2d');
-1 5 document.body.appendChild(canvas);
-1 6 canvas.width = 16;
-1 7 canvas.height = 16;
-1 8 canvas.hidden = true;
-1 9
-1 10 export var setFavicon = function(bg, fg) {
-1 11 ctx.clearRect(0, 0, 16, 16);
-1 12
-1 13 ctx.fillStyle = bg;
-1 14 ctx.fillRect(0, 0, 8, 16);
-1 15
-1 16 ctx.fillStyle = fg;
-1 17 ctx.fillRect(8, 0, 8, 16);
-1 18
-1 19 favicon.href = canvas.toDataURL();
-1 20 };
diff --git a/index.html b/index.html
@@ -0,0 +1,33 @@ -1 1 <!DOCTYPE html> -1 2 <html lang="en" class="checkered"> -1 3 <head> -1 4 <meta charset="utf-8" /> -1 5 <title>Contrast Ratio</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 <output class="gradient" id="output" for="bgInput fgInput" aria-live="polite"> -1 11 <strong></strong> -1 12 <span></span> -1 13 </output> -1 14 -1 15 <div id="form"> -1 16 <label> -1 17 <span>Background</span> -1 18 <input id="bgInput" value="hsla(200,0%,0%,0.8)" autofocus> -1 19 </label> -1 20 <label> -1 21 <span>Text color</span> -1 22 <input id="fgInput" value="orangered"> -1 23 </label> -1 24 <button id="swap">Swap colors</button> -1 25 </div> -1 26 -1 27 <div id="display"> -1 28 <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 29 </div> -1 30 -1 31 <script src="contrast.js" type="module"></script> -1 32 </body> -1 33 </html>
diff --git a/style.css b/style.css
@@ -0,0 +1,108 @@
-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 button {
-1 13 font-size: 80%;
-1 14 padding-block: 0.4em;
-1 15 padding-inline: 0.6em;
-1 16 white-space: nowrap;
-1 17 }
-1 18
-1 19 html,
-1 20 body {
-1 21 margin: 0;
-1 22 padding: 0;
-1 23 }
-1 24
-1 25 .checkered {
-1 26 background: linear-gradient(45deg, currentColor 25%, transparent 25%, transparent 75%, currentColor 75%, currentColor),
-1 27 linear-gradient(45deg, currentColor 25%, transparent 25%, transparent 75%, currentColor 75%, currentColor) 0.5rem 0.5rem;
-1 28 background-color: #eee;
-1 29 background-size: 1rem 1rem;
-1 30 }
-1 31
-1 32 body {
-1 33 min-block-size: 100vb;
-1 34 padding-block: 2em;
-1 35 padding-inline: 1em;
-1 36 }
-1 37
-1 38 #form {
-1 39 display: flex;
-1 40 gap: 0.5em;
-1 41 flex-direction: column;
-1 42 align-items: center;
-1 43 justify-content: center;
-1 44 text-align: center;
-1 45 }
-1 46
-1 47 #form label span {
-1 48 display: inline-block;
-1 49 margin-inline: 0.8rem;
-1 50 padding-block: 0.1em;
-1 51 padding-inline: 0.4em;
-1 52 white-space: nowrap;
-1 53 background-color: #666;
-1 54 color: #fff;
-1 55 font-size: 70%;
-1 56 font-weight: bold;
-1 57 }
-1 58
-1 59 #form input {
-1 60 display: block;
-1 61 inline-size: 22ch; /* to fit rgba(255,255,255,0.5) */
-1 62 margin-block-start: -1px;
-1 63 padding-block: 0.2em;
-1 64 padding-inline: 0.5ch;
-1 65 font-family: monospace;
-1 66 font-size: 150%;
-1 67 text-align: inherit;
-1 68 box-shadow: 0.05em 0.1em 0.2em rgba(0,0,0,.4) inset;
-1 69 background: #eee;
-1 70 color: #000;
-1 71 border-radius: 0.3em;
-1 72 }
-1 73
-1 74 #form label + label {
-1 75 order: 2;
-1 76 }
-1 77
-1 78 #display {
-1 79 max-inline-size: 30em;
-1 80 margin-inline: auto;
-1 81 }
-1 82
-1 83 .gradient {
-1 84 display: flex;
-1 85 block-size: 5em;
-1 86 inline-size: 5em;
-1 87 justify-content: center;
-1 88 align-items: center;
-1 89 text-align: center;
-1 90 border-radius: 50%;
-1 91 margin-inline: auto;
-1 92 margin-block-end: 1em;
-1 93 padding: 0.5em;
-1 94 box-shadow: 0 0.1em 0.2em rgba(0,0,0,.4);
-1 95 background-color: #888;
-1 96 color: #fff;
-1 97 text-shadow: 0 -0.06em .05em rgba(0,0,0,.5);
-1 98 font-weight: bold;
-1 99 }
-1 100
-1 101 @media (min-width: 70em) {
-1 102 #form {
-1 103 flex-direction: row;
-1 104 }
-1 105 #form button {
-1 106 margin-block-start: 1.2rem;
-1 107 }
-1 108 }
diff --git a/wcag2.js b/wcag2.js
@@ -0,0 +1,57 @@
-1 1 export const levels = [3, 4.5, 7];
-1 2
-1 3 var sRGBtoY = function(srgb) {
-1 4 var pre = c => c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
-1 5 var r = pre(srgb[0] / 255);
-1 6 var g = pre(srgb[1] / 255);
-1 7 var b = pre(srgb[2] / 255);
-1 8 return 0.2126 * r + 0.7152 * g + 0.0722 * b;
-1 9 };
-1 10
-1 11 export var contrast = function(fg, bg) {
-1 12 // NOTE: this returns a "signed" value.
-1 13 // `c >= 1 ? c : 1 / c` gives you the actual value.
-1 14 // See also `getAbsRange()`.
-1 15
-1 16 var yfg = sRGBtoY(fg);
-1 17 var ybg = sRGBtoY(bg);
-1 18 return (ybg + 0.05) / (yfg + 0.05);
-1 19 };
-1 20
-1 21 export var abs = function(c) {
-1 22 return c < 1 ? 1 / c : c;
-1 23 };
-1 24
-1 25 export var getAbsRange = function(range) {
-1 26 if (range[0] >= 1) {
-1 27 return range;
-1 28 } else if (range[1] <= 1) {
-1 29 return [1 / range[1], 1 / range[0]];
-1 30 } else {
-1 31 return [1, Math.max(1 / range[0], range[1])];
-1 32 }
-1 33 };
-1 34
-1 35 var alphaBlend = function(fg, bg) {
-1 36 return [
-1 37 fg[0] * fg[3] + bg[0] * (1 - fg[3]),
-1 38 fg[1] * fg[3] + bg[1] * (1 - fg[3]),
-1 39 fg[2] * fg[3] + bg[2] * (1 - fg[3]),
-1 40 ];
-1 41 };
-1 42
-1 43 export var contrastAlpha = function(afg, abg, contrast) {
-1 44 var bgBlack = alphaBlend(abg, [0, 0, 0]);
-1 45 var fgBlack = alphaBlend(afg, bgBlack);
-1 46 var cBlack = contrast(fgBlack, bgBlack);
-1 47
-1 48 var bgWhite = alphaBlend(abg, [255, 255, 255]);
-1 49 var fgWhite = alphaBlend(afg, bgWhite);
-1 50 var cWhite = contrast(fgWhite, bgWhite);
-1 51
-1 52 return [
-1 53 Math.min(cBlack, cWhite),
-1 54 Math.max(cBlack, cWhite),
-1 55 ];
-1 56 };
-1 57