Code Monkey home page Code Monkey logo

better-color-tools's Introduction

better-color-tools

⚠️ Update Jun 2023

⚠️ This library is no longer maintained. Please use culori’s ESM build which delivers everything this library does, and then some, in the same bundlesize and at 5–10× performance.

Readme

JS and Sass color tools for the Oklab/Oklch colorspace for better color operations.

🏀 Playground

Usage

npm install better-color-tools

This library was created to provide performant and accurate access to the Oklab and Oklch colorspaces at minimum filesize and maximum performance. This is not a comprehensive colorspace tool like Culori or Color.js, rather, better-color-tools seeks to give you the best “bang for the buck” by providing maximum utility with minimal footprint.

To learn more, see Project Goals

JavaScript

Works in the browser (ESM) and Node (14+).

Importing

import better from 'better-color-tools';

Input

// CSS format
better.from('#b3f6e6'); // hex string
better.from('rebeccapurple'); // CSS keyword
better.from('rgb(136, 48, 62)'); // CSS sRGB
better.from('hsl(210, 85%, 37%)'); // CSS HSL
better.from('hwb(210, 6%, 31%)'); // CSS HWB
better.from('oklab(48.56949% -0.03971 -0.14459)'); // CSS Oklab
better.from('oklch(83.11253% 0.22612 147.35276)'); // CSS Oklch

// Other JS formats
better.from(0xb3f6e6); // hex integer (note: only mode that doesn’t support transparency)
better.from([0.533, 0.188, 0.243, 1]); // sRGB array/P3 (normalized to 1)
better.from({ r: 0.533, g: 0.188, b: 0.243, alpha: 1 }); // sRGB/P3 object (normalized to 1)
better.from({ h: 210, s: 0.85, l: 0.37, alpha: 1 }); // HSL object
better.from({ h: 210, w: 0.06, b: 0.31, alpha: 1 }); // HWB object
better.from({ l: 0.4856949, a: -0.03971, b: -0.14459, alpha: 1 }); // Oklab object (not CIELAB)
better.from({ l: 0.8311253, c: 0.22612, h: 147.35276, alpha: 1 }); // Oklch object (not CIELCh)

This library understands any CSS-valid color, including CSS Color Module 4* (but if some aspect isn’t implemented yet, please request it!).

* With the exception that lab() and lch() CSS Module 4 colors will be interpreted as oklab() and oklch(), respectively.

Playground

Output

This library converts to most web-compatible formats¹, with an emphasis on usefulness over completeness:

const c = better.from('rgba(136, 48, 62, 1)');

// CSS formats
c.hex; // #b3f6e6
c.rgb; // rgb(179, 246, 230) (note: will output rgba() if alpha < 1)
c.p3; // color(display-p3 0.701961 0.964706 0.901961)
c.linearRGB; // color(srgb-linear 0.450786 0.921582 0.791298)
c.oklab; // oklab(92.404387% -0.070395 0.002188)
c.oklch; // oklch(92.404387% 0.070429 178.219895)
c.xyz; // color(xyz-d65 0.658257 0.812067 0.870716)

// JS formats
c.hexVal; // 0xb3f6e6
const [r, g, b, alpha] = c.rgbVal; // [0.701961, 0.964706, 0.901961, 1]
const [r, g, b, alpha] = c.p3Val; // [0.701961, 0.964706, 0.901961, 1]
const [lr, lg, lb, alpha] = c.linearRGBVal; // [0.450786, 0.921582, 0.791298, 1]
const [l, a, b, alpha] = c.oklabVal; // [0.924044, -0.070395, 0.002188, 1]
const [l, c, h, alpha] = c.oklchVal; // [0.924044, 0.070429, 178.219895, 1]
const [x, y, z, alpha] = c.xyzVal; // [0.658257, 0.812067, 0.870716, 1]

¹The following formats aren’t supported as outputs:

  • HSL isn’t supported because you shouldn’t use it
  • HWB isn’t supported because it’s another form of HSL
  • CIELAB/CIELCh aren’t supported because Oklab/Oklch are superior

For a comprehensive color conversion library, see Culori.

Adjust

To adjust a color via Oklch, append .adjust() along with the adjustments to make:

better.from('#0060ff').adjust({ lightness: 0.5 }); // set lightness to 50% (absolute)
better.from('#0060ff').adjust({ mode: 'relative', lightness: -0.1 }); // darken lightness by 10%

You can adjust lightness, chroma, and hue altogether, and you can either operate in relative or absolute (default) mode.

Note: adjustments may be chained together, but it’s worth noting that this library will always “snap” to the closest sRGB color with each adjustment. So chaining enough together, you may get different colors purely due to rounding errors. Prefer fewer chained adjustments (or keep the original color around) for best results.

P3

This library supports P3 by expanding the sRGB space into the P3 gamut 1:1. For example, 100% red sRGB is converted to 100% red P3:

const red = '#ff0000';
better.from(red).rgb; // rgb(255, 0, 0)
better.from(red).p3; // color(display-p3 1 0 0)

Playground

This is the practice recommended by WebKit because when dealing with web colors you probably intend to take full advantage of that expanded gamut and this is the easiest, quickest way to do so without dealing with the specifics of both the sRGB and P3 gamuts. This gives you more vibrant colors in supporting browsers without your colors appearing “off.”

While you wouldn’t want to use this technique for other methods such as photo manipulation, for CSS purposes this method is ideal (which better-color-tools assumes you’re using this for). Any deviation between this library’s implementation of P3 from others’ are intentional.

Mix

This uses Oklab (best) by default, but also supports oklch, lms, sRGB, and linearRGB colorspaces for mixing.

better.mix('red', 'lime', 0.35); // Mix `red` and `lime` 35%, i.e. more red
better.mix('blue', 'magenta', 0.5, 'linearRGB'); // Mix `blue` and `magenta` 50% using linearRGB colorspace

Playground

Lighten / Darken

This takes hue and human perception into account for visually-accurate results. Also, fun fact: both functions accept negative values.

better.lighten('red', 0.5); // Lighten color by 50%
better.darken('red', 0.5); // Darken color by 50%

Playground

Lightness

Get the human-perceived lightness of any color (this is an alias for .oklabVal[0])

better.lightness('#fc7030'); // 0.7063999

Playground

Contrast Ratio

Get WCAG 2.1 contrast ratio for 2 colors. The order doesn’t matter.

better.contrastRatio('#37ca93', '#055af6'); // { ratio: 2.4,   AA: false, AAA: false }
better.contrastRatio('#37ca93', '#4474cc'); // { ratio: 4.5,   AA: true,  AAA: false }
better.contrastRatio('#37ca93', '#002c7b'); // { ratio: 12.76, AA: true,  AAA: true }

Light or dark?

Should you overlay white or black text over a color? This will figure out whether a color is perceptually “dark” or “light,” taken directly from Myndex’s “flip for color” technique. You can then use white text for dark colors, and vice-versa.

Note: though it would be reasonable to assume this just checks whether Oklab’s l (lightness) value is over or under 0.5, there’s a bit more to it)

better.lightOrDark('#2d659e'); // "dark" (white text will show better)
better.lightOrDark('#b2d6d3'); // "light" (black text will show better)

Optimizing use in JS Frameworks (React, Svelte, etc.)

For most usecases, this library can be used in any JS framework without the need for memoization or caching.

However, if you need to optimize usage, it’s important to know:

  • better.from() will parse the color each time, and create a new internal cache for all subsequent colorspace operations for that color.
  • Colorspace transforms (e.g. color.oklab) are all getters that only transform the color when requested (so no work is wasted). Further, it uses the internal cache, so subsequent calls are memoized.

In practical terms, optimal usage only requires caching the better.from() call and nothing else. Here are some examples of optimized code with a color prop:

React
import better from 'better-color-tools';
import React, { useMemo } from 'react';

function MyComponent({ color }: { color: string }) {
  const myColor = useMemo(() => better.from(color), [color]);

  return <div>{myColor.oklab}</div>;
}
Svelte
<script type"ts">
  import better from 'better-color-tools';

  export let color: string;

  $: myColor = better.from(color);
</script>

<div>{myColor.oklab}</div>

In both instances, saving the output of better.from() is all that’s needed to preserve the internal cache so that subsequent renders require no work if the base color hasn’t changed. Further optimizations are unnecessary. But again, this library will usually perform faster than most other color transformations anyway, so in most instances, this library will be one of the last things slowing your code down.

Sass

Works with any version of Dart Sass (the current version).

Importing

@use 'better-color-tools' as better;

Mix

.foo {
  color: better.mix('red', 'lime', 0.35);
}

Uses Oklab for best results (which yields much better results than Sass’ mix).

Lighten / Darken

.foo:hover {
  color: better.lighten('blue', 0.2);
  border-color: better.darken('blue', 0.15);
}

