How I Built a Color Picker That Actually Converts Colors Correctly (HEX/RGB/HSL)

작성자

카테고리:

← 피드로
DEV Community · ggwork · 2026-08-12 개발(SW)

While working on a design system recently, I kept running into the same frustrating problem: I’d grab a color from Figma in HEX format, need it in HSL for a CSS variable, and end up bouncing between three different websites just to convert one value. Each site had its own UI quirks, some required JavaScript to be enabled, and none of them gave me a proper color scheme alongside the conversion.

So I did what any reasonable developer would do — I built my own. Because apparently I enjoy reinventing wheels.

The Problem With Existing Solutions

The existing color converter tools online weren’t bad, but they had a few issues that bugged me:

  1. They were slow — many loaded heavy JavaScript libraries just to do simple math
  2. They lacked context — I wanted to see complementary colors and schemes alongside the conversion
  3. They were ad-heavy — I don’t want to dodge pop-ups while trying to match a shade of blue

I wanted something that felt like a native tool: instant, offline-capable, and comprehensive. A single HTML file that I could open, use, and close without ceremony.

The Architecture Decision

The first decision was whether to use a library or write the conversion logic myself. Libraries like color (npm) are battle-tested, but they add weight. Since this is a browser-only tool with no build step, I decided to write the conversions in vanilla JavaScript.

Here’s the core conversion logic that handles the heavy lifting:

function hslToRgb(h, s, l) {
  s /= 100;
  l /= 100;
  const k = n => (n + h / 30) % 12;
  const a = s * Math.min(l, 1 - l);
  const f = n => 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)];
}

Enter fullscreen mode Exit fullscreen mode

This is the most concise HSL-to-RGB conversion I know. It’s a compact version of the standard formula that avoids the typical case-based approach. The math checks out for all edge cases, including grayscale (when s = 0).

AI-Assisted Development: The Honest Take

I built this tool with heavy AI assistance, and I want to be transparent about how that went. I described my requirements to Claude, iterated on the design, and let it generate most of the initial code. Here’s what I learned:

What the AI Got Right

The AI was excellent at generating the initial structure — the HTML skeleton, CSS variables for theming, and the basic event handling. It had a solid grasp of the i18n pattern I wanted, with a translation dictionary and a t() function that looked up keys.

Where It Stumbled

