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
|
||||
|
||||
//primary
|
||||
$primary-very-low: dark-light-diff($primary, $secondary, 97%, -82%) !default;
|
||||
$primary-low: dark-light-diff($primary, $secondary, 90%, -78%) !default;
|
||||
$primary-low-mid: dark-light-diff($primary, $secondary, 70%, -45%) !default;
|
||||
$primary-medium: dark-light-diff($primary, $secondary, 50%, -35%) !default;
|
||||
$primary-high: dark-light-diff($primary, $secondary, 30%, -25%) !default;
|
||||
$primary-very-high: dark-light-diff($primary, $secondary, 15%, -10%) !default;
|
||||
@function _dark-primary-shade($units) {
|
||||
$primary-50-lightness: -72%;
|
||||
$primary-900-lightness: -8%;
|
||||
$step: math.div($primary-50-lightness - $primary-900-lightness, 850);
|
||||
@return $primary-50-lightness - ($step * ($units - 50));
|
||||
}
|
||||
|
||||
//primary-numbers
|
||||
$primary-50: dark-light-diff($primary, $secondary, 97%, -82%) !default;
|
||||
$primary-100: dark-light-diff($primary, $secondary, 94%, -80%) !default;
|
||||
$primary-200: dark-light-diff($primary, $secondary, 90%, -78%) !default;
|
||||
$primary-300: dark-light-diff($primary, $secondary, 80%, -60%) !default;
|
||||
$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;
|
||||
$primary-700: dark-light-diff($primary, $secondary, 38%, -30%) !default;
|
||||
$primary-800: dark-light-diff($primary, $secondary, 30%, -25%) !default;
|
||||
$primary-900: dark-light-diff($primary, $secondary, 15%, -10%) !default;
|
||||
@function _light-primary-shade($units) {
|
||||
$primary-50-lightness: 90%;
|
||||
$primary-900-lightness: 18%;
|
||||
$step: math.div($primary-50-lightness - $primary-900-lightness, 850);
|
||||
@return $primary-50-lightness - ($step * ($units - 50));
|
||||
}
|
||||
|
||||
@function _numbered-primary($units) {
|
||||
@return dark-light-diff(
|
||||
$primary,
|
||||
$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-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:map";
|
||||
@use "./oklab" as oklab;
|
||||
|
||||
$small-width: 800px !default;
|
||||
$medium-width: 995px !default;
|
||||
|
@ -179,11 +181,42 @@ $z-layers: (
|
|||
@if dc-color-brightness($adjusted-color) <
|
||||
dc-color-brightness($comparison-color)
|
||||
{
|
||||
@return scale-color($adjusted-color, $lightness: $lightness);
|
||||
@return oklab-scale-color($adjusted-color, $lightness: $lightness);
|
||||
} @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) {
|
||||
@if is-light-color-scheme() {
|
||||
@return $light-theme-result;
|
||||
|
|
|
@ -76,6 +76,7 @@ module Stylesheet
|
|||
target = nil
|
||||
target_match =
|
||||
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
|
||||
|
||||
{ basename: File.basename(long), target: target, plugin_name: plugin_name }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<section class="color-example">
|
||||
<div class="color-bg {{@color}}"></div>
|
||||
<div class="color-name">var(--{{@color}})</div>
|
||||
<div class="color-name">{{@color}}</div>
|
||||
</section>
|
|
@ -3,8 +3,6 @@
|
|||
<ColorExample @color="primary-very-low" />
|
||||
<ColorExample @color="primary-low" />
|
||||
<ColorExample @color="primary-low-mid" />
|
||||
</section>
|
||||
<section class="color-row">
|
||||
<ColorExample @color="primary-medium" />
|
||||
<ColorExample @color="primary-high" />
|
||||
<ColorExample @color="primary" />
|
||||
|
@ -19,8 +17,6 @@
|
|||
<ColorExample @color="primary-300" />
|
||||
<ColorExample @color="primary-400" />
|
||||
<ColorExample @color="primary-500" />
|
||||
</section>
|
||||
<section class="color-row">
|
||||
<ColorExample @color="primary-600" />
|
||||
<ColorExample @color="primary-700" />
|
||||
<ColorExample @color="primary-800" />
|
||||
|
@ -55,8 +51,6 @@
|
|||
<ColorExample @color="tertiary-300" />
|
||||
<ColorExample @color="tertiary-400" />
|
||||
<ColorExample @color="tertiary-500" />
|
||||
</section>
|
||||
<section class="color-row">
|
||||
<ColorExample @color="tertiary-600" />
|
||||
<ColorExample @color="tertiary-700" />
|
||||
<ColorExample @color="tertiary-800" />
|
||||
|
|
|
@ -128,26 +128,27 @@
|
|||
}
|
||||
.color-row {
|
||||
display: flex;
|
||||
border: 1px solid black;
|
||||
|
||||
.color-example {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 120px;
|
||||
margin: 0.5em 0.5em 0.5em 0;
|
||||
border: 1px solid var(--primary-300);
|
||||
// margin: 0.5em 0.5em 0.5em 0;
|
||||
// border: 1px solid black;
|
||||
|
||||
.color-bg {
|
||||
flex: 4;
|
||||
border-bottom: 1px solid var(--primary-300);
|
||||
// border-bottom: 1px solid var(--primary-300);
|
||||
}
|
||||
.color-name {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.25em 0.5em;
|
||||
font-weight: 700;
|
||||
font-size: var(--font-down-1);
|
||||
// padding: 0.25em 0.5em;
|
||||
// font-weight: 700;
|
||||
font-size: var(--font-down-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue