HSL

RGB 대신 색상(Hue)·채도(Saturation)·명도(Lightness) 세 값으로 색을 표현하는 방식. 사람이 색을 조절하는 감각과 더 가깝습니다.

다른 이름: Hue Saturation Lightness색상 채도 명도
···
html
<div class="panel">
  <div class="swatch" id="sw"></div>
  <div class="row"><span class="lbl">H</span><div class="bar" id="barH"><i id="dotH"></i></div><b id="vh">210°</b></div>
  <div class="row"><span class="lbl">S</span><div class="bar" id="barS"><i id="dotS"></i></div><b id="vs">78%</b></div>
  <div class="row"><span class="lbl">L</span><div class="bar" id="barL"><i id="dotL"></i></div><b id="vl">55%</b></div>
</div>
css
.panel{width:min(520px,92%);display:grid;gap:clamp(8px,2.4vmin,14px)}
.swatch{height:clamp(48px,16vmin,72px);border-radius:14px;border:1px solid var(--line)}
.row{display:grid;grid-template-columns:18px 1fr 58px;align-items:center;gap:10px}
.lbl{color:var(--muted);font:700 clamp(10px,2.6vmin,13px)/1 ui-monospace,monospace}
.bar{position:relative;height:clamp(12px,3.4vmin,16px);border-radius:8px;border:1px solid var(--line);cursor:pointer;touch-action:none}
.bar i{position:absolute;top:50%;width:16px;height:16px;margin-left:-8px;margin-top:-8px;border-radius:50%;background:#fff;border:2px solid var(--fg);box-shadow:0 1px 3px rgba(0,0,0,.35);pointer-events:none}
#barH{background:linear-gradient(90deg,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)}
b{font:700 clamp(10px,2.6vmin,13px)/1 ui-monospace,monospace;color:var(--fg);text-align:right}
js
let hue = 210, sat = 78, light = 55, autoRotate = true;
const sw = document.getElementById('sw');
const barH = document.getElementById('barH'), barS = document.getElementById('barS'), barL = document.getElementById('barL');
const dotH = document.getElementById('dotH'), dotS = document.getElementById('dotS'), dotL = document.getElementById('dotL');
const vh = document.getElementById('vh'), vs = document.getElementById('vs'), vl = document.getElementById('vl');

function hsl(h, s, l) { return 'hsl(' + h + ', ' + s + '%, ' + l + '%)'; }

function render() {
  sw.style.background = hsl(hue, sat, light);
  dotH.style.left = (hue / 360 * 100) + '%';
  barS.style.background = 'linear-gradient(90deg,' + hsl(hue, 0, light) + ',' + hsl(hue, 100, light) + ')';
  dotS.style.left = sat + '%';
  barL.style.background = 'linear-gradient(90deg,' + hsl(hue, sat, 0) + ',' + hsl(hue, sat, 50) + ',' + hsl(hue, sat, 100) + ')';
  dotL.style.left = light + '%';
  vh.textContent = Math.round(hue) + '°';
  vs.textContent = Math.round(sat) + '%';
  vl.textContent = Math.round(light) + '%';
}

function bindDrag(bar, onMove) {
  function fromEvent(e) {
    const r = bar.getBoundingClientRect();
    const p = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
    onMove(p);
    render();
  }
  bar.addEventListener('pointerdown', e => { autoRotate = false; bar.setPointerCapture(e.pointerId); fromEvent(e); });
  bar.addEventListener('pointermove', e => { if (e.buttons) fromEvent(e); });
}
bindDrag(barH, p => hue = p * 360);
bindDrag(barS, p => sat = p * 100);
bindDrag(barL, p => light = p * 100);

(function tick() {
  if (autoRotate) hue = (hue + 0.5) % 360;
  render();
  requestAnimationFrame(tick);
})();

RGB는 빛의 삼원색을 얼마나 섞을지를 정하는 방식이라 "더 밝게", "더 탁하게" 같은 조정이 직관적이지 않습니다. HSL은 원기둥 모델로 이 문제를 풉니다 — 색상(0~360°, 색상환 위의 각도) · 채도(0~100%, 색의 순도) · 명도(0~100%, 검정~흰색 사이 밝기)로 나눠서 조절합니다.

CSS에서는 hsl(210 80% 55%)처럼 그대로 쓸 수 있고, 같은 색상의 밝은/어두운 버전을 만들 때(명도만 바꾸기)나 채도만 낮춰 톤을 죽일 때 특히 편합니다.

다만 HSL의 명도는 사람 눈에 느껴지는 밝기와 정확히 일치하지 않습니다 — 같은 L값이라도 노랑은 밝아 보이고 파랑은 어두워 보입니다. 이 한계를 보완한 것이 OKLCH입니다.

언제 쓰나

색상 하나를 고정하고 명도·채도만 바꿔 팔레트를 만들 때, 또는 색상값을 코드에서 동적으로 계산할 때 씁니다.