The first version had a subtle but critical bug in the color conversion. When I tested it with a known color (e.g., #3b82f6 which should be hsl(217, 91%, 60%)), the HSL output was slightly off. The AI had implemented the conversion with floating-point rounding errors that accumulated.

I had to step in and rewrite the conversion functions myself, using the compact formula above. This was a reminder that while AI can generate code, it doesn’t always validate the output against real-world test cases.

The Iteration Process

Getting the AI to fix the issue took several rounds. I’d paste the buggy output, explain what was wrong, and it would suggest a fix. Sometimes the fix worked, sometimes it introduced new bugs. The most effective approach was to:

  1. Give it a specific test case with expected output
  2. Ask it to explain the math before writing the code
  3. Verify the output manually

Here’s the RGB to HSL conversion that I ended up with after iterating:

function rgbToHsl(r, g, b) {
  r /= 255;
  g /= 255;
  b /= 255;
  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  let h, s, l = (max + min) / 2;

  if (max === min) {
    h = s = 0;
  } else {
    const d = max - min;
    s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
    switch (max) {
      case r: h = (g - b) / d + (g < b ? 6 : 0); break;
      case g: h = (b - r) / d + 2; break;
      default: h = (r - g) / d + 4;
    }
    h /= 6;
  }
  return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
}

Enter fullscreen mode Exit fullscreen mode

The Color Scheme Generation

Beyond just conversion, I wanted the tool to suggest color schemes. This is where the HSL model shines — you can rotate the hue to get complementary colors.

The logic is straightforward: take the current hue, add specific offsets, and you get different scheme types.

function generateSchemes(h, s, l) {
  return {
    complementary: `hsl(${(h + 180) % 360}, ${s}%, ${l}%)`,
    analogous: [
      `hsl(${(h + 30) % 360}, ${s}%, ${l}%)`,
      `hsl(${(h - 30 + 360) % 360}, ${s}%, ${l}%)`
    ],
    triadic: [
      `hsl(${(h + 120) % 360}, ${s}%, ${l}%)`,
      `hsl(${(h + 240) % 360}, ${s}%, ${l}%)`
    ],
    monochromatic: [0.7, 0.85, 1, 1.15, 1.3].map(mult => 
      `hsl(${h}, ${s}%, ${Math.min(100, l * mult)}%)`
    )
  };
}

Enter fullscreen mode Exit fullscreen mode

This runs in milliseconds and requires no external API calls. The monochromatic scale uses multipliers to create lighter and darker variations of the same hue.

Handling Edge Cases

One thing that surprised me was how many edge cases exist in color parsing. Users will type anything into those input fields. Here’s how I handled it:

function parseHex(input) {
  let hex = input.trim().replace(/^#/, '');
  if (hex.length === 3) {
    hex = hex.split('').map(c => c + c).join('');
  }
  if (!/^[0-9a-fA-F]{6}$/.test(hex)) {
    throw new Error('Invalid HEX format');
  }
  return hex;
}

Enter fullscreen mode Exit fullscreen mode

The 3-digit shorthand (e.g., #3bf) was something users expected, so I expanded it. Invalid input throws an error that gets caught and displayed as a friendly message rather than crashing the app.

The i18n Pattern

Since this tool might be used by developers worldwide, I wanted it to support both English and Chinese. The pattern is simple: a dictionary object and a t() function.

const i18n = {
  zh: {
    'title': '颜色选择器',
    'copy': '复制',
    'copied': '已复制'
  },
  en: {
    'title': 'Color Picker',
    'copy': 'Copy',
    'copied': 'Copied'
  }
};

function t(key) {
  return i18n[currentLang][key] || i18n['zh'][key] || key;
}

Enter fullscreen mode Exit fullscreen mode

Language detection prioritizes URL parameters over browser settings. This was a deliberate choice — developers often want to force a specific language regardless of their browser locale.

Performance Considerations

This tool has zero dependencies and no network requests. The entire thing is a single HTML file that loads instantly. The only “heavy” operation is generating color schemes, which is pure math and takes microseconds.

I did consider using canvas for a more sophisticated color picker (like the color wheel in Photoshop), but the native <input type="color"> element is surprisingly good and requires zero code. The trade-off is less visual polish, but the reliability and cross-browser consistency win.

What I’d Do Differently

Looking back, there are a few things I’d improve:

  1. Add a color history — remembering recently used colors would be genuinely useful
  2. Support for CSS variables output — generating --color-name: #hex would be handy
  3. Named color suggestions — mapping hex values to CSS color names

The AI-Assisted Development Verdict

Working with AI on this project was a mixed experience. It excelled at:

  • Generating boilerplate and structure quickly
  • Understanding design patterns and CSS theming
  • Writing the i18n infrastructure

But it struggled with:

  • Precise mathematical implementations
  • Understanding edge cases without explicit examples
  • Validating its own output against real test cases

My advice: use AI for the scaffolding, but always verify the critical logic yourself. For a color converter, that means testing with known values. For anything else, it means writing unit tests.

Final Thoughts

Building this tool taught me that the simplest solutions often require the most careful math. The HSL-to-RGB conversion looks like a one-liner, but getting it right took real attention to detail.

If you’re working on a project that needs color conversion, I’d encourage you to implement it yourself rather than reaching for a library. The math is well-documented, and you’ll gain a deeper understanding of how colors work in the browser.

During this process, I built a small browser-based tool to make this workflow easier. You can try it here if you’re curious about the implementation.

Tags: javascript, css, webdev, colors, tutorial

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다