채도와 크로마

Saturation and chroma

둘 다 "색이 얼마나 선명한가"를 재지만, 채도(HSL)는 명도 대비 상대값이고 크로마(OKLCH)는 절대적인 컬러풀니스입니다.

다른 이름: Saturation vs chroma컬러풀니스
···
html
<div class="wrap">
  <div class="rowlabel">HSL Saturation <span id="sVal" class="muted">S 0%</span></div>
  <div class="track" id="trackS"><div class="fill" id="fillS"></div><i id="dotS"></i></div>
  <div class="rowlabel">OKLCH Chroma <span id="cVal" class="muted">C 0.00</span></div>
  <div class="track" id="trackC"><div class="fill" id="fillC"></div><i id="dotC"></i></div>
</div>
css
.wrap{width:min(520px,92%);display:grid;gap:6px}
.rowlabel{display:flex;justify-content:space-between;align-items:baseline;font:700 clamp(10px,2.6vmin,12px)/1 ui-monospace,monospace;color:var(--fg);margin-top:10px}
.rowlabel .muted{color:var(--muted);font-weight:600}
.track{position:relative;height:clamp(20px,5.4vmin,32px);border-radius:10px;border:1px solid var(--line);cursor:pointer;touch-action:none;overflow:hidden}
.fill{position:absolute;inset:0}
.track i{position:absolute;top:50%;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;background:#fff;border:2px solid var(--fg);box-shadow:0 1px 3px rgba(0,0,0,.35);pointer-events:none}
js
const hue = 250;
let t = 0, dir = 1, autoPlay = true;
const trackS = document.getElementById('trackS'), fillS = document.getElementById('fillS'), dotS = document.getElementById('dotS'), sVal = document.getElementById('sVal');
const trackC = document.getElementById('trackC'), fillC = document.getElementById('fillC'), dotC = document.getElementById('dotC'), cVal = document.getElementById('cVal');
fillS.style.background = 'linear-gradient(90deg, hsl(' + hue + ',0%,55%), hsl(' + hue + ',100%,55%))';
fillC.style.background = 'linear-gradient(90deg, oklch(62% 0 ' + hue + '), oklch(62% 0.25 ' + hue + '))';

function render(p) {
  const sat = Math.round(p * 100);
  sVal.textContent = 'S ' + sat + '%';
  dotS.style.left = (p * 100) + '%';
  const chroma = (p * 0.25);
  cVal.textContent = 'C ' + chroma.toFixed(2);
  dotC.style.left = (p * 100) + '%';
}
function bind(track, setP) {
  function fromEvent(e) {
    const r = track.getBoundingClientRect();
    const p = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
    setP(p);
  }
  track.addEventListener('pointerdown', e => { autoPlay = false; track.setPointerCapture(e.pointerId); fromEvent(e); render(t); });
  track.addEventListener('pointermove', e => { if (e.buttons) { fromEvent(e); render(t); } });
}
bind(trackS, p => t = p);
bind(trackC, p => t = p);

(function tick() {
  if (autoPlay) {
    t += dir * 0.006;
    if (t > 1) { t = 1; dir = -1; }
    if (t < 0) { t = 0; dir = 1; }
  }
  render(t);
  requestAnimationFrame(tick);
})();

HSL의 채도(S)는 0~100%로 딱 떨어져서 다루기 쉽지만, 명도(L)와 얽혀 있습니다 — 같은 S=100%라도 L=10%(거의 검정)와 L=90%(거의 흰색)에서는 눈에 보이는 "선명함"이 전혀 다릅니다.

크로마(chroma, OKLCH·LCH가 쓰는 축)는 명도와 독립적으로 정의된 절대 컬러풀니스입니다. 그래서 같은 크로마값을 유지한 채 명도만 바꾸면, 채도를 유지한 채 명도를 바꿀 때보다 "쨍한 정도"가 훨씬 일관되게 유지됩니다.

디자인 토큰에서 "이 브랜드색을 명도만 다르게 여러 단계로 뽑고 싶다"면 크로마 기반(OKLCH)이, "0~100% 슬라이더로 빠르게 감을 잡고 싶다"면 채도 기반(HSL)이 더 편합니다.

언제 쓰나

여러 명도 단계에 걸쳐 "쨍한 느낌"을 일정하게 유지하고 싶다면 채도 대신 크로마를 기준으로 삼으세요.