Lightens (or darkens) color via Oklab for human-perceived lightness value (which yields much better results than Sass’ lighten/darken:

P3

.foo {
  color: better.p3(#f00); // color(display-p3 1 0 0)
}

Convert any Sass-readable color to P3.

Fallback

.foo {
  @include better.fallback('color', better.p3(#f00), #f00);
  // color: #f00;
  // color: color(display-p3 1 0 0);
}

Mixin for adding CSS fallbacks. Can take infinite arguments. Specify desired format first, followed by fallbacks.

Oklab/Oklch

$oklab: better.rgbToOklab(#f00); // (l: 0.6279554, a: 0.22486, b: 0.12585)
$oklch: better.rgbToOklch(#f00); // (l: 0.6279554, c: 0.25769, h: 29.23389)
$rgb: better.oklabToRGB($oklab); // #f00
$rgb: better.oklchToRGB($oklch); // #f00

Converts any Sass-readable color to an Oklab map of (l: …, a: …, b: …), or Oklch map of (l: …, c: …, h: …). The Sass map can either be used to make a CSS string:

@use 'sass:map';

.foo {
  color: oklab(#{map.get($oklab, 'l')} #{map.get($oklab, 'a')} #{map.get($oklab, 'b')});
  color: better.oklabToRGB($oklab); // fallback
}

Or for color manipulation:

$oklab-lighter: map.set($oklab, 'l', 0.8);

Lightness

Get the human-perceived lightness of any color (identical to the first value of .oklabVal):

$lightness: better.lightness(#f00);

better-color-tools's People

Contributors

drwpow avatar github-actions[bot] avatar piezoid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

better-color-tools's Issues

Inaccurate readme

Readme states:

// Other JS formats
...
better.from({ l: 0.4856949, a: -0.03971, b: -0.14459, alpha: 1 }); // Oklab object (not CIELAB)

However, from() does not accept objects. All color types are either a string or a tuple

export type HSL = [number, number, number, number];
export type HWB = [number, number, number, number];
export type LAB = [number, number, number, number];
export type LCH = [number, number, number, number];
export type LUV = [number, number, number, number];
export type LMS = [number, number, number, number];
export type LinearRGBD65 = [number, number, number, number];
export type Oklab = [number, number, number, number];
export type Oklch = [number, number, number, number];
export type sRGB = [number, number, number, number];
export type XYZ = [number, number, number, number];
export type Color = string | number | sRGB | LinearRGBD65 | LMS | Oklab | Oklch;

and passing an oklch value is interpteted as sRGB.

Goodbye, better-color-tools

👋 After a couple years of working on this library in the background (late 2021), several notable events have happened that don’t make this worth maintaining for me:

oklab() / oklch() landed in all browsers

CleanShot 2023-07-27 at 20 42 29@2x

This is now supported in CSS in all major browsers (~86% as of Jul 2023) 🎉. There’s no JS tool that compete with a built-in CSS feature. Why use an npm package for something the web platform gives you for free?

color-mix() landed AND supports Oklab mixing

Mixing colors through Oklab via color-mix() is now supported in CSS in all major browsers, and is safe to use in web projects today.

Culori also got Oklab support

Culori added Oklab/Oklch support, and if you use the “tree-shakeable” version of colori/fn, you can basically get the footprint of better-color-tools at only a few kB gzip, but with a more stable library and support for more options.

Oh, and did I mention culori/fn is a whopping 5-10× faster than this library for the same package size? 🚀🚀🚀

  culori - test/better.bench.ts > rgb -> hex
    6.69x faster than better

  culori - test/better.bench.ts > rgb -> p3
    4.97x faster than better

  culori - test/better.bench.ts > hsl -> rgb
    8.45x faster than better

  culori - test/better.bench.ts > rgb -> oklab
    6.21x faster than better

  culori - test/better.bench.ts > rgb -> oklch
    5.98x faster than better

  culori - test/better.bench.ts > parse speed: hex string
    10.30x faster than better

Frankly, I don’t have the resources to try and tune better-color-tools to be that fast.


All of the above are developments I have welcomed, and couldn’t be happier for 🙂. But it’s made maintaining this library not worth the effort.

Though I considered the possibility of this library simply wrapping culori/fn, I’m not going to do that. Because creating unnecessary abstractions don’t make a good npm package.

Thanks to all those who tried this library and liked the API. But migrating to culori/fn for all your color needs will not only yield a better experience; you’ll also be contributing to better color science tools. And in the end, that’s what matters. 🎨

Switch to internal value type with P3 support?

Thanks for the awesome library. Having a very pragmatic approach combined with a nice to use API: congrats!

Unfortunately there is one issue I experienced when working with "deeper" colors e.g.

oklch(76.28% 0.18 82.0399)

This is outside of the sRGB spectrum. The library seems fine for a starting point for doing calculations on sRGB values but I wish there would be an option to keep an extended color format internally and only fallback to sRGB if required e.g. by calling the methods on the from return object.

This is a nice color picker showing how it might be done better: https://oklch.com/#76.28,0.18,82.0399,100 - doing all processing on the extended color space and showing a fallback value where needed.

contrast algorithm: APCA

Long term, APCA should probably replace WCAG2 contrast calculation entirely. But with WCAG3 still being a draft (and WCAG2 being enshrined in law in many places), it might be more practical to provide them both for a time.

Issues when converting OKLCH back to RGB

Hi !

First of all, awesome library it does exactly what I need and it's very lightweight.

But I found some issues and results quite different from https://oklch.evilmartians.io/ when using better.from(oklchString).rgb

For example:

Black:

oklch(0% 0.08567819015077803 221.11457081307586/1)

Returns: rgb(0, 0, 3)
Should be: rgb(0, 0, 0) link

oklch(0% 2.248544052636656e-8 89.8755635095349/1)

Returns: rgb(255, 0, 38)
Should be: rgb(0, 0, 0) link

White:

oklch(100% 0.12801375474374593 168.89572110316647)

Returns: rgb(159, 255, 240)
Should be: rgb(255, 255, 255) link

Other:

oklch(85% 0.17463268857418898 54.10799672454288)

Returns: rgb(255, 170, 80)
Should be: rgb(255, 189.4, 146.73) link

Anyway,

Thank you so much for this library, it saved me quite some time !

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.