บทเรียนแนะนำ

Color Interpolation ใน CSS: เหตุใด OKLCH จึงชนะ

อ่าน 8 นาที

Every time CSS blends two colors — in a gradient, in color-mix(), or in a transition — it performs color interpolation: calculating intermediate values between a start color and an end color. For most of the web's history, that calculation happened in the sRGB color space, and the results were often muddy, desaturated, or perceptually uneven. With modern CSS, you can now choose the color space for interpolation. The choice matters enormously, and OKLCH consistently produces the best results.

This tutorial explains why RGB interpolation fails, where HSL falls short, and why OKLCH is the right default for gradients and color blending in 2024 and beyond.

The RGB Interpolation Problem: Muddy Midpoints

When you write a CSS gradient without specifying a color space, the browser interpolates in sRGB — it calculates intermediate colors by linearly blending the R, G, and B channel values independently.

This seems reasonable, but it breaks down badly when the two endpoints pass through the middle of the sRGB cube. Consider a gradient from red to green:

/* Default: interpolates in sRGB */
background: linear-gradient(to right, #FF0000, #00FF00);

The midpoint of this gradient in sRGB is #808000 — a dull olive. Mathematically, RGB 128, 128, 0 is exactly halfway between red and green in channel values. But perceptually, it looks nothing like a vibrant midpoint between two vivid primaries. The gradient sags through a muddy, dark valley before brightening on the other side.

The fundamental problem is that sRGB is not perceptually uniform. Equal numerical steps in R, G, and B do not correspond to equal steps in perceived color or brightness. The sRGB cube was designed for display hardware, not for human vision.

/* You can force sRGB explicitly */
background: linear-gradient(in srgb, red, blue);

/* Or use the legacy syntax (same result) */
background: linear-gradient(red, blue);

Both produce the same desaturated midpoints. The issue is the color space itself, not the syntax.

The Gamma Problem

There is another subtlety: sRGB values are gamma-encoded. Raw sRGB values of 0.5 do not correspond to 50% of the physical light emitted by a display. The actual perceptual midpoint of a gradient needs to be calculated in linear light, not in gamma-encoded sRGB. CSS level 4 introduced in srgb-linear to address this:

background: linear-gradient(in srgb-linear, red, blue);

Linear sRGB is better than gamma sRGB for luminance transitions, but it still does not solve the hue-shift and saturation-drop problems because it is still a rectangular RGB space.

HSL Hue Shifts

HSL was introduced as a more human-readable alternative to RGB. Its cylindrical representation — Hue on a 0–360 angle, Saturation and Lightness as percentages — makes it easier to reason about color relationships. But HSL interpolation has its own characteristic failure mode: hue shifts.

When you interpolate between two HSL colors, the browser travels along the shortest arc of the hue circle. If you go from a warm red (hsl(10, 100%, 50%)) to a cool blue (hsl(220, 100%, 50%)), the shortest path passes through purple and violet — which may or may not be what you want.

But the deeper problem is that HSL's Lightness is not perceptually uniform. A yellow at hsl(60, 100%, 50%) looks dramatically brighter than a blue at hsl(240, 100%, 50%), even though they share the same L value. When you interpolate from yellow to blue in HSL, the perceived brightness lurches and sags in ways that feel incoherent.

/* HSL gradient — perceptual brightness is uneven across hues */
background: linear-gradient(in hsl, hsl(60 100% 50%), hsl(240 100% 50%));

The midpoint of this gradient at hsl(150, 100%, 50%) is a vivid green that appears perceptually brighter than either endpoint. The gradient peaks in the middle rather than transitioning smoothly.

HSL interpolation also has a long-arc problem: the hue angle wraps at 360°, and if you interpolate from hsl(350, ...) (near-red) to hsl(20, ...) (orange), HSL may travel the long way around the hue circle through blue, green, and yellow rather than the short way through red.

OKLCH: Consistency and Perceptual Uniformity

OKLCH is a cylindrical form of the Oklab color space, designed by Björn Ottosson in 2020 specifically to address the perceptual uniformity shortcomings of earlier color spaces. Its three channels — L (lightness), C (chroma), and H (hue angle) — are calibrated so that equal numerical changes in L produce equal changes in perceived brightness, regardless of the hue.

When you interpolate between two OKLCH colors, you get:

  1. Consistent perceived brightness throughout the transition — no sagging into dark midpoints or lurching bright peaks.
  2. Preserved chroma — the midpoints stay vivid because chroma is interpolated independently of hue.
  3. Shorter, more intuitive hue paths — OKLCH's hue angles are distributed in perceptual order, so interpolation travels through the expected intermediate hues.
/* OKLCH gradient — vivid, perceptually even */
background: linear-gradient(in oklch, red, blue);

A gradient from red to blue in OKLCH travels through vivid magenta and purple — the perceptually intuitive midpoints. It stays saturated and evenly bright throughout.

Side-by-Side Comparison

Gradient Midpoint Color Perceived Quality
in srgb from red to green #808000 — dull olive Muddy, dark valley
in hsl from yellow to blue Vivid green peak Uneven brightness, color spike
in oklch from red to green Vivid warm yellow Smooth, saturated, even

To see the difference yourself, use the Gradient Generator which lets you preview gradients across different color spaces.

How to Convert Your Colors to OKLCH

You do not need to rewrite every color value in your CSS. The in oklch flag on a gradient tells the browser to convert the endpoint colors to OKLCH before interpolating, then convert back to the output color space. You can keep your HEX values:

/* These HEX endpoints are automatically converted to OKLCH for interpolation */
background: linear-gradient(in oklch, #FF5733, #3498DB);

#FF5733 is approximately oklch(0.63 0.24 27) and #3498DB is approximately oklch(0.63 0.14 232). Use the Color Converter to check the OKLCH values of any HEX code before you design a gradient, so you know in advance what the interpolation will traverse.

The color-mix() Function

CSS color-mix() is the declarative API for blending two colors, available in all modern browsers since 2023. Its syntax includes the interpolation color space as a first argument:

color: color-mix(in oklch, #FF5733 50%, #3498DB);

This blends #FF5733 and #3498DB at equal proportions in OKLCH space. The result is a vivid intermediate that looks like a real perceptual midpoint between the two colors — not a muddy sRGB average.

Practical color-mix() Uses

Creating transparent variants:

:root {
  --brand: #2563EB;
  --brand-10: color-mix(in oklch, var(--brand) 10%, transparent);
  --brand-20: color-mix(in oklch, var(--brand) 20%, transparent);
  --brand-50: color-mix(in oklch, var(--brand) 50%, transparent);
}

Tinting toward white or black:

:root {
  --brand: #2563EB;
  --brand-light: color-mix(in oklch, var(--brand) 70%, white);
  --brand-dark:  color-mix(in oklch, var(--brand) 70%, black);
}

This is the same idea as a shade generator but fully in CSS, without preprocessing. The result in OKLCH tinting looks more natural than equivalent operations in HSL because the chroma shifts appropriately as you approach white or black.

Hover state derivation:

.button {
  background: var(--brand);
}
.button:hover {
  background: color-mix(in oklch, var(--brand) 80%, black);
}

Warning: sRGB color-mix()

If you omit the in <colorspace> argument, color-mix() defaults to sRGB interpolation:

/* This uses sRGB — may produce muddy results */
color: color-mix(#FF5733, #3498DB);

/* Correct: specify the space */
color: color-mix(in oklch, #FF5733, #3498DB);

Always specify in oklch (or another preferred space) explicitly. The default sRGB is preserved for backwards compatibility but is rarely the best choice.

Gradient Interpolation Color Space

Modern CSS syntax allows you to specify the interpolation color space directly in a gradient declaration using in <colorspace> after the direction keyword:

/* sRGB (default, often muddy) */
background: linear-gradient(to right, red, blue);

/* Linear sRGB (better luminance, still has hue issues) */
background: linear-gradient(in srgb-linear to right, red, blue);

/* HSL (hue shifts, uneven brightness) */
background: linear-gradient(in hsl to right, red, blue);

/* OKLCH (recommended: vivid, perceptually uniform) */
background: linear-gradient(in oklch to right, red, blue);

/* Oklab (also excellent, less intuitive syntax) */
background: linear-gradient(in oklab to right, red, blue);

This syntax applies to all gradient types: linear-gradient, radial-gradient, and conic-gradient.

Hue Interpolation Direction in OKLCH

For hue interpolation specifically, you can control whether the gradient takes the shorter or longer arc around the hue circle:

/* Shorter arc (default) */
background: linear-gradient(in oklch, hsl(30 100% 50%), hsl(270 100% 50%));

/* Longer arc — travels through yellow, green, cyan, then to purple */
background: linear-gradient(in oklch longer hue, hsl(30 100% 50%), hsl(270 100% 50%));

/* Increasing arc — always goes in the direction of increasing hue angle */
background: linear-gradient(in oklch increasing hue, hsl(30 100% 50%), hsl(270 100% 50%));

The shorter hue keyword (default) gives the most natural result in most cases. The longer hue variant is useful for rainbow effects or when you specifically want the gradient to traverse a wide portion of the hue spectrum.

Multi-Stop Gradients

OKLCH interpolation becomes even more valuable for multi-stop gradients, because each segment interpolates through perceptually vivid midpoints:

/* A rainbow gradient that stays vivid throughout */
background: linear-gradient(
  in oklch to right,
  oklch(0.70 0.20 30),   /* orange-red */
  oklch(0.80 0.20 90),   /* yellow */
  oklch(0.75 0.20 150),  /* green */
  oklch(0.65 0.20 240),  /* blue */
  oklch(0.60 0.20 300)   /* purple */
);

Because the L and C values are perceptually calibrated, setting them to similar values across all stops results in a rainbow that looks naturally bright and saturated, without the luminance dips common in sRGB rainbows.

Browser Support

The in <colorspace> gradient syntax is supported in all major browsers:

  • Chrome/Edge: Full support since version 111 (March 2023)
  • Firefox: Full support since version 113 (May 2023)
  • Safari: Full support since version 16.2 (December 2022)

For older browsers, fallback gracefully with the legacy syntax first:

/* Fallback: sRGB gradient */
background: linear-gradient(to right, #FF5733, #3498DB);

/* Modern: OKLCH interpolation */
@supports (background: linear-gradient(in oklch, red, blue)) {
  background: linear-gradient(in oklch to right, #FF5733, #3498DB);
}

Key Takeaways

  • sRGB interpolation (the CSS default) produces muddy, desaturated midpoints when gradients pass near the center of the sRGB cube — particularly visible in red-to-green and complementary color gradients.
  • HSL interpolation avoids some RGB problems but introduces inconsistent perceived brightness — its Lightness channel is not perceptually uniform — and can produce unexpected hue paths.
  • OKLCH interpolation produces vivid, perceptually consistent midpoints because it was designed around human vision. Gradients in OKLCH stay saturated and evenly bright across their full range.
  • Use in oklch in any gradient: linear-gradient(in oklch to right, red, blue). Use color-mix(in oklch, color1 50%, color2) for programmatic blending.
  • The Color Converter lets you convert HEX or RGB values to OKLCH so you can understand how your colors will interpolate before committing to a gradient design.
  • The Gradient Generator lets you preview how different color spaces affect gradient output for any pair of colors.
  • Hue interpolation direction can be controlled with shorter hue, longer hue, increasing hue, and decreasing hue keywords — useful for rainbow effects and precise multi-stop gradient control.

สีที่เกี่ยวข้อง

แบรนด์ที่เกี่ยวข้อง

เครื่องมือที่เกี่ยวข้อง