-
Notifications
You must be signed in to change notification settings - Fork 8
/
a11y-color.scss
244 lines (228 loc) · 8 KB
/
a11y-color.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// @function strip-unit($value)
// @param {number with unit} $value
@function strip-unit($value) {
@return ($value / ($value * 0 + 1));
}
// @function pow($base, $exponents)
// Helper: Raise a number the power of another number
// Adapted from: https://gist.github.com/hail2u/1964056
//
// @param {number} $base — The base number
// @param {number} $exponents — The exponent to which to raise $base
@function pow($base, $exponents) {
$raised: 1;
@for $i from 1 through $exponents {
$raised: $raised * $base;
}
@return $raised;
}
// @function luminance($color)
// Helper: Calculate Luminance of a single color
// Adapted from: https://medium.com/dev-channel/using-sass-to-automatically-pick-text-colors-4ba7645d2796
// White luminance is 1, Black luminance is 0
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { luminance: luminance(#c00); }
// Output:
// .sample { luminance: 0.1283736922; }
//
@function luminance($color) {
// Thanks voxpelli for a very concise implementation of luminance measure in sass
// Adapted from: https://gist.github.com/voxpelli/6304812
$rgba: red($color), green($color), blue($color);
$rgba2: ();
@for $i from 1 through 3 {
$rgb: nth($rgba, $i);
$rgb: $rgb / 255;
$rgb: if($rgb < .03928, $rgb / 12.92, pow(($rgb + .055) / 1.055, 2));
$rgba2: append($rgba2, $rgb);
}
@return (.2126 * nth($rgba2, 1) + .7152 * nth($rgba2, 2) + 0.0722 * nth($rgba2, 3))*100;
}
// @function contrast-ratio($fg, $bg)
// Helper: Calculate "readability" as defined by WCAG 2.1
// Adapted from: https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/color.js
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { color-contrast: contrast-ratio(#c00, #fff); }
// Output:
// .sample { color-contrast: 5.89; }
//
@function contrast-ratio($fg, $bg) {
$luminance1: luminance($fg) + 0.05;
$luminance2: luminance($bg) + 0.05;
$ratio: $luminance1 / $luminance2;
@if $luminance2 > $luminance1 {
$ratio: 1 / $ratio;
}
// Round to a hundreth because 6.96 should not pass a ratio of 7.0
$ratio: round($ratio * 100) / 100;
@return $ratio;
}
// @function validate-font-size($size)
// Helper: Depending on the unit recalculate a font size value into pixels if possible
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { validate-font-size: validate-font-size(1em); }
// Output:
// .sample { validate-font-size: 16; }
//
@function validate-font-size($size) {
@if unit($size) == 'em' or unit($size) == 'rem' or unit($size) == 'px' or unit($size) == '' {
// Check if a flexible unit
@if unit($size) == 'em' or unit($size) == 'rem' {
// Need to convert to a pixel value. Let's not overcomplicate it with possible EM inheritence scale factors
@return strip-unit($size * 16)
}
@if unit($size) == 'px' {
// We expect PX, so strip the value and return it
@return strip-unit($size);
}
@if unit($size) == '' {
@return $size;
}
} @else {
@error 'validate-font-size(): An unexpected font size unit was supplied.';
}
}
// @function get-ratio($level: 'AA', $size: 16, $bold: false)
// Helper: Determine the correct ratio value to use based on font-size and WCAG Level
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { get-ratio: get-ratio('AAA', 19, true); }
// Output:
// .sample { get-ratio: 4.5; }
//
@function get-ratio($level: 'AA', $size: 16, $bold: false) {
// Default ratio
$ratio: 4.5;
@if $level == 'AAA' {
$ratio: 7;
}
// Make sure the size is valid. If the value is not EM, REM, or PX (preferred), we can't help
$size: validate-font-size($size);
// Check font size
@if $size < 24 {
// Small text, use defaults
// But:
@if $size >= 19 and $bold == true {
// Special case: Small text but also bold
@if $level == 'AAA' {
$ratio: 4.5;
} @else {
$ratio: 3;
}
}
} @else {
// Larger than 24
$ratio: 3;
@if $level == 'AAA' {
$ratio: 4.5;
}
}
@return $ratio;
}
// @function light-or-dark($color)
// Helper: Use contrast against white or black to determine if a color is "light" or "dark"
// Adapted from: https://medium.com/dev-channel/using-sass-to-automatically-pick-text-colors-4ba7645d2796
// To be used in other functions or mixins — creates non-standard CSS output:
// Usage:
// .sample { light-or-dark: light-or-dark(#c00); }
// Output:
// .sample { light-or-dark: "light"; }
//
@function light-or-dark($color) {
$light-contrast: contrast-ratio($color, $white);
$dark-contrast: contrast-ratio($color, $black);
@if $light-contrast > $dark-contrast {
// Contrast against white is higher than against black, so, this is a dark color
@return "dark";
} @else {
@return "light";
}
}
// @function color-contrast($color)
// Helper: Returns black or white compared to a color, whichever produces the highest contrast
// Usage:
// .sample {
// background-color: #c00;
// color: color-contrast(#c00);
// }
// Output:
// .sample {
// background-color: #c00;
// color: #fff;
// }
//
// Named for the forthcoming CSS Module 5 spec function color-contrast();
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-contrast()
//
@function color-contrast($color) {
$color-lod: light-or-dark($color);
@if ($color-lod == "dark") {
@return $white;
} @else {
@return $black;
}
}
// @function a11y-color($fg, $bg, $level: 'AA', $size: 16, $bold: false)
// Goal: Return a color that passes for the chosen WCAG level without changing the Hue of the color
// Usage:
// .sample {
// background-color: #000;
// color: a11y-color(#c0c, #000);
// }
// Output:
// .sample {
// background-color: #000;
// color: #d200d2;
// }
//
@function a11y-color($fg, $bg, $level: 'AA', $size: 16, $bold: false) {
// Helper: make sure the font size value is acceptable
$font-size: validate-font-size($size);
// Helper: With the level, font size, and bold boolean, return the proper target ratio. 3.0, 4.5, or 7.0 expected
$ratio: get-ratio($level, $font-size, $bold);
// Calculate the first contrast ratio of the given pair
$original-contrast: contrast-ratio($fg, $bg);
@if $original-contrast >= $ratio {
// If we pass the ratio already, return the original color
@return $fg;
} @else {
// Doesn't pass. Time to get to work
// Should the color be lightened or darkened?
$fg-lod: light-or-dark($fg);
$bg-lod: light-or-dark($bg);
// Set a "step" value to lighten or darken a color
// Note: Higher percentage steps means faster compile time, but we might overstep the required threshold too far with something higher than 4%
$step: 1%;
// Run through some cases where we want to darken, or use a negative step value
@if $fg-lod == 'light' and $bg-lod == 'light' {
// Both are light colors, darken the fg
$step: - $step;
} @else if $fg-lod == 'dark' and $bg-lod == 'light' {
// bg is light, fg is dark but does not pass, darken more
$step: - $step;
}
// Keeping the rest of the logic here, but our default values do not change, so this logic is not needed
//@else if $fg-lod == 'light' and $bg-lod == 'dark' {
// // bg is dark, fg is light but does not pass, lighten further
// $step: $step;
//} @else if $fg-lod == 'dark' and $bg-lod == 'dark' {
// // Both are dark, so lighten the fg
// $step: $step;
//}
// The magic happens here
// Loop through with a @while statement until the color combination passes our required ratio. Scale the color by our step value until the expression is false
// This might loop 100 times or more depending on the colors
@while contrast-ratio($fg, $bg) < $ratio {
$sat-step: 0%;
@if saturation($fg) > 10 {
$sat-step: $step;
}
$fg: scale-color($fg, $lightness: $step, $saturation:$sat-step);
}
@return $fg;
}
}