color

tosijs includes a lightweight, powerful Color class for manipulating colors. I hope at some point CSS will provide sufficiently capable native color calculations so that this will no longer be needed. Some of these methods have begun to appear, and are approaching wide implementation.

Color

The most straightforward methods for creating a Color instance are to use the Color() constructor to create an rgb or rgba representation, or using the Color.fromCss() to create a Color from any CSS (s)rgb representation, e.g.

new Color(255, 255, 0)               // yellow
new Color(0, 128, 0, 0.5)            // translucent dark green
Color.fromCss('#000')                // black
Color.fromCss('hsl(90deg 100% 50%))  // orange
Color.fromCss('color(srgb 1 0 0.5))  // purple

Note that Color.fromCss() is not compatible with non-srgb color spaces. The new CSS color functions produce color specifications of the form color(<space> ....) and Color.fromCSS() will handle color(srgb ...) correctly (this is so it can parse the output of color-mix(in hsl ...) but not other color spaces.

Manipulating Colors

import { elements, Color } from 'tosijs'

const { label, span, div, input, button } = elements

const swatches = div({ class: 'swatches' })
function makeSwatch(text) {
  const color = Color.fromCss(colorInput.value)
  const adjustedColor = eval('color.' + text)
  swatches.style.setProperty('--original', color)
  swatches.append(
    div(
      text,
      {
        class: 'swatch',
        title: `${adjustedColor.html} ${adjustedColor.hsla}`,
        style: {
          _adjusted: adjustedColor,
          _text: adjustedColor.contrasting()
        }
      }
    )
  )
}

const colorInput = input({
  type: 'color',
  value: '#000',
  onInput: update
})
const red = Color.fromCss('#f00')
const gray = Color.fromCss('#888')
const teal = Color.fromCss('teal')
const aliceblue = Color.fromCss('aliceblue')

function update() {
  swatches.textContent = ''
  makeSwatch('brighten(-0.5)')
  makeSwatch('brighten(0.5)')
  makeSwatch('saturate(0.25)')
  makeSwatch('saturate(0.5)')
  makeSwatch('desaturate(0.5)')
  makeSwatch('desaturate(0.75)')
  makeSwatch('contrasting()')
  makeSwatch('contrasting(0.05)')
  makeSwatch('contrasting(0.25)')
  makeSwatch('contrasting(0.45)')
  makeSwatch('inverseLuminance')
  makeSwatch('mono')
  makeSwatch('rotate(-330)')
  makeSwatch('rotate(60)')
  makeSwatch('rotate(-270)')
  makeSwatch('rotate(120)')
  makeSwatch('rotate(-210)')
  makeSwatch('rotate(180)')
  makeSwatch('rotate(-150)')
  makeSwatch('rotate(240)')
  makeSwatch('rotate(-90)')
  makeSwatch('rotate(300)')
  makeSwatch('rotate(-30)')
  makeSwatch('opacity(0.1)')
  makeSwatch('opacity(0.5)')
  makeSwatch('opacity(0.75)')
  makeSwatch('rotate(-90).opacity(0.75)')
  makeSwatch('brighten(0.5).desaturate(0.5)')
  makeSwatch('blend(Color.black, 0.5)')
  makeSwatch('mix(Color.white, 0.4)')
  makeSwatch('blend(gray, 0.4)')
  makeSwatch('mix(red, 0.25)')
  makeSwatch('mix(red, 0.5)')
  makeSwatch('mix(red, 0.75)')
  makeSwatch('mix(teal, 0.25)')
  makeSwatch('mix(teal, 0.5)')
  makeSwatch('mix(teal, 0.75)')
  makeSwatch('colorMix(aliceblue, 0.25)')
  makeSwatch('colorMix(aliceblue, 0.5)')
  makeSwatch('colorMix(aliceblue, 0.75)')
}

function randomColor() {
  colorInput.value = Color.fromHsl(Math.random() * 360, Math.random(), Math.random() * 0.5 + 0.25)
  update()
}

randomColor()

preview.append(
  label(
    span('base color'),
    colorInput
  ),
  button(
    'Random(ish) Color',
    {
      onClick: randomColor
    }
  ),
  swatches
)
.preview .swatches {
  display: flex;
  gap: 4px;
  padding: 4px;
  flex-wrap: wrap;
  font-size: 80%;
}
.preview .swatch {
  display: inline-block;
  padding: 2px 6px;
  color: var(--text);
  background: var(--adjusted);
  border: 2px solid var(--original);
}

Each of these methods creates a new color instance based on the existing color(s).

In each case amount is from 0 to 1, and degrees is an angle in degrees.

Note the captions in the example above are colored using contrasting() and thus should always be readable. In general, a base color will produce the worst results when its brightness is around 0.5, much as is the case with the new and experimental CSS contrast-color() function.

Also note that highly translucent colors might produce disappointing .contrasting() results since it's the blended color you need to worry about.

Where-ever possible, unless otherwise indicated, all of these operations are performed in HSL-space. HSL space is not great! For example, desaturate essentially blends you with medium gray (#888) rather than a BT.601 brightness value where "yellow" is really bright and "blue" is really dark.

If you want to desaturate colors more nicely, you can try blending them with their own mono.

Static Methods

These are alternatives to the standard Color(r, g, b, a = 1) constructor.

Color.fromVar(cssVariableName: string, element = document.body): Color evaluates the color at the specified element and then returns a Color instance with that value. It will accept both bare variable names (--foo-bar) and wrapped (var(--foo-bar)).

Color.fromCss(cssColor: string): Color produces a Color instance from any css color definition the browser can handle.

Color.fromHsl(h: number, s: number, l: number, a = 1) produces a Color instance from HSL/HSLA values. The HSL values are cached internally and used for internal calculations to reduce precision problems that occur when converting HSL to RGB and back. It's nowhere near as sophisticated as the models used by (say) Adobe or Apple, but it's less bad than doing all computations in rgb.

Static Properties

Properties

Properties (read-only)

Utilities