Beispiel:Farben-HSL-Visualize.html
Aus SELFHTML-Wiki
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" media="screen" href="./Beispiel:SELFHTML-Beispiel-Grundlayout.css">
<title>hsl()-Visualizer</title>
<style>
body {
margin: 0 auto;
}
#hsl-picker {
--hue: 0deg;
--light: 0;
--view: 25deg;
--hue-r1: 80cqw;
--bar-width: 5cqw;
--bar-height: 60cqw;
--bar-safe: 1px;
width: min(640px, 100%);
margin: 0 auto;
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: calc(var(--bar-height) * (100 - var(--light))/100);
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);
}
.light-bar {
--seg-angle: calc(90deg / 4); /* max. Index der Bar Segmente + 1 */
--seg-width: calc(1px + var(--bar-width) * tan(var(--seg-angle)/2));
--bar-border: 1px;
position:absolute;
width: var(--bar-width);
box-sizing: border-box;
height: var(--bar-height);
border-inline: var(--bar-border) solid black;
top: var(--extra-height);
left: calc(50% - var(--bar-width)/2);
transform-style: preserve-3d;
}
.light-bar > .cap {
position:absolute;
width: var(--bar-width);
box-sizing: border-box;
left: calc(0px - var(--bar-border));
aspect-ratio: 1 / 1;
background-color: white;
border: 1px solid black;
border-radius: 50%;
transform: translateY(calc(var(--bar-width)/-2)) rotateX(90deg);
}
.light-bar > .bar {
position: absolute;
width: var(--seg-width);
height: 100%;
left: calc(50% - var(--seg-width) / 2);
background: linear-gradient(to bottom, white, black);
transform: rotateY(calc(var(--seg-angle) * var(--seg-shift)))
translateZ(calc(var(--bar-width)/2 - 1px));
}
.light-bar > :nth-child(1 of .bar) { --seg-shift: 0; }
.light-bar > :nth-child(2 of .bar) { --seg-shift: 1; }
.light-bar > :nth-child(3 of .bar) { --seg-shift: -1; }
.light-bar > :nth-child(4 of .bar) { --seg-shift: 2; }
.light-bar > :nth-child(5 of .bar) { --seg-shift: -2; }
.light-bar > :nth-child(6 of .bar) { --seg-shift: 3; }
.light-bar > :nth-child(7 of .bar) { --seg-shift: -3; }
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
const picker = document.querySelector("#hsl-picker");
const hueRing = picker.querySelector(".hue-ring");
const barCap = picker.querySelector(".light-bar .cap");
// Canvas-Auflösung an Canvas-Größe anpassen
hueRing.setAttribute("width" , hueRing.clientWidth );
hueRing.setAttribute("height", hueRing.clientHeight);
pickStyle = window.getComputedStyle(picker);
// Funktion zum Zeichnen der Farbkreisscheibe mit Sättigungsverlauf
function showHueDisk(ctx2D, hue, lightness) {
const width = ctx2D.canvas.width, // Größe der Scheibe
height = ctx2D.canvas.height,
cx = width/2, // Mittelpunkt
cy = height/2,
r = cx, // Radius
rBar = barCap.clientWidth / 2 + 0.5,
// Farbringe mit Sättigung von 0-100%.
// Ab 50 Ringe entsteht ein visuell stufenloser Übergang
anzRinge = 50,
ringWidth = (r - rBar) / anzRinge + 0.5,
r0 = rBar + ringWidth/2,
k = (r - ringWidth - rBar) / 100;
ctx2D.lineWidth = ringWidth;
for (let i=100; i>=0; i-=100/anzRinge) {
ctx2D.beginPath();
ctx2D.strokeStyle = makeGradient(ctx2D, cx, cy, hue, i, lightness);
ctx2D.arc(cx, cy, r0 + k*i, 0, Math.PI * 2);
ctx2D.stroke();
}
ctx2D.lineWidth = 0.5;
ctx2D.strokeStyle = "black";
// Innere Randlinie - separat weil 2x arc in einem Path Artefakte ergibt
ctx2D.beginPath();
ctx2D.arc(cx, cy, rBar, 0, Math.PI * 2);
ctx2D.stroke();
// Äußere Randlinie + "Gasse" vom Zentrum zum Rand
ctx2D.beginPath();
ctx2D.arc(cx, cy, r, 0, Math.PI * 2);
ctx2D.moveTo(cx-5, cy + rBar);
ctx2D.lineTo(cx-5, cy + r*1.1+ringWidth/2);
ctx2D.moveTo(cx+5, cy + rBar);
ctx2D.lineTo(cx+5, cy + r*1.1+ringWidth/2);
ctx2D.stroke();
}
function makeGradient(ctx, cx, cy, hue, saturation, lightness) {
const grad = ctx.createConicGradient(Math.PI/2 - hue/180*Math.PI, cx, cy),
// 6 reicht, Haupsache, die Vielfachen von 60° werden getroffen
stops=6;
for (let j=0; j<=stops; j++)
grad.addColorStop(j/stops, `hsl(${0+j*360/stops}deg ${saturation}% ${lightness}%)`);
return grad;
}
const controls = picker.querySelector(".controls");
const hueControl = controls.querySelector(".hue input");
const lightControl = controls.querySelector(".light input");
const saturationControl = controls.querySelector(".saturation input");
controls.addEventListener("input", updateControls);
updateControls();
function updateControls(changeEvent) {
const ctx2D = hueRing.getContext("2d");
picker.style.setProperty("--hue", hueControl.value + "deg");
controls.querySelector(".hue output").textContent = hueControl.value + "°";
picker.style.setProperty("--light", lightControl.value);
controls.querySelector(".light output").textContent = lightControl.value + "%";
controls.querySelector(".saturation output").textContent = saturationControl.value + "%";
showHueDisk(ctx2D, hueControl.value, lightControl.value);
}
});
</script>
</head>
<body>
<div id="hsl-picker">
<fieldset class="controls">
<legend>HSL Farbnavigator</legend>
<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>
<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>
</body>
</html>