Closed Mischback closed 1 year ago
Providing sliders
- or inputs of type range
- is a convenient way to adjust the colors, especially for the hue
components of HSL
, HWB
and OkLCH
.
Providing a meaningful interface to these sliders is not that easy and requires lots of code, in JS aswell as CSS.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Colorizer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="assets/style.css">
<script src="assets/colorizer.js" defer></script>
<style>
#this-hue-container {
--this-hue: 0;
--this-sat: 100%;
--this-light: 50%;
}
.hue {
--slider-track-height: 1em;
--slider-thumb-height: 1.5em;
--slider-thumb-width: 1em;
width: 25%;
padding: 2rem;
}
.hue input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
.hue input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: var(--slider-track-height);
cursor: pointer;
border-radius: 2px;
border: 1px solid #000;
background: linear-gradient(90deg,
hsl(0 var(--this-sat) var(--this-light)) 0%,
hsl(calc(0.1 * 360) var(--this-sat) var(--this-light)) 10%,
hsl(calc(0.2 * 360) var(--this-sat) var(--this-light)) 20%,
hsl(calc(0.3 * 360) var(--this-sat) var(--this-light)) 30%,
hsl(calc(0.4 * 360) var(--this-sat) var(--this-light)) 40%,
hsl(calc(0.5 * 360) var(--this-sat) var(--this-light)) 50%,
hsl(calc(0.6 * 360) var(--this-sat) var(--this-light)) 60%,
hsl(calc(0.7 * 360) var(--this-sat) var(--this-light)) 70%,
hsl(calc(0.8 * 360) var(--this-sat) var(--this-light)) 80%,
hsl(calc(0.9 * 360) var(--this-sat) var(--this-light)) 90%,
hsl(360 var(--this-sat) var(--this-light)) 100%);
}
.hue input[type=range]::-moz-range-track {
width: 100%;
height: var(--slider-track-height);
cursor: pointer;
border-radius: 2px;
border: 1px solid #000;
background: linear-gradient(90deg,
hsl(0 var(--this-sat) var(--this-light)) 0%,
hsl(calc(0.1 * 360) var(--this-sat) var(--this-light)) 10%,
hsl(calc(0.2 * 360) var(--this-sat) var(--this-light)) 20%,
hsl(calc(0.3 * 360) var(--this-sat) var(--this-light)) 30%,
hsl(calc(0.4 * 360) var(--this-sat) var(--this-light)) 40%,
hsl(calc(0.5 * 360) var(--this-sat) var(--this-light)) 50%,
hsl(calc(0.6 * 360) var(--this-sat) var(--this-light)) 60%,
hsl(calc(0.7 * 360) var(--this-sat) var(--this-light)) 70%,
hsl(calc(0.8 * 360) var(--this-sat) var(--this-light)) 80%,
hsl(calc(0.9 * 360) var(--this-sat) var(--this-light)) 90%,
hsl(360 var(--this-sat) var(--this-light)) 100%);
}
.hue input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: var(--slider-thumb-height);
width: var(--slider-thumb-width);
border: 1px solid #000;
background: hsl(var(--this-hue) var(--this-sat) var(--this-light));
cursor: pointer;
margin-top: calc((var(--slider-thumb-height) - var(--slider-track-height)) / -2);
}
.hue input[type=range]::-moz-range-thumb {
-webkit-appearance: none;
height: var(--slider-thumb-height);
width: var(--slider-thumb-width);
border: 1px solid #000;
background: hsl(var(--this-hue) var(--this-sat) var(--this-light));
cursor: pointer;
/* FIXME: This might be obsolete for FF or might let it break. */
margin-top: calc((var(--slider-thumb-height) - var(--slider-track-height)) / -2);
}
</style>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", (e) => {
console.info("DOM ready, manipulating the slider");
const hueContainer = document.querySelector("#this-hue-container");
const hueSlider = document.querySelector("#this-hue-container input[type=range]");
const hueValue = document.querySelector("#this-hue-container .value");
hueSlider.addEventListener("input", (e) => {
console.log(`Slider value: ${hueSlider.value}`);
hueValue.innerHTML = hueSlider.value;
hueContainer.style.setProperty("--this-hue", Number(hueSlider.value));
});
});
</script>
</head>
<body>
<header>
<h1>Colorizer</h1>
<p>A tool to build color palettes while considering contrast values.</p>
</header>
<form>
<div class="hue" id="this-hue-container">
<input type="range" min="0" max="360" step="0.01">
<span class="value">0.00</span>
</div>
</form>
</body>
</html>
background
attribute of the slider bar, is tightly coupled with the actual purpose of the slider (adjusting hue
of HSL
in this case), because the colors in the gradient are provided using the hsl()
notation.<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Colorizer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript">
/**
* Attach tooltip to a toggle button.
*
* @param fsId The ID of the fieldset
*
* The implementation is based on this guide
* https://inclusive-components.design/tooltips-toggletips/
* but makes some assumptions about the general structure of the DOM.
*/
function setupColorFormFieldsetTooltip(fsId) {
// Get the required DOM elements
//
// - ``ttButton`` is the actual control element. Clicking the button will show
// the tooltip.
// - ``ttContent`` is the container of the actual content of the tooltip. Its
// ``innerHTML`` will be placed in ``ttDisplay``.
// - ``ttDisplay`` is the actual tooltip. All styling should relate to this
// element.
let ttButton = document.querySelector(`#${fsId} > legend > .tooltip-anchor > button`);
let ttContent = document.querySelector(`#${fsId}-tooltip`);
let ttDisplay = document.querySelector(`#${fsId} > legend > .tooltip-anchor > .tooltip-display`);
// Attach the required event listeners
// Show the tooltip on click of the button
ttButton.addEventListener("click", () => {
ttDisplay.innerHTML = "";
window.setTimeout(() => {
ttDisplay.innerHTML = `<div>${ttContent.innerHTML}</div>`;
}, 100); // TODO: Should this timeout be adjustable?
});
// Hide the tooltip when ESC is pressed
ttButton.addEventListener("keydown", (e) => {
if ((e.keyCode || e.which) === 27)
ttDisplay.innerHTML = "";
});
// Hide the tooltip when the button loses focus, e.g. the user clicks
// somewhere else
document.addEventListener("click", (e) => {
if (e.target !== ttButton)
ttDisplay.innerHTML = "";
});
// Setup is completed, now remove ``ttContent`` from the (visual) DOM
//
// TODO: Is this the correct way? Or should it be *removed* from the DOM by
// using ``visibility: hidden``?
ttContent.classList.add("hide-visually");
}
function devGenericCallback(data) {
console.log(`R: ${data.r}, G: ${data.g}, B: ${data.b}`);
}
function devGenericCallback2(r, g, b) {
console.log(`R2: ${r}, G2: ${g}, B2: ${b}`);
}
/**
* Establish the logical connections between slider, text input and the containing fieldset.
*
* @param container DOM element, this input method's overall ``<fieldset>``.
* @param slider DOM element, the ``<input type="range" ...>`` element.
* @param text DOM element, the ``<input type="text" ...>`` element.
* @param property ``string`` The CSS custom property to make the value accessible for CSS.
*
* Adds event listeners for ``input`` events to the ``<input ...>`` elements to
* update the corresponding *other* ``<input ...>`` element.
* The ``container`` element's ``style`` attribute is updated with a CSS
* custom property, providing the value of the ``<input ...>`` elements for
* styling purposes.
*/
function linkContainerSliderText(container, slider, text, property) {
slider.addEventListener("input", () => {
let val = Number(slider.value);
text.value = val;
container.style.setProperty(property, val);
});
text.addEventListener("input", () => {
let val = Number(text.value);
slider.value = val;
container.style.setProperty(property, val);
});
}
function debounceInput(fn, d) {
let timer;
return function () {
clearTimeout(timer);
timer = setTimeout(fn, d);
}
}
function setupColorFormInputRgb(cbFunc) {
const fsId = "color-form-rgb";
let inputRed, inputGreen, inputBlue, inputSlider;
function cbWrapper() {
cbFunc({r: inputRed.value, g: inputGreen.value, b: inputBlue.value});
}
let methodFs = document.querySelector(`#${fsId}`);
inputRed = document.querySelector(`#${fsId} .component-red > input[type=text]`);
inputSlider = document.querySelector(`#${fsId} .component-red > input[type=range]`);
linkContainerSliderText(methodFs, inputSlider, inputRed, "--this-red");
inputRed.addEventListener("input", debounceInput(cbWrapper, 500));
inputSlider.addEventListener("input", debounceInput(cbWrapper, 500));
inputGreen = document.querySelector(`#${fsId} .component-green > input[type=text]`);
inputSlider = document.querySelector(`#${fsId} .component-green > input[type=range]`);
linkContainerSliderText(methodFs, inputSlider, inputGreen, "--this-green");
inputGreen.addEventListener("input", debounceInput(cbWrapper, 500));
inputSlider.addEventListener("input", debounceInput(cbWrapper, 500));
inputBlue = document.querySelector(`#${fsId} .component-blue > input[type=text]`);
inputSlider = document.querySelector(`#${fsId} .component-blue > input[type=range]`);
linkContainerSliderText(methodFs, inputSlider, inputBlue, "--this-blue");
inputBlue.addEventListener("input", debounceInput(cbWrapper, 500));
inputSlider.addEventListener("input", debounceInput(cbWrapper, 500));
setupColorFormFieldsetTooltip(fsId);
}
document.addEventListener("DOMContentLoaded", () => {
console.info("DOM ready, doing stuff!");
setupColorFormInputRgb(devGenericCallback);
});
</script>
<style>
* {
box-sizing: border-box;
}
/* This hides the element visually, but keep it as part of the DOM for
* accessibility.
* Ref: https://www.w3.org/WAI/tutorials/forms/labels/#note-on-hiding-elements
*/
.hide-visually {
position: absolute;
margin: -1px;
padding: 0;
border: 0;
clip: rect(0 0 0 0);
height: 1px;
width: 1px;
overflow: hidden;
}
/* Place and style the tooltips, which are provided as part of an input
* method's ``<legend>``.
*/
.color-form-input-method > legend > .tooltip-anchor {
position: relative;
display: inline-block;
}
.color-form-input-method > legend > .tooltip-anchor button {
font-size: 0.9em;
font-weight: bold;
border-radius: 5em;
border: 1px solid black;
}
.color-form-input-method > legend > .tooltip-anchor > .tooltip-display > div {
position: absolute;
display: block;
top: 0;
left: 100%;
font-size: 1rem;
width: 20em;
padding: 0.25em 0.5em;
background: rgb(200 200 200);
color: black;
border: 1px solid black;
}
#color-form {
--input-text-size: 1.25em;
--input-text-vertical-padding: 0.25em;
--input-text-border-width: 1px;
}
.color-form-input-method > fieldset {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.color-form-input-method > fieldset > input {
font-size: var(--input-text-size);
margin: 0;
padding: var(--input-text-vertical-padding) 0.5em;
font-family: monospace;
border: var(--input-text-border-width) solid #000;
}
.color-form-input-method > fieldset > input[type=range] {
--slider-track-height: 1em;
--slider-thumb-height: calc(var(--slider-track-height) * 1.5);
--slider-thumb-width: var(--slider-thumb-height);
-webkit-appearance: none;
width: 10em;
border: 0;
background: transparent;
}
.color-form-input-method > fieldset > input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 1em;
cursor: pointer;
border-radius: 5px;
border: 1px solid #000;
background: rgb(200 200 200);
}
.color-form-input-method > fieldset > input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: var(--slider-thumb-height);
width: var(--slider-thumb-width);
border: 1px solid #000;
border-radius: 999px;
cursor: pointer;
margin-top: calc((var(--slider-thumb-height) - var(--slider-track-height)) / -2);
}
#color-form-rgb {
--this-red: 0;
--this-green: 0;
--this-blue: 0;
}
#color-form-rgb > .component-red > input[type=range]::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgb(0 var(--this-green) var(--this-blue)) 0%,
rgb(255 var(--this-green) var(--this-blue)) 100%);
}
#color-form-rgb > .component-green > input[type=range]::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgb(var(--this-red) 0 var(--this-blue)) 0%,
rgb(var(--this-red) 255 var(--this-blue)) 100%);
}
#color-form-rgb > .component-blue > input[type=range]::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgb(var(--this-red) var(--this-green) 0) 0%,
rgb(var(--this-red) var(--this-green) 255) 100%);
}
#color-form-rgb > fieldset > input[type=range]::-webkit-slider-thumb {
background: rgb(var(--this-red) var(--this-green) var(--this-blue));
}
</style>
</head>
<body>
<header>
<h1>Colorizer</h1>
<p>A tool to build color palettes while considering contrast values.</p>
</header>
<form id="color-form">
<fieldset id="color-form-rgb" class="color-form-input-method">
<legend>
<strong>RGB input</strong>
<span class="tooltip-anchor">
<button type="button" aria-label="Description of the RGB input">?</button>
<div class="tooltip-display" role="status"></div>
</span>
</legend>
<fieldset class="component-red">
<legend>Red component</legend>
<label for="color-form-rgb-r-slider" class="hide-visually">Slider to adjust red component</label>
<input id="color-form-rgb-r-slider" type="range" min="0" max="255" step="1" aria-describedby="color-form-rgb-tooltip">
<label for="color-form-rgb-r" class="hide-visually">Decimal input for red component</label>
<input id="color-form-rgb-r" type="text" inputmode="numeric" aria-describedby="color-form-rgb-tooltip">
</fieldset>
<fieldset class="component-green">
<legend>Green component</legend>
<label for="color-form-rgb-g-slider" class="hide-visually">Slider to adjust green component</label>
<input id="color-form-rgb-g-slider" type="range" min="0" max="255" step="1" aria-describedby="color-form-rgb-tooltip">
<label for="color-form-rgb-g" class="hide-visually">Decimal input for green component</label>
<input id="color-form-rgb-g" type="text" inputmode="numeric" aria-describedby="color-form-rgb-tooltip">
</fieldset>
<fieldset class="component-blue">
<legend>Blue component</legend>
<label for="color-form-rgb-b-slider" class="hide-visually">Slider to adjust blue component</label>
<input id="color-form-rgb-b-slider" type="range" min="0" max="255" step="1" aria-describedby="color-form-rgb-tooltip">
<label for="color-form-rgb-b" class="hide-visually">Decimal input for blue component</label>
<input id="color-form-rgb-b" type="text" inputmode="numeric" aria-describedby="color-form-rgb-tooltip">
</fieldset>
<div role="tooltip" id="color-form-rgb-tooltip">
<p>The RGB input lets you specify the values for R, G and B components of the color directly.</p>
<p>Each of the components may be specified in a range between 0 and 255.</p>
<p>The values may be entered directly or using the slider.</p>
</div>
</fieldset>
</form>
</body>
</html>
This is a re-implementation of Experimental 01
, with a clear and class-based interface to the form - or more specifically - the fieldset that provides RGB input.
This is the preferred approach. Differences between 01/02 are only in the JS part, HTML and CSS are untouched!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Colorizer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript">
class ColorFormInput {
// TODO: Can this be a static method? Should this be a static method?
_getDomElement(query) {
// console.debug(`ColorFormInput.#getDomElement() query: ${query}`);
let tmp = document.querySelector(query);
if (tmp === null) {
throw new Error(`Missing required DOM element with query '${query}'`);
}
return tmp;
}
/**
* Establish the logical connections between slider, text input and the containing fieldset.
*
* @param container DOM element, this input method's overall ``<fieldset>``.
* @param slider DOM element, the ``<input type="range" ...>`` element.
* @param text DOM element, the ``<input type="text" ...>`` element.
* @param property ``string`` The CSS custom property to make the value accessible for CSS.
*
* Adds event listeners for ``input`` events to the ``<input ...>`` elements to
* update the corresponding *other* ``<input ...>`` element.
* The ``container`` element's ``style`` attribute is updated with a CSS
* custom property, providing the value of the ``<input ...>`` elements for
* styling purposes.
*/
// TODO: Can this be a static method? Should this be a static method?
_linkContainerSliderText(container, slider, text, property) {
slider.addEventListener("input", () => {
let val = Number(slider.value);
text.value = val;
container.style.setProperty(property, val);
});
text.addEventListener("input", () => {
let val = Number(text.value);
slider.value = val;
container.style.setProperty(property, val);
});
}
/**
* Attach tooltip to a toggle button.
*
* @param fsId The ID of the fieldset
*
* The implementation is based on this guide
* https://inclusive-components.design/tooltips-toggletips/
* but makes some assumptions about the general structure of the DOM.
*/
// TODO: Can this be a static method? Should this be a static method?
_setupColorFormFieldsetTooltip(fsId) {
// Get the required DOM elements
//
// - ``ttButton`` is the actual control element. Clicking the button will show
// the tooltip.
// - ``ttContent`` is the container of the actual content of the tooltip. Its
// ``innerHTML`` will be placed in ``ttDisplay``.
// - ``ttDisplay`` is the actual tooltip. All styling should relate to this
// element.
let ttButton = this._getDomElement(`#${fsId} > legend > .tooltip-anchor > button`);
let ttContent = this._getDomElement(`#${fsId}-tooltip`);
let ttDisplay = this._getDomElement(`#${fsId} > legend > .tooltip-anchor > .tooltip-display`);
// Attach the required event listeners
// Show the tooltip on click of the button
ttButton.addEventListener("click", () => {
ttDisplay.innerHTML = "";
window.setTimeout(() => {
ttDisplay.innerHTML = `<div>${ttContent.innerHTML}</div>`;
}, 100); // TODO: Should this timeout be adjustable?
});
// Hide the tooltip when ESC is pressed
ttButton.addEventListener("keydown", (e) => {
if ((e.keyCode || e.which) === 27)
ttDisplay.innerHTML = "";
});
// Hide the tooltip when the button loses focus, e.g. the user clicks
// somewhere else
document.addEventListener("click", (e) => {
if (e.target !== ttButton)
ttDisplay.innerHTML = "";
});
// Setup is completed, now remove ``ttContent`` from the (visual) DOM
//
// TODO: Is this the correct way? Or should it be *removed* from the DOM by
// using ``visibility: hidden``?
ttContent.classList.add("hide-visually");
}
/**
*
*
* Reference: https://chiamakaikeanyi.dev/event-debouncing-and-throttling-in-javascript/
*/
// TODO: Can this be a static method? Should this be a static method?
_debounce(context, fn, debounceTime) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, debounceTime);
}
}
_publishColor(ev) {
console.log(ev);
}
}
class ColorFormInputRgb extends ColorFormInput {
// Private Attributes
#fieldset;
#inputTextRed;
#inputSliderRed;
#inputTextGreen;
#inputSliderGreen;
#inputTextBlue;
#inputSliderBlue;
static #fieldsetId = "color-form-rgb";
constructor() {
super();
// Get DOM elements
this.#fieldset = this._getDomElement(`#${this.constructor.#fieldsetId}`);
this.#inputTextRed = this._getDomElement(`#${this.constructor.#fieldsetId} .component-red > input[type=text]`);
this.#inputSliderRed = this._getDomElement(`#${this.constructor.#fieldsetId} .component-red > input[type=range]`);
this.#inputTextGreen = this._getDomElement(`#${this.constructor.#fieldsetId} .component-green > input[type=text]`);
this.#inputSliderGreen = this._getDomElement(`#${this.constructor.#fieldsetId} .component-green > input[type=range]`);
this.#inputTextBlue = this._getDomElement(`#${this.constructor.#fieldsetId} .component-blue > input[type=text]`);
this.#inputSliderBlue = this._getDomElement(`#${this.constructor.#fieldsetId} .component-blue > input[type=range]`);
// Establish connections between related input elements
this._linkContainerSliderText(this.#fieldset, this.#inputSliderRed, this.#inputTextRed, "--this-red");
this._linkContainerSliderText(this.#fieldset, this.#inputSliderGreen, this.#inputTextGreen, "--this-green");
this._linkContainerSliderText(this.#fieldset, this.#inputSliderBlue, this.#inputTextBlue, "--this-blue");
// Attach event listeners
this.#inputTextRed.addEventListener("input", this._debounce(this, this._publishColor, 500));
this.#inputSliderRed.addEventListener("input", this._debounce(this, this._publishColor, 500));
this.#inputTextGreen.addEventListener("input", this._debounce(this, this._publishColor, 500));
this.#inputSliderGreen.addEventListener("input", this._debounce(this, this._publishColor, 500));
this.#inputTextBlue.addEventListener("input", this._debounce(this, this._publishColor, 500));
this.#inputSliderBlue.addEventListener("input", this._debounce(this, this._publishColor, 500));
this._setupColorFormFieldsetTooltip(this.constructor.#fieldsetId);
}
_publishColor(ev) {
// console.debug(ev);
// FIXME: Probably a good place to check the validity of the inputs!
console.info(`R: ${this.#inputTextRed.value}, G: ${this.#inputTextGreen.value}, B: ${this.#inputTextBlue.value}`);
}
}
document.addEventListener("DOMContentLoaded", () => {
console.info("DOM ready, doing stuff!");
const foo = new ColorFormInputRgb();
});
</script>
<style>
* {
box-sizing: border-box;
}
/* This hides the element visually, but keep it as part of the DOM for
* accessibility.
* Ref: https://www.w3.org/WAI/tutorials/forms/labels/#note-on-hiding-elements
*/
.hide-visually {
position: absolute;
margin: -1px;
padding: 0;
border: 0;
clip: rect(0 0 0 0);
height: 1px;
width: 1px;
overflow: hidden;
}
/* Place and style the tooltips, which are provided as part of an input
* method's ``<legend>``.
*/
.color-form-input-method > legend > .tooltip-anchor {
position: relative;
display: inline-block;
}
.color-form-input-method > legend > .tooltip-anchor button {
font-size: 0.9em;
font-weight: bold;
border-radius: 5em;
border: 1px solid black;
}
.color-form-input-method > legend > .tooltip-anchor > .tooltip-display > div {
position: absolute;
display: block;
top: 0;
left: 100%;
font-size: 1rem;
width: 20em;
padding: 0.25em 0.5em;
background: rgb(200 200 200);
color: black;
border: 1px solid black;
}
#color-form {
--input-text-size: 1.25em;
--input-text-vertical-padding: 0.25em;
--input-text-border-width: 1px;
}
.color-form-input-method > fieldset {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.color-form-input-method > fieldset > input {
font-size: var(--input-text-size);
margin: 0;
padding: var(--input-text-vertical-padding) 0.5em;
font-family: monospace;
border: var(--input-text-border-width) solid #000;
}
.color-form-input-method > fieldset > input[type=range] {
--slider-track-height: 1em;
--slider-thumb-height: calc(var(--slider-track-height) * 1.5);
--slider-thumb-width: var(--slider-thumb-height);
-webkit-appearance: none;
width: 10em;
border: 0;
background: transparent;
}
.color-form-input-method > fieldset > input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 1em;
cursor: pointer;
border-radius: 5px;
border: 1px solid #000;
background: rgb(200 200 200);
}
.color-form-input-method > fieldset > input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: var(--slider-thumb-height);
width: var(--slider-thumb-width);
border: 1px solid #000;
border-radius: 999px;
cursor: pointer;
margin-top: calc((var(--slider-thumb-height) - var(--slider-track-height)) / -2);
}
#color-form-rgb {
--this-red: 0;
--this-green: 0;
--this-blue: 0;
}
#color-form-rgb > .component-red > input[type=range]::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgb(0 var(--this-green) var(--this-blue)) 0%,
rgb(255 var(--this-green) var(--this-blue)) 100%);
}
#color-form-rgb > .component-green > input[type=range]::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgb(var(--this-red) 0 var(--this-blue)) 0%,
rgb(var(--this-red) 255 var(--this-blue)) 100%);
}
#color-form-rgb > .component-blue > input[type=range]::-webkit-slider-runnable-track {
background: linear-gradient(90deg,
rgb(var(--this-red) var(--this-green) 0) 0%,
rgb(var(--this-red) var(--this-green) 255) 100%);
}
#color-form-rgb > fieldset > input[type=range]::-webkit-slider-thumb {
background: rgb(var(--this-red) var(--this-green) var(--this-blue));
}
</style>
</head>
<body>
<header>
<h1>Colorizer</h1>
<p>A tool to build color palettes while considering contrast values.</p>
</header>
<form id="color-form">
<fieldset id="color-form-rgb" class="color-form-input-method">
<legend>
<strong>RGB input</strong>
<span class="tooltip-anchor">
<button type="button" aria-label="Description of the RGB input">?</button>
<div class="tooltip-display" role="status"></div>
</span>
</legend>
<fieldset class="component-red">
<legend>Red component</legend>
<label for="color-form-rgb-r-slider" class="hide-visually">Slider to adjust red component</label>
<input id="color-form-rgb-r-slider" type="range" min="0" max="255" step="1" aria-describedby="color-form-rgb-tooltip">
<label for="color-form-rgb-r" class="hide-visually">Decimal input for red component</label>
<input id="color-form-rgb-r" type="text" inputmode="numeric" aria-describedby="color-form-rgb-tooltip">
</fieldset>
<fieldset class="component-green">
<legend>Green component</legend>
<label for="color-form-rgb-g-slider" class="hide-visually">Slider to adjust green component</label>
<input id="color-form-rgb-g-slider" type="range" min="0" max="255" step="1" aria-describedby="color-form-rgb-tooltip">
<label for="color-form-rgb-g" class="hide-visually">Decimal input for green component</label>
<input id="color-form-rgb-g" type="text" inputmode="numeric" aria-describedby="color-form-rgb-tooltip">
</fieldset>
<fieldset class="component-blue">
<legend>Blue component</legend>
<label for="color-form-rgb-b-slider" class="hide-visually">Slider to adjust blue component</label>
<input id="color-form-rgb-b-slider" type="range" min="0" max="255" step="1" aria-describedby="color-form-rgb-tooltip">
<label for="color-form-rgb-b" class="hide-visually">Decimal input for blue component</label>
<input id="color-form-rgb-b" type="text" inputmode="numeric" aria-describedby="color-form-rgb-tooltip">
</fieldset>
<div role="tooltip" id="color-form-rgb-tooltip">
<p>The RGB input lets you specify the values for R, G and B components of the color directly.</p>
<p>Each of the components may be specified in a range between 0 and 255.</p>
<p>The values may be entered directly or using the slider.</p>
</div>
</fieldset>
</form>
</body>
</html>
As of now, colors have to be added using hex notation in a text-based
input
field. This works well enough but is not really convenient.Requirements
type="color"
ColorizerInterface
and/or inColorizerEngine
, because that input field includes the leading#
character.This might be implemented in frontend-only, meaning that the actual interface toSwitched toColorizerEngine
remains the hex notation (see above, especially the this resource)0..255
internally!add means to use OklabNot for now!Oklch
seems more intuitive!Resources
sRGB
(which is predominant for usual computer screens) is described here, including sample implementation in JS