diff --git a/package.json b/package.json
index e1b605ec..f203cd4c 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,8 @@
"script-loader": "^0.6.1",
"style-loader": "^0.10.1",
"webpack": "^1.8.4",
- "webpack-dev-server": "^1.9.0"
+ "webpack-dev-server": "^1.9.0",
+ "color-blind": "^0.1.0"
},
"scripts": {
"build": "npm run prod && npm run dev",
diff --git a/plugins/contrast/error-description.handlebars b/plugins/contrast/error-description.handlebars
index d6eb2ac6..131cedbb 100644
--- a/plugins/contrast/error-description.handlebars
+++ b/plugins/contrast/error-description.handlebars
@@ -2,8 +2,8 @@
The color combination
{{fgColorHex}}/{{bgColorHex}}
has a contrast ratio of {{contrastRatio}}, which is not
- sufficient. At this size, you will need a ratio of at least
- {{requiredRatio}}.
+ sufficient for {{blindness}} users. At this size, you will need a ratio of at
+ least {{requiredRatio}}.
diff --git a/plugins/contrast/error-title.handlebars b/plugins/contrast/error-title.handlebars
index 1593f153..9012f7c3 100644
--- a/plugins/contrast/error-title.handlebars
+++ b/plugins/contrast/error-title.handlebars
@@ -1,4 +1,9 @@
-Insufficient contrast ratio ({{contrastRatio}} < {{requiredRatio}})
+Insufficient contrast ratio (
+
{{contrastRatio}} < {{requiredRatio}})
diff --git a/plugins/contrast/index.js b/plugins/contrast/index.js
index 21ee7df7..3975b657 100644
--- a/plugins/contrast/index.js
+++ b/plugins/contrast/index.js
@@ -6,6 +6,7 @@
let $ = require("jquery");
let Plugin = require("../base");
let annotate = require("../shared/annotate")("labels");
+let blinder = require("color-blind/lib/blind").Blind;
let titleTemplate = require("./error-title.handlebars");
let descriptionTemplate = require("./error-description.handlebars");
@@ -21,7 +22,7 @@ class ContrastPlugin extends Plugin {
return "Labels elements with insufficient contrast";
}
- addError({fgColor, bgColor, contrastRatio, requiredRatio}, el) {
+ addError({fgColor, bgColor, blindness, contrastRatio, requiredRatio}, el) {
// Suggest colors at an "AA" level
let suggestedColors = axs.utils.suggestColors(
bgColor,
@@ -36,7 +37,8 @@ class ContrastPlugin extends Plugin {
requiredRatio: requiredRatio,
suggestedFgColorHex: suggestedColors.fg,
suggestedBgColorHex: suggestedColors.bg,
- suggestedColorsRatio: suggestedColors.contrast
+ suggestedColorsRatio: suggestedColors.contrast,
+ blindness: blindness
};
return this.error(
@@ -45,6 +47,80 @@ class ContrastPlugin extends Plugin {
$(el));
}
+ blind(color, blindness) {
+ let alpha = color.alpha;
+ color = blinder({
+ R: color.red,
+ G: color.green,
+ B: color.blue
+ }, blindness, false);
+ return {
+ red: color.R | 0,
+ green: color.G | 0,
+ blue: color.B | 0,
+ alpha: alpha
+ };
+ }
+
+ runOneType(el, combinations, fgColor, bgColor, style, blindness, blindnessName) {
+ // Calculate required ratio based on size
+ // Using strings to prevent rounding
+ let requiredRatio = axs.utils.isLargeFont(style) ?
+ "3.0" : "4.5";
+
+ // Build a key for our `combinations` map and report the color
+ // if we have not seen it yet
+ let key = axs.utils.colorToString(fgColor) + "/" +
+ axs.utils.colorToString(bgColor) + "/" +
+ // blindness + "/" + // Overwhelming: report one at a time
+ requiredRatio;
+
+ if (blindness !== null)
+ {
+ fgColor = this.blind(fgColor, blindness);
+ bgColor = this.blind(bgColor, blindness);
+ }
+ let contrastRatio = axs.utils.calculateContrastRatio(
+ fgColor, bgColor).toFixed(2);
+
+ if (!axs.utils.isLowContrast(contrastRatio, style)) {
+ // For acceptable contrast values, we don't show ratios if
+ // they have been presented already
+ if (!combinations[key]) {
+ annotate
+ .label($(el), contrastRatio)
+ .addClass("tota11y-label-success");
+
+ // Add the key to the combinations map. We don't have an
+ // error to associate it with, so we'll just give it the
+ // value of `true`.
+ combinations[key] = true;
+ }
+ } else {
+ if (!combinations[key]) {
+ // We do not show duplicates in the errors panel, however,
+ // to keep the output from being overwhelming
+ let error = this.addError(
+ {fgColor, bgColor, blindness: blindnessName, contrastRatio, requiredRatio},
+ el);
+
+ combinations[key] = error;
+ }
+
+ // We display errors multiple times for emphasis. Each error
+ // will point back to the entry in the info panel for that
+ // particular color combination.
+ //
+ // TODO: The error entry in the info panel will only highlight
+ // the first element with that color combination
+ annotate.errorLabel(
+ $(el),
+ contrastRatio,
+ "This contrast is insufficient at this size.",
+ combinations[key]);
+ }
+ }
+
run() {
// Temporary parseColor proxy for FF, which offers "transparent" as a
// default computed backgroundColor instead of `rgba(0, 0, 0, 0)`.
@@ -63,6 +139,14 @@ class ContrastPlugin extends Plugin {
// entry currently present in the info panel
let combinations = {};
+ // [, ]
+ let blindnesses = [
+ [null, "trichromat"],
+ ["protan", "protanopia"],
+ ["deutan", "deuteranopia"],
+ ["tritan", "tritanopia"]
+ ];
+
$("*").each((i, el) => {
// Only check elements with a direct text descendant
if (!axs.properties.hasDirectTextDescendant(el)) {
@@ -83,55 +167,9 @@ class ContrastPlugin extends Plugin {
let style = getComputedStyle(el);
let bgColor = axs.utils.getBgColor(style, el);
let fgColor = axs.utils.getFgColor(style, el, bgColor);
- let contrastRatio = axs.utils.calculateContrastRatio(
- fgColor, bgColor).toFixed(2);
-
- // Calculate required ratio based on size
- // Using strings to prevent rounding
- let requiredRatio = axs.utils.isLargeFont(style) ?
- "3.0" : "4.5";
-
- // Build a key for our `combinations` map and report the color
- // if we have not seen it yet
- let key = axs.utils.colorToString(fgColor) + "/" +
- axs.utils.colorToString(bgColor) + "/" +
- requiredRatio;
-
- if (!axs.utils.isLowContrast(contrastRatio, style)) {
- // For acceptable contrast values, we don't show ratios if
- // they have been presented already
- if (!combinations[key]) {
- annotate
- .label($(el), contrastRatio)
- .addClass("tota11y-label-success");
-
- // Add the key to the combinations map. We don't have an
- // error to associate it with, so we'll just give it the
- // value of `true`.
- combinations[key] = true;
- }
- } else {
- if (!combinations[key]) {
- // We do not show duplicates in the errors panel, however,
- // to keep the output from being overwhelming
- let error = this.addError(
- {fgColor, bgColor, contrastRatio, requiredRatio},
- el);
-
- combinations[key] = error;
- }
-
- // We display errors multiple times for emphasis. Each error
- // will point back to the entry in the info panel for that
- // particular color combination.
- //
- // TODO: The error entry in the info panel will only highlight
- // the first element with that color combination
- annotate.errorLabel(
- $(el),
- contrastRatio,
- "This contrast is insufficient at this size.",
- combinations[key]);
+
+ for (var blindness of blindnesses) {
+ this.runOneType(el, combinations, fgColor, bgColor, style, blindness[0], blindness[1]);
}
});
diff --git a/plugins/contrast/style.less b/plugins/contrast/style.less
index df3673dd..2d8bfb3d 100644
--- a/plugins/contrast/style.less
+++ b/plugins/contrast/style.less
@@ -20,3 +20,55 @@
.tota11y-color-hexes {
font-family: monospace;
}
+
+.tota11y-blindness {
+ width: 11px !important;
+ height: 11px !important;
+ display: inline-block;
+
+ > .tota11y-cone {
+ border-radius: 50%;
+ width: 5px !important;
+ height: 5px !important;
+ position: relative;
+ border: 1px solid rgba(0, 0, 0, 0.5);
+ }
+
+ > .tota11y-short {
+ background-color: #00F;
+ left: 0px;
+ top: 0px;
+ }
+
+ > .tota11y-medium {
+ background-color: #0F0;
+ margin-top: -5px;
+ left: 3px;
+ top: 5px;
+ }
+
+ > .tota11y-long {
+ background-color: #F00;
+ margin-top: -5px;
+ top: 0px;
+ left: 6px;
+ }
+
+ &.tota11y-protanopia {
+ > .tota11y-long {
+ background-color: transparent;
+ }
+ }
+
+ &.tota11y-deuteranopia {
+ > .tota11y-medium {
+ background-color: transparent;
+ }
+ }
+
+ &.tota11y-tritanopia {
+ > .tota11y-short {
+ background-color: transparent;
+ }
+ }
+}
\ No newline at end of file