[CSS] Gradient의 Grey Dead Zone

이춘구·2022년 1월 20일
0
post-thumbnail

Grey Dead Zone이란?

위의 gradient는 노란색(RGB 255, 255, 0)과 파란색(RGB 0, 0, 255)으로 만들었는데, 중간에 색이 바래고 흐릿한 회색 구간이 있는 걸 볼 수 있다.

어떤 색을 골라 gradient를 만드느냐에 따라 종종 이런 구간이 생기며, 이 구간을 Grey Dead Zone이라 한다.

양 끝단의 색상이 자연스럽게 연결되어 있어야 할 gradient의 중간에 색이 죽어있으니 연결이 끊어진 것처럼 보여서 시각적으로 좋지 않다.

Grey Dead Zone이 생기는 원인

css가 gradient를 생성하는 방법

css는 gradient를 생성할 때 RGB 값의 수학적 평균값을 사용한다.

노란색과 파란색으로 생성된 gradient에 사용된 색들의 RGB 값을 주욱 나열해보면
R 값은 255 => 0, G값은 255 => 0, B 값은 0 => 255로 증가, 감소한다.

그렇다면 필연적으로 정중앙에 위치한 색의 RGB 값들은 0과 255의 평균값인 127.5가 되는데,
RGB 체계에서 RGB의 각 값들이 모두 같은 색은 무채색 계열이다.

채도가 아주 높은 두가지 색상을 조합했는데 그 결과물이 채도가 없는 색이라는 건 이상하다. 채도는 유지하면서 색상만 조합하려면 어떻게 해야할까?

해결책

컬러 모드 변경

Grey Dead Zone이 RGB 색상의 표현 방식으로 인해 나타나는 문제이므로, 또다른 컬러 모드 중 하나인 HSL로 변경함으로써 해결할 수 있다.

HSL

HSL은 Hue(색조), Saturation(채도), Lightness(명도)로 색을 표현하는 모델이다.
HSL 컬러 모드에서 노란색은 hsl(60, 100%, 50%), 파란색은 hsl(240, 100%, 50%)의 값을 가지며,
saturationlightness는 동일하고 hue만 다른 것을 알 수 있다.
채도와 명도는 각각 100%와 50%로 고정하고 색조만 240부터 60까지 서서히 감소하는 색들을 나열해 gradient를 만들면, 아래와 같이 Grey Dead Zone이 사라진다.

아쉬운 점은 아래의 css 코드처럼 gradient의 색상 값으로 hsl 값을 넣는다고 해서 위와 같은 결과물이 나오지 않는다는 것이다.

background-image: linear-gradient(
  to right,
  hsl(60, 100%, 50%),
  hsl(240, 100%, 50%)
  );

결과물

CSS는 HSL 색상 모드로 입력된 값을 RGB로 변환시켜 평균값을 내기 때문에 gradient의 중앙에 들어가는 색은 여전히 rgb(128, 128, 128)이 된다.

그래서 우리가 바라는 결과물을 만들기 위해선 직접 두 Hue 값의 평균을 내어 중간값으로 입력해주어야 한다.

background-image: linear-gradient(
  to right,
  hsl(60, 100%, 50%),
  hsl(150, 100%, 50%),
  hsl(240, 100%, 50%)
);

직접 중간의 색상값을 추가하면 아래와 같은 결과물을 얻을 수 있다.

해결 코드

동일한 색을 RGB나 HSL는 다르게 표기하지만, 16진수는 같게 표기하기 때문에 16진수 컬러 코드를 입출력값으로 사용하겠다.

아래 2개의 16진수 컬러 코드들(노랑, 파랑)의 중간에 양측의 평균값을 넣어서 컬러 코드 3개의 배열로 만들어보겠다.

const hexValues = ['#ffff00', '#0000ff']

먼저 위 16진수 코드들을 HSL값으로 변환하면 이차원 배열이 된다.

const HSLs = [
  [60, 100, 50],
  [240, 100, 50]
]

위 배열의 HSLs[0]HSLs[1]의 사이에
HSLs[0][0]HSLs[1][0]의 중간값을 H,
HSLs[0][1]HSLs[1][1]의 중간값을 S,
HSLs[0][2]HSLs[1][2]의 중간값을 L로 갖는 배열을 만들어 추가해야 한다.

계산을 편하게 하기 위해 계산해야 하는 값들끼리 묶는데, 그렇게 하면 행과 열을 뒤바꾸게(transpose) 된다.

// 1행 <-> 1열, 2행 <-> 2열이 되었다.
const transposedHSLs = [
  [60, 240],
  [100, 100],
  [50, 50]
]

위 배열들의 요소들 사이에 양측 요소들의 평균값을 계산해서 넣어준다.

const interpolatedHSLs = [
  [60, 150, 240],
  [100, 100, 100],
  [50, 50, 50]
]

행과 열을 다시 뒤바꾸면 HSL 색상이 3개가 되었다.

const transposedInterpolatedHSLs = [
  [60, 100, 50],
  [150, 100, 50],
  [240, 100, 50]
]

각 HSL 값을 16진수 컬러 코드로 변환하면 완성이다.

const resultHexValues = ['#ffff00', '#00ff80', '#0000ff']

위 과정을 코드로 옮기면 아래와 같다.

const colorCodeInputs = document.querySelectorAll(".color-code-input");
// 사용자로부터 입력받은 16진수 색상값을 배열로 만든다.
const hexValues = [...colorCodeInputs].map((input) => input.value);
// 입력받은 16진수 색상값들을 HSL로 변환한다.
const HSLs = hexValues.map((hexValue) => hexToHSL(hexValue));
// 각 값들의 중간값을 구해서 합친다.
const interpolatedHSLs = transpose(HSLs).map((HSL) => interpolate(HSL));
// HSL을 다시 16진수로 변환한다.
const resultHexValues = transpose(interpolatedHSLs).map((HSL) => HSLToHex(HSL));

// 배열의 각 요소 간 중간값을 추가해서 반환하는 함수
function interpolate(array) {
  const result = [];
  const length = array.length;
  
  for (let i = 0; i < length; i++) {
    // 현재 요소를 result 배열에 넣는다.
    result.push(array[i]);
    
    // 현재 요소가 마지막 요소라면 반복문을 종료한다.
    if (i === length - 1) break;

    // 현재 요소와 다음 요소의 평균값을 구한다.
    const average = (array[i] + array[i + 1]) / 2;
    // 소수점 둘째자리에서 반올림한다.
    const fixedNumber = Number(average.toFixed(1));
    // result 배열에 넣는다.
    result.push(fixedNumber);
  }

  return result;
}

// 이차원 배열의 행과 열을 뒤바꾸는 함수
function transpose(matrix) {
  return matrix.reduce(
    (result, row) => row.map((_, i) => [...(result[i] || []), row[i]]),
    []
  );
}

References

profile
프런트엔드 개발자

0개의 댓글