HSL Picker
by Rolf
HTML
<div id="hsl-picker">
<fieldset class="controls">
<legend>HSL Farbnavigator</legend>
<div class="view">
<label for="s-view">Blickwinkel: </label>
<span>
<input id="s-view" type="range" min="-45" max="135" value="25" step="1">
</span>
<output></output>
</div>
<div class="hue">
<label for="s-hue">Farbton: </label>
<span>
<input id="s-hue" type="range" min="0" max="360" value="0" step="1">
</span>
<output></output>
</div>
<div class="light">
<label for="s-lightness">Helligkeit: </label>
<span>
<input id="s-lightness" type="range" min="0" max="100" value="50" step="1" list="range_0_100">
</span>
<output></output>
</div>
<div class="saturation">
<label for="s-saturation">Sättigung: </label>
<span>
<input id="s-saturation" type="range" min="0" max="100" value="50" step="1" list="range_0_100">
</span>
<output></output>
</div>
<div class="mode">
<label>Modus:</label>
<span>
<label><input type="radio" name="mode" value="move-disk" checked>Bewege Farbscheibe</label>
<label><input type="radio" name="mode" value="move-bar">Bewege Helligkeitsbalken</label>
</span>
</div>
<output id="rendertime"></output>
<datalist id="range_0_100">
<option value="50"></option>
</datalist>
</fieldset>
<div class="hue-select">
<canvas class="hue-ring" width="800" height="800"></canvas>
<div class="light-bar">
<!-- 3D-Modell für die Helligkeits-"Stange" -->
<div class="cap"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
</div>
</div>
CSS
#hsl-picker {
--hue: 0deg;
--light: 0;
--view: 80deg;
--hue-r1: 80cqw;
--bar-width: 5cqw;
--bar-height: 60cqw;
--bar-safe: 1px;
width: 800px;
container-type: inline-size;
.controls {
padding-inline: 0.5em;
position: relative;
xz-index: 1;
background-color: hsl(320deg 5% 95%);
div {
display: flex;
align-items: center;
margin: 0.5em 0;
}
> div > label {
flex: 0 0 5.5em;
text-align: right;
}
span {
flex: 1 0 auto;
padding: 0.2em 0.5em;
input[type=range] {
width: 100%;
margin: 0.2em 0;
}
.saturation & {
background: linear-gradient(to right,
hsl(var(--hue) 0% calc(1%*var(--light))),
hsl(var(--hue) 100% calc(1%*var(--light))))
1em top / calc(100% - 2em) 100% no-repeat;
}
}
output {
flex: 0 0 4em;
background-color: hsl(60deg 5% 85%);;
height: 1.5em;
}
}
}
.hue-select {
--extra-height: calc(0.15 * var(--bar-height));
position: relative;
width: 100%;
height: calc(2*var(--extra-height) + var(--bar-height));
transform-style: preserve-3d;
transform: rotateX(calc(-1*var(--view)));
}
.hue-ring {
--light-shift: 0px;
position: absolute;
left: calc((100% - var(--hue-r1)) / 2);
width: var(--hue-r1);
aspect-ratio: 1/1;
transform-origin: center;
transform: translateY(calc(var(--extra-height) - var(--hue-r1)/2 + var(--light-shift)))
rotateX(90deg)
rotateZ(-45deg);
}
.controls:has([value=move-disk]:checked) ~ .hue-select .hue-ring {
--light-shift: calc(var(--bar-height) * (100 - var(--light))/100);
}
.controls:has([value=move-bar]:checked) ~ .hue-select .hue-ring {
--light-shift: calc( var(--bar-height) / 2);
}
.controls:has([value=move-bar]:checked) ~ .hue-select .light-bar {
transform:
translateY(calc(var(--bar-height)*(var(--light) -...
JavaScript
const picker = document.querySelector("#hsl-picker");
const can1 = picker.querySelector(".hue-ring");
const can2 = picker.querySelector(".light-bar .cap");
// showHueDisk(ctx2D, 50);
can1.setAttribute("width" , can1.clientWidth );
can1.setAttribute("height", can1.clientHeight);
//can2.setAttribute("width" , can2.clientWidth );
//can2.setAttribute("height", can2.clientHeight);
pickStyle = window.getComputedStyle(picker);
function paintLightBar(ctx2D) {
const view = CSSUnitValue.parse(pickStyle.getPropertyValue("--view"));
const viewAngle = view.to("rad").value,
width = ctx2D.canvas.width,
rx = width/2,
ry = rx * Math.sin(viewAngle),
height = ctx2D.canvas.height - 2*(ry+2);
console.log("bar height is", height, "view is",view,"view Angle is",viewAngle,"rad, rx is", rx, ", ry=", ry);
const step = 10;
const dy = height*step/100;
for (let i=step; i<=100; i+=step) {
// y2: Ohne +1 ist das unterste Segment nicht hoch genug
const y1 = ry+1+ height*(100-i)/100,
y2 = y1 + dy + 2;
ctx2D.fillStyle = `hsl(0 0% ${i}%)`;
ctx2D.beginPath();
ctx2D.moveTo(width/2 - rx, y1);
ctx2D.lineTo(width/2 - rx, y2);
// Mittlerer Stützpunkt: Ohne +1 für y ist die Kurve zu flach
ctx2D.quadraticCurveTo(width/2 - rx, y2 + ry, width/2 , y2+ry+1);
ctx2D.quadraticCurveTo(width/2 + rx, y2 + ry, width/2+rx, y2);
ctx2D.lineTo(width/2 + rx, y1);
ctx2D.closePath();
//ctx2D.ellipse(width/2 - rx, ry+1+height*(100-i)/100, rx, ry, 0, 0, Math.PI*2);
ctx2D.fill();
}
ctx2D.beginPath();
ctx2D.fillStyle = "white";
ctx2D.strokeStyle = "black";
ctx2D.lineWidth = 0.5;
ctx2D.ellipse(width/2, ry+1, rx, ry, 0, 0, Math.PI*2);
ctx2D.fill();
ctx2D.stroke();
ctx2D.beginPath();
ctx2D.moveTo(0, ry+1);
ctx2D.lineTo(0, ry+1+height);
ctx2D.moveTo(width, ry+1);
ctx2D.lineTo(width, ry+1+height);
ctx2D.stroke();
}
function...