UX: Use oklab color space for perceptually-uniform lightness
Oklab is supported natively in the latest versions of all major browsers. We'll need to wait until we drop support for Safari 14 before using it directly, but we can use it for sass color transformations right away. Its perceptually-uniform lighness makes it significantly better than hsl for our automatic light/dark variations of colors.
This commit is contained in:
parent
56795f5c07
commit
67c68c9c60
|
@ -3,24 +3,46 @@
|
||||||
// all variables should have the !default flag
|
// all variables should have the !default flag
|
||||||
|
|
||||||
//primary
|
//primary
|
||||||
$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%) !default;
|
@function _dark-primary-shade($units) {
|
||||||
$primary-low: dark-light-diff($primary, $secondary, 90%, -78%) !default;
|
$primary-50-lightness: -72%;
|
||||||
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%) !default;
|
$primary-900-lightness: -8%;
|
||||||
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%) !default;
|
$step: math.div($primary-50-lightness - $primary-900-lightness, 850);
|
||||||
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%) !default;
|
@return $primary-50-lightness - ($step * ($units - 50));
|
||||||
$primary-very-high: dark-light-diff($primary, $secondary, 15%, -10%) !default;
|
}
|
||||||
|
|
||||||
//primary-numbers
|
@function _light-primary-shade($units) {
|
||||||
$primary-50: dark-light-diff($primary, $secondary, 97%, -82%) !default;
|
$primary-50-lightness: 90%;
|
||||||
$primary-100: dark-light-diff($primary, $secondary, 94%, -80%) !default;
|
$primary-900-lightness: 18%;
|
||||||
$primary-200: dark-light-diff($primary, $secondary, 90%, -78%) !default;
|
$step: math.div($primary-50-lightness - $primary-900-lightness, 850);
|
||||||
$primary-300: dark-light-diff($primary, $secondary, 80%, -60%) !default;
|
@return $primary-50-lightness - ($step * ($units - 50));
|
||||||
$primary-400: dark-light-diff($primary, $secondary, 70%, -45%) !default;
|
}
|
||||||
$primary-500: dark-light-diff($primary, $secondary, 60%, -40%) !default;
|
|
||||||
$primary-600: dark-light-diff($primary, $secondary, 50%, -35%) !default;
|
@function _numbered-primary($units) {
|
||||||
$primary-700: dark-light-diff($primary, $secondary, 38%, -30%) !default;
|
@return dark-light-diff(
|
||||||
$primary-800: dark-light-diff($primary, $secondary, 30%, -25%) !default;
|
$primary,
|
||||||
$primary-900: dark-light-diff($primary, $secondary, 15%, -10%) !default;
|
$secondary,
|
||||||
|
_light-primary-shade($units),
|
||||||
|
_dark-primary-shade($units)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$primary-50: _numbered-primary(50) !default;
|
||||||
|
$primary-100: _numbered-primary(100) !default;
|
||||||
|
$primary-200: _numbered-primary(200) !default;
|
||||||
|
$primary-300: _numbered-primary(300) !default;
|
||||||
|
$primary-400: _numbered-primary(400) !default;
|
||||||
|
$primary-500: _numbered-primary(500) !default;
|
||||||
|
$primary-600: _numbered-primary(600) !default;
|
||||||
|
$primary-700: _numbered-primary(700) !default;
|
||||||
|
$primary-800: _numbered-primary(800) !default;
|
||||||
|
$primary-900: _numbered-primary(900) !default;
|
||||||
|
|
||||||
|
$primary-very-low: $primary-50 !default;
|
||||||
|
$primary-low: $primary-200 !default;
|
||||||
|
$primary-low-mid: $primary-400 !default;
|
||||||
|
$primary-medium: $primary-600 !default;
|
||||||
|
$primary-high: $primary-800 !default;
|
||||||
|
$primary-very-high: $primary-900 !default;
|
||||||
|
|
||||||
$header_primary-low: blend-header-primary-background(10%) !default;
|
$header_primary-low: blend-header-primary-background(10%) !default;
|
||||||
$header_primary-low-mid: blend-header-primary-background(35%) !default;
|
$header_primary-low-mid: blend-header-primary-background(35%) !default;
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
@use "sass:math";
|
||||||
|
@use "sass:color";
|
||||||
|
@use "sass:map";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Adapted from https://bottosson.github.io/posts/colorwrong and https://bottosson.github.io/posts/oklab/
|
||||||
|
(public domain)
|
||||||
|
*/
|
||||||
|
|
||||||
|
@function component-to-nonninear($x) {
|
||||||
|
@if $x >= 0.0031308 {
|
||||||
|
@return (1.055) * math.pow($x, math.div(1, 2.4)) - 0.055;
|
||||||
|
} @else {
|
||||||
|
@return 12.92 * $x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@function component-to-linear($x) {
|
||||||
|
@if ($x >= 0.04045) {
|
||||||
|
@return math.pow(math.div($x + 0.055, 1 + 0.055), 2.4);
|
||||||
|
} @else {
|
||||||
|
@return math.div($x, 12.92);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@function rgb_to_oklab($color) {
|
||||||
|
$r: component-to-linear(math.div(color.red($color), 255));
|
||||||
|
$g: component-to-linear(math.div(color.green($color), 255));
|
||||||
|
$b: component-to-linear(math.div(color.blue($color), 255));
|
||||||
|
|
||||||
|
$l: 0.4122214708 * $r + 0.5363325363 * $g + 0.0514459929 * $b;
|
||||||
|
$m: 0.2119034982 * $r + 0.6806995451 * $g + 0.1073969566 * $b;
|
||||||
|
$s: 0.0883024619 * $r + 0.2817188376 * $g + 0.6299787005 * $b;
|
||||||
|
|
||||||
|
$l_: math.pow($l, math.div(1, 3));
|
||||||
|
$m_: math.pow($m, math.div(1, 3));
|
||||||
|
$s_: math.pow($s, math.div(1, 3));
|
||||||
|
|
||||||
|
@return (
|
||||||
|
"L": 0.2104542553 * $l_ + 0.793617785 * $m_ - 0.0040720468 * $s_,
|
||||||
|
"a": 1.9779984951 * $l_ - 2.428592205 * $m_ + 0.4505937099 * $s_,
|
||||||
|
"b": 0.0259040371 * $l_ + 0.7827717662 * $m_ - 0.808675766 * $s_
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@function oklab_to_rgb($oklab) {
|
||||||
|
$L: map.get($oklab, "L");
|
||||||
|
$a: map.get($oklab, "a");
|
||||||
|
$b: map.get($oklab, "b");
|
||||||
|
|
||||||
|
$l_: $L + 0.3963377774 * $a + 0.2158037573 * $b;
|
||||||
|
$m_: $L - 0.1055613458 * $a - 0.0638541728 * $b;
|
||||||
|
$s_: $L - 0.0894841775 * $a - 1.291485548 * $b;
|
||||||
|
|
||||||
|
$l: math.pow($l_, 3);
|
||||||
|
$m: math.pow($m_, 3);
|
||||||
|
$s: math.pow($s_, 3);
|
||||||
|
|
||||||
|
$linear_r: +4.0767416621 * $l - 3.3077115913 * $m + 0.2309699292 * $s;
|
||||||
|
$linear_g: -1.2684380046 * $l + 2.6097574011 * $m - 0.3413193965 * $s;
|
||||||
|
$linear_b: -0.0041960863 * $l - 0.7034186147 * $m + 1.707614701 * $s;
|
||||||
|
|
||||||
|
$r: component-to-nonninear($linear_r);
|
||||||
|
$g: component-to-nonninear($linear_g);
|
||||||
|
$b: component-to-nonninear($linear_b);
|
||||||
|
|
||||||
|
@return rgb($r * 255, $g * 255, $b * 255);
|
||||||
|
}
|
|
@ -7,6 +7,8 @@
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
@use "sass:math";
|
@use "sass:math";
|
||||||
|
@use "sass:map";
|
||||||
|
@use "./oklab" as oklab;
|
||||||
|
|
||||||
$small-width: 800px !default;
|
$small-width: 800px !default;
|
||||||
$medium-width: 995px !default;
|
$medium-width: 995px !default;
|
||||||
|
@ -179,11 +181,42 @@ $z-layers: (
|
||||||
@if dc-color-brightness($adjusted-color) <
|
@if dc-color-brightness($adjusted-color) <
|
||||||
dc-color-brightness($comparison-color)
|
dc-color-brightness($comparison-color)
|
||||||
{
|
{
|
||||||
@return scale-color($adjusted-color, $lightness: $lightness);
|
@return oklab-scale-color($adjusted-color, $lightness: $lightness);
|
||||||
} @else {
|
} @else {
|
||||||
@return scale-color($adjusted-color, $lightness: $darkness);
|
@return oklab-scale-color($adjusted-color, $lightness: $darkness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Scale a colour's luminance using the oklab color space. Eventually, this could be implemented
|
||||||
|
in the browser using css Relative Color Syntax. In native CSS, this function is equivelant to:
|
||||||
|
|
||||||
|
```css
|
||||||
|
// For lightness > 0
|
||||||
|
oklab(from var(--color) calc(L + ((1-L) * var(--lightness))) a b)
|
||||||
|
// For lightness < 0
|
||||||
|
oklab(from var(--color) calc(L + (L * var(--lightness)) a b)
|
||||||
|
```
|
||||||
|
**/
|
||||||
|
@function oklab-scale-color($color, $lightness: 0) {
|
||||||
|
$current-oklab: oklab.rgb_to_oklab($color);
|
||||||
|
$current-lightness: map.get($current-oklab, "L");
|
||||||
|
|
||||||
|
$new-lightness: null;
|
||||||
|
@if ($lightness > 0) {
|
||||||
|
$new-lightness: $current-lightness +
|
||||||
|
((1 - $current-lightness) * math.div($lightness, 100%));
|
||||||
|
} @else {
|
||||||
|
$new-lightness: $current-lightness +
|
||||||
|
($current-lightness * math.div($lightness, 100%));
|
||||||
|
}
|
||||||
|
|
||||||
|
$transformed-oklab: map.set($current-oklab, "L", $new-lightness);
|
||||||
|
$transformed-rgb: oklab.oklab-to-rgb($transformed-oklab);
|
||||||
|
|
||||||
|
@return $transformed-rgb;
|
||||||
|
}
|
||||||
|
|
||||||
@function dark-light-choose($light-theme-result, $dark-theme-result) {
|
@function dark-light-choose($light-theme-result, $dark-theme-result) {
|
||||||
@if is-light-color-scheme() {
|
@if is-light-color-scheme() {
|
||||||
@return $light-theme-result;
|
@return $light-theme-result;
|
||||||
|
|
|
@ -76,6 +76,7 @@ module Stylesheet
|
||||||
target = nil
|
target = nil
|
||||||
target_match =
|
target_match =
|
||||||
long.match(/admin|desktop|mobile|publish|wizard|wcag|color_definitions/)
|
long.match(/admin|desktop|mobile|publish|wizard|wcag|color_definitions/)
|
||||||
|
target_match ||= "color_definitions" if long.match(/color_transformations/)
|
||||||
target = target_match[0] if target_match&.length
|
target = target_match[0] if target_match&.length
|
||||||
|
|
||||||
{ basename: File.basename(long), target: target, plugin_name: plugin_name }
|
{ basename: File.basename(long), target: target, plugin_name: plugin_name }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<section class="color-example">
|
<section class="color-example">
|
||||||
<div class="color-bg {{@color}}"></div>
|
<div class="color-bg {{@color}}"></div>
|
||||||
<div class="color-name">var(--{{@color}})</div>
|
<div class="color-name">{{@color}}</div>
|
||||||
</section>
|
</section>
|
|
@ -3,8 +3,6 @@
|
||||||
<ColorExample @color="primary-very-low" />
|
<ColorExample @color="primary-very-low" />
|
||||||
<ColorExample @color="primary-low" />
|
<ColorExample @color="primary-low" />
|
||||||
<ColorExample @color="primary-low-mid" />
|
<ColorExample @color="primary-low-mid" />
|
||||||
</section>
|
|
||||||
<section class="color-row">
|
|
||||||
<ColorExample @color="primary-medium" />
|
<ColorExample @color="primary-medium" />
|
||||||
<ColorExample @color="primary-high" />
|
<ColorExample @color="primary-high" />
|
||||||
<ColorExample @color="primary" />
|
<ColorExample @color="primary" />
|
||||||
|
@ -19,8 +17,6 @@
|
||||||
<ColorExample @color="primary-300" />
|
<ColorExample @color="primary-300" />
|
||||||
<ColorExample @color="primary-400" />
|
<ColorExample @color="primary-400" />
|
||||||
<ColorExample @color="primary-500" />
|
<ColorExample @color="primary-500" />
|
||||||
</section>
|
|
||||||
<section class="color-row">
|
|
||||||
<ColorExample @color="primary-600" />
|
<ColorExample @color="primary-600" />
|
||||||
<ColorExample @color="primary-700" />
|
<ColorExample @color="primary-700" />
|
||||||
<ColorExample @color="primary-800" />
|
<ColorExample @color="primary-800" />
|
||||||
|
@ -55,8 +51,6 @@
|
||||||
<ColorExample @color="tertiary-300" />
|
<ColorExample @color="tertiary-300" />
|
||||||
<ColorExample @color="tertiary-400" />
|
<ColorExample @color="tertiary-400" />
|
||||||
<ColorExample @color="tertiary-500" />
|
<ColorExample @color="tertiary-500" />
|
||||||
</section>
|
|
||||||
<section class="color-row">
|
|
||||||
<ColorExample @color="tertiary-600" />
|
<ColorExample @color="tertiary-600" />
|
||||||
<ColorExample @color="tertiary-700" />
|
<ColorExample @color="tertiary-700" />
|
||||||
<ColorExample @color="tertiary-800" />
|
<ColorExample @color="tertiary-800" />
|
||||||
|
|
|
@ -128,26 +128,27 @@
|
||||||
}
|
}
|
||||||
.color-row {
|
.color-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
border: 1px solid black;
|
||||||
|
|
||||||
.color-example {
|
.color-example {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
margin: 0.5em 0.5em 0.5em 0;
|
// margin: 0.5em 0.5em 0.5em 0;
|
||||||
border: 1px solid var(--primary-300);
|
// border: 1px solid black;
|
||||||
|
|
||||||
.color-bg {
|
.color-bg {
|
||||||
flex: 4;
|
flex: 4;
|
||||||
border-bottom: 1px solid var(--primary-300);
|
// border-bottom: 1px solid var(--primary-300);
|
||||||
}
|
}
|
||||||
.color-name {
|
.color-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.25em 0.5em;
|
// padding: 0.25em 0.5em;
|
||||||
font-weight: 700;
|
// font-weight: 700;
|
||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue