Color harmony wheel

색상 조화 원리

The color wheel summarizes every harmony rule — complementary, analogous, triadic, split-complementary — as angles on a circle. Only the marker count and spacing change between them.

Also known as: Color wheel컬러 하모니
···
html
<div class="wrap">
  <div class="row">
    <div class="wheel" id="wheel"><div class="marker" id="m0"></div><div class="marker" id="m1"></div><div class="marker" id="m2"></div><div class="marker" id="m3"></div></div>
    <div class="swatches" id="sws"></div>
  </div>
  <div class="tag" id="tag">Complementary</div>
</div>
css
.wrap{display:grid;gap:12px;justify-items:center;width:min(600px,94%)}
.row{display:grid;grid-template-columns:auto 1fr;gap:clamp(14px,4vmin,28px);align-items:center;width:100%}
.wheel{position:relative;width:clamp(100px,30vmin,180px);height:clamp(100px,30vmin,180px);border-radius:50%;cursor:grab;touch-action:none;
  background:conic-gradient(hsl(0 80% 55%),hsl(60 80% 55%),hsl(120 80% 55%),hsl(180 80% 55%),hsl(240 80% 55%),hsl(300 80% 55%),hsl(360 80% 55%));
  box-shadow:0 0 0 1px var(--line),0 10px 26px rgba(0,0,0,.18)}
.marker{position:absolute;width:16%;height:16%;min-width:13px;min-height:13px;border-radius:50%;background:#fff;border:3px solid var(--fg);transform:translate(-50%,-50%);box-shadow:0 2px 6px rgba(0,0,0,.35)}
.swatches{display:grid;gap:6px}
.sw{height:clamp(20px,5vmin,28px);border-radius:7px;border:1px solid var(--line);display:flex;align-items:center;padding:0 9px;font:700 clamp(9px,2.4vmin,11px)/1 ui-monospace,monospace;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.4)}
.tag{font:800 clamp(11px,3vmin,14px)/1 ui-monospace,monospace;letter-spacing:.04em;color:var(--fg);padding:6px 14px;border:1px solid var(--line);border-radius:999px;background:var(--surface)}
js
const PRESETS = [
  { name: 'Complementary', offsets: [0, 180] },
  { name: 'Analogous', offsets: [-30, 0, 30] },
  { name: 'Triadic', offsets: [0, 120, 240] },
  { name: 'Split-Comp', offsets: [0, 150, 210] },
  { name: 'Tetradic', offsets: [0, 90, 180, 270] },
];
const wheel = document.getElementById('wheel');
const markers = [0, 1, 2, 3].map(i => document.getElementById('m' + i));
const sws = document.getElementById('sws'), tag = document.getElementById('tag');
let base = 210, presetIdx = 0, autoRotate = true;

function place(hue, el, R) {
  const rad = hue * Math.PI / 180;
  el.style.left = (50 + R * Math.sin(rad)) + '%';
  el.style.top = (50 - R * Math.cos(rad)) + '%';
}
function render() {
  const preset = PRESETS[presetIdx];
  tag.textContent = preset.name;
  markers.forEach((m, i) => {
    if (i < preset.offsets.length) {
      const h = (base + preset.offsets[i] + 360) % 360;
      place(h, m, 40);
      m.style.background = 'hsl(' + h + ', 78%, 52%)';
      m.style.display = 'block';
    } else {
      m.style.display = 'none';
    }
  });
  sws.innerHTML = preset.offsets.map(off => {
    const h = (base + off + 360) % 360;
    return '<div class="sw" style="background:hsl(' + h + ',78%,52%)">' + Math.round(h) + '°</div>';
  }).join('');
}
function angleFromEvent(e) {
  const r = wheel.getBoundingClientRect();
  const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
  const deg = Math.atan2(e.clientX - cx, -(e.clientY - cy)) * 180 / Math.PI;
  return (deg + 360) % 360;
}
wheel.addEventListener('pointerdown', e => { autoRotate = false; wheel.setPointerCapture(e.pointerId); base = angleFromEvent(e); render(); });
wheel.addEventListener('pointermove', e => { if (e.buttons) { base = angleFromEvent(e); render(); } });
tag.addEventListener('click', () => { presetIdx = (presetIdx + 1) % PRESETS.length; render(); });

let lastSwitch = 0;
(function tick(t) {
  if (autoRotate) base = (base + 0.25) % 360;
  if (t - lastSwitch > 2600) { presetIdx = (presetIdx + 1) % PRESETS.length; lastSwitch = t; }
  render();
  requestAnimationFrame(tick);
})(0);

A color wheel is just hue (0–360°) bent into a circle. "Harmony" reduces to how many points you place on it and at what angles — complementary is 2 points at 180°, analogous is 3 points within ±30°, triadic is 3 points 120° apart, split-complementary is 3 points at 0°/150°/210°.

The wheel alone says nothing about saturation or lightness, though. Two hues can be "harmonious" by angle and still feel unbalanced if one is vivid and the other muddy — so in practice you pick the angles first, then fine-tune saturation and lightness.

Picking one base hue and choosing which of these rules to apply is the fastest way to start any palette.

When to use

Use it when starting a palette from scratch, to decide which rule to build on.