05Figmaパネル — 3つの表記を互いに合わせて求め直すカラーピッカー
一つの色の値をHEX・RGB・HSLの3表記にそれぞれ計算し直し、3つの欄が常に同じ色を指すように揃えるカラーピッカーパネルです。
使いどころデザインコラボツールのカラーパネル、デザインシステムトークンエディタ
hexFieldrgbFieldPRESETS

<main class="stage">
<section class="panel">
<div class="swatch" id="swatch"></div>
<dl class="fields">
<div class="field">
<dt class="fieldLabel">HEX</dt>
<dd class="fieldValue" id="hexField">#000000</dd>
</div>
<div class="field">
<dt class="fieldLabel">RGB</dt>
<dd class="fieldValue" id="rgbField">rgb(0, 0, 0)</dd>
</div>
<div class="field">
<dt class="fieldLabel">HSL</dt>
<dd class="fieldValue" id="hslField">hsl(0deg 0% 0%)</dd>
</div>
</dl>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
@include stage($stage-ink);
.panel {
display: flex;
flex-direction: column;
align-items: stretch;
gap: $sp-4;
width: 100%;
max-width: 220px;
}
.swatch {
height: 64px;
border-radius: $r-card;
box-shadow: $shadow-raised;
}
.fields {
display: flex;
flex-direction: column;
gap: $sp-2;
margin: 0;
}
.field {
display: flex;
align-items: center;
justify-content: space-between;
gap: $sp-2;
padding: $sp-2 $sp-3;
border-radius: $r-sm;
background: rgba($subject-cream, .08);
}
.fieldLabel {
margin: 0;
color: $subject-cream;
opacity: .82;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 1.3;
letter-spacing: .04em;
}
.fieldValue {
margin: 0;
color: $subject-cream;
font-family: "Space Mono", ui-monospace, monospace;
font-weight: 700;
font-size: 12px;
line-height: 1.3;
}
@media (max-width: 340px) {
.panel { max-width: 168px; }
.swatch { height: 48px; }
.fieldValue { font-size: 11px; }
}
@media (prefers-reduced-motion: reduce) {
.swatch { transition: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.panel {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 16px;
width: 100%;
max-width: 220px;
}
.swatch {
height: 64px;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
}
.fields {
display: flex;
flex-direction: column;
gap: 8px;
margin: 0;
}
.field {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 12px;
border-radius: 8px;
background: rgba(255, 247, 230, 0.08);
}
.fieldLabel {
margin: 0;
color: #fff7e6;
opacity: 0.82;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
font-weight: 600;
font-size: 10px;
line-height: 1.3;
letter-spacing: 0.04em;
}
.fieldValue {
margin: 0;
color: #fff7e6;
font-family: "Space Mono", ui-monospace, monospace;
font-weight: 700;
font-size: 12px;
line-height: 1.3;
}
@media (max-width: 340px) {
.panel {
max-width: 168px;
}
.swatch {
height: 48px;
}
.fieldValue {
font-size: 11px;
}
}
@media (prefers-reduced-motion: reduce) {
.swatch {
transition: none;
}
}// Figma 패널(05) — 하나의 색 값을 HEX·RGB·HSL 세 표기법으로 각각 다시 계산해,
// 세 필드가 항상 같은 색을 가리키도록 서로 맞춘다. 실키·실서버 요청 없음.
(function () {
var swatchEl = document.getElementById('swatch');
var hexField = document.getElementById('hexField');
var rgbField = document.getElementById('rgbField');
var hslField = document.getElementById('hslField');
if (!swatchEl || !hexField || !rgbField || !hslField) return;
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var CYCLE_MS = 2600;
var PRESETS = [
{ h: 10, s: 72, l: 54 },
{ h: 70, s: 60, l: 48 },
{ h: 130, s: 55, l: 42 },
{ h: 190, s: 65, l: 50 },
{ h: 250, s: 60, l: 58 },
{ h: 310, s: 68, l: 52 },
{ h: 370, s: 72, l: 54 }
];
var SEG_MS = CYCLE_MS / (PRESETS.length - 1);
function hslToRgb(h, s, l) {
h = h % 360;
s /= 100; l /= 100;
var k = function (n) { return (n + h / 30) % 12; };
var a = s * Math.min(l, 1 - l);
var f = function (n) { return l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); };
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
}
function toHex(n) {
return n.toString(16).padStart(2, '0');
}
function apply(t) {
var idx = Math.min(PRESETS.length - 2, Math.floor(t / SEG_MS));
var frac = (t % SEG_MS) / SEG_MS;
var a = PRESETS[idx];
var b = PRESETS[idx + 1];
var h = a.h + (b.h - a.h) * frac;
var s = a.s + (b.s - a.s) * frac;
var l = a.l + (b.l - a.l) * frac;
var hMod = h % 360;
var rgb = hslToRgb(h, s, l);
var hex = '#' + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
var hslStr = 'hsl(' + hMod.toFixed(0) + 'deg ' + s.toFixed(0) + '% ' + l.toFixed(0) + '%)';
var rgbStr = 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
swatchEl.style.background = hslStr;
hexField.textContent = hex;
rgbField.textContent = rgbStr;
hslField.textContent = hslStr;
}
if (reduce) {
apply(0);
return;
}
var start = null;
function loop(ts) {
if (start === null) start = ts;
var t = (ts - start) % CYCLE_MS;
apply(t);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
})();import { useEffect, useRef } from "react";
import styles from "./ColorPickerFigmaPanel.module.scss";
// Figma 패널 — 하나의 색 값을 HEX·RGB·HSL 세 표기법으로 각각 다시 계산해,
// 세 필드가 항상 같은 색을 가리키도록 서로 맞춘다. 실키·실서버 요청 없음.
const CYCLE_MS = 2600;
const PRESETS = [
{ h: 10, s: 72, l: 54 },
{ h: 70, s: 60, l: 48 },
{ h: 130, s: 55, l: 42 },
{ h: 190, s: 65, l: 50 },
{ h: 250, s: 60, l: 58 },
{ h: 310, s: 68, l: 52 },
{ h: 370, s: 72, l: 54 },
];
const SEG_MS = CYCLE_MS / (PRESETS.length - 1);
function hslToRgb(h: number, s: number, l: number): [number, number, number] {
h = h % 360;
s /= 100;
l /= 100;
const k = (n: number) => (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
const f = (n: number) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
}
function toHex(n: number) {
return n.toString(16).padStart(2, "0");
}
export default function ColorPickerFigmaPanel() {
const swatchRef = useRef<HTMLDivElement>(null);
const hexRef = useRef<HTMLElement>(null);
const rgbRef = useRef<HTMLElement>(null);
const hslRef = useRef<HTMLElement>(null);
useEffect(() => {
const swatch = swatchRef.current;
const hexField = hexRef.current;
const rgbField = rgbRef.current;
const hslField = hslRef.current;
if (!swatch || !hexField || !rgbField || !hslField) return;
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function apply(t: number) {
const idx = Math.min(PRESETS.length - 2, Math.floor(t / SEG_MS));
const frac = (t % SEG_MS) / SEG_MS;
const a = PRESETS[idx];
const b = PRESETS[idx + 1];
const h = a.h + (b.h - a.h) * frac;
const s = a.s + (b.s - a.s) * frac;
const l = a.l + (b.l - a.l) * frac;
const hMod = h % 360;
const rgb = hslToRgb(h, s, l);
const hex = "#" + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
const hslStr = `hsl(${hMod.toFixed(0)}deg ${s.toFixed(0)}% ${l.toFixed(0)}%)`;
const rgbStr = `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
swatch!.style.background = hslStr;
hexField!.textContent = hex;
rgbField!.textContent = rgbStr;
hslField!.textContent = hslStr;
}
if (reduce) {
apply(0);
return;
}
let start: number | null = null;
let raf = 0;
function loop(ts: number) {
if (start === null) start = ts;
const t = (ts - start) % CYCLE_MS;
apply(t);
raf = requestAnimationFrame(loop);
}
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);
return (
<div className={styles.stage}>
<section className={styles.panel}>
<div className={styles.swatch} ref={swatchRef} />
<dl className={styles.fields}>
<div className={styles.field}>
<dt className={styles.fieldLabel}>HEX</dt>
<dd className={styles.fieldValue} ref={hexRef}>#000000</dd>
</div>
<div className={styles.field}>
<dt className={styles.fieldLabel}>RGB</dt>
<dd className={styles.fieldValue} ref={rgbRef}>rgb(0, 0, 0)</dd>
</div>
<div className={styles.field}>
<dt className={styles.fieldLabel}>HSL</dt>
<dd className={styles.fieldValue} ref={hslRef}>hsl(0deg 0% 0%)</dd>
</div>
</dl>
</section>
</div>
);
}05 Figmaパネル — 3つの表記を互いに合わせて求め直すカラーピッカー — 計測 累積面積 10.797% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · html カラーピッカー デザイン9選 — ホイールから履歴色まで
このデモが属する記事
html カラーピッカー デザイン9選 — ホイールから履歴色まで「html カラーピッカー デザイン」で検索すると、色を選ぶ見た目だけを真似た静止画像がほとんど出てきます。ここで扱う9つの画面はその手前に計算の層を1枚重ねます。選択地点の角度が色相値へ求め直され、スウォッチの明るさで チェックアイコンの この記事のデモをすべて見る


