Beispiel:Farben-HSL-Visualize.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
<!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>