<header>
<h1>Gradients</h1>
<p>In modern color spaces</p>
</header>
<form>
<fieldset>
<legend>Colors</legend>
<input type="color" id="color1" value="#00ff00">
<input type="color" id="color2" value="#0000FF">
</fieldset>
<fieldset>
<legend>Steps</legend>
<input type="range" id="quantize" min="1" max="100" value="10" />
</fieldset>
<fieldset>
<legend>Hue Interpolation</legend>
<label for="showInterpolations">Show shorter, longer, etc<br>
<small>
Only applies to cylindrical color spaces.<br>
<code>shorter</code> is the default.</small></label>
<input type="checkbox" id="showInterpolations">
</fieldset>
</form>
<main id="app"></main>
@import "https://unpkg.com/open-props" layer(design.system);
@import "https://unpkg.com/open-props/normalize.min.css" layer(demo.support);
@import "https://unpkg.com/open-props/buttons.min.css" layer(demo.support);
@layer demo {
main {
display: grid;
gap: var(--size-5);
inline-size: clamp(90%, calc(var(--size-content-3) * 1.5), 90vw);
}
:has(#showInterpolations:not(:checked)) main {
gap: 0;
}
fieldset {
display: flex;
align-items: center;
gap: var(--size-3);
}
section {
display: grid;
grid-auto-flow: column;
grid-template-columns: 20ch 1fr;
align-items: center;
gap: 2px var(--size-3);
}
@media (width < 500px) {
section {
grid-template-columns: 1fr;
}
section > h3 {
grid-column: 1 / span 2;
}
section > .ramp {
grid-column: -1/1;
}
}
section > h3 {
font-size: var(--font-size-4);
text-transform: uppercase;
}
.cylindrical > h3 {
grid-column: 1 / span 2;
}
section > p {
grid-column: 1;
}
.ramp {
grid-column: 2;
display: flex;
block-size: 5vh;
}
.swatch {
flex: 1;
}
form {
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
margin-block: var(--size-3) var(--size-8);
justify-content: center;
}
header {
text-align: center;
}
}
@layer demo.support {
body {
display: grid;
place-content: center;
padding: var(--size-5);
gap: var(--size-5);
justify-items: center;
}
}
import Color from 'https://colorjs.io/dist/color.js'
const state = {
quantize: quantize.value,
showInterpolations: showInterpolations.checked,
color: [color1.value, color2.value]
}
function updateUI() {
const color1 = new Color(state.color[0])
const color2 = new Color(state.color[1])
// todo: linear gradients between steps?
const spaces = ['srgb', 'srgb-linear', 'rec2020', 'p3', 'a98rgb', 'prophoto', 'xyz', 'lab', 'oklab', 'hsl', 'hwb', 'lch', 'oklch']
const cylindrical = ['hsl', 'hwb', 'lch', 'oklch']
const interpolations = ['shorter', 'longer', 'increasing', 'decreasing']
let mix = null
let rows = ''
for (let space of spaces) {
rows += `
<section ${cylindrical.includes(space) && state.showInterpolations && `class="cylindrical"`}>
<h3>${space}</h3>`
if (cylindrical.includes(space) && state.showInterpolations) {
for (let hue of interpolations) {
mix = color1.steps(color2, {
hue,
space,
steps: state.quantize,
outputSpace: 'srgb',
})
rows += `
<p>${hue}</p>
<div class="ramp">
${mix.reduce((acc, item) => {
return acc += `<div class="swatch" style="background: ${item}"></div>`
}, '')}
</div>
`
}
}
else {
mix = color1.steps(color2, {
space,
steps: state.quantize,
outputSpace: 'srgb',
})
rows += `
<div class="ramp">
${mix.reduce((acc, item) => {
return acc += `<div class="swatch" style="background: ${item}"></div>`
}, '')}
</div>
`
}
rows += `</section>`
}
app.innerHTML = rows
}
color1.oninput = e => {
state.color[0] = e.target.value
updateUI()
}
color2.oninput = e => {
state.color[1] = e.target.value
updateUI()
}
quantize.oninput = e => {
state.quantize = parseInt(e.target.value)
updateUI()
}
showInterpolations.onchange = e => {
state.showInterpolations = e.target.checked
updateUI()
}
updateUI()
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.