본문 바로가기

FrontEnd

리액트 개발자를 위한 CSS Variable

반응형

원문 : https://www.joshwcomeau.com/css/css-variables-for-react-devs/

 

CSS Variables for React Devs

CSS Variables are *really* cool, and they're incredibly powerful when it comes to React! This tutorial shows how we can use them with React to create dynamic themes. We'll see how to get the most out of CSS-in-JS tools like styled-components, and how our m

www.joshwcomeau.com

CSS 변수 정의 / 사용

일반 CSS 변수와 동일하게 선언

사용 시 var로 감싸서 사용함

CSS 변수에 대한 몇 가지 다른 간단한 사실:
  • 기존 CSS 속성의 차이점
    • var() 함수를 사용하여 해당 값에 액세스할 수 있습니다
    • 커스텀 속성은 두 개의 대시로 시작해야 합니다.
  • CSS 변수는 지역변수입니다.
  • 색상과 픽셀뿐만 아니라 모든 타입의 값을 보유할 수 있습니다.
  • CSS 변수가 정의되지 않은 경우 기본값을 지정할 수 있습니다.
    • var(--primary-color, pink)는 필요한 경우 분홍색으로 대체됩니다.

CSS 변수는 지역변수입니다.

<style>
  p {
    --highlight-color: yellow;
  }
  /* 효과 없음*/
  em {
    background: var(--highlight-color);
  }
</style>

<h1>
  This heading has some <em>emphasized text</em>!
</h1>

CSS 변수 전역 사용 방법

:root는 최상위 요소에 대한 참조로, html 셀렉터와 동일함.
/*
  This variable will be available everywhere,
  because every element is a descendant of
  the HTML tag:
*/
html {
  --color-red: hsl(0deg 80% 50%);
  --color-blue: hsl(270deg 75% 60%);
}
/* 혹은 */
:root {
  --color-red: hsl(0deg 80% 50%);
  --color-blue: hsl(270deg 75% 60%);
}

CSS 변수가 일반적인 CSS 속성과 다른 점은 var() 함수를 사용하여 해당 값에 액세스할 수 있다는 것입니다.


리액트 앱에서

styled-components를 씁시다.

아래와 같은 디자인 토큰이 있다면...

const COLORS = {
  text: 'black',
  background: 'white',
  primary: 'rebeccapurple',
};
const SIZES = [
  8,
  16,
  24,
  32,
  /* And so on */
];

React 앱에서 이를 필요로 하는 컴포넌트로 직접 가져올 수 있습니다. (soso)

import { COLORS } from '../constants';
const Button = styled.button`
  background: ${COLORS.primary};
`;

theme을 사용할 수 있습니다.(not good)

// components/App.js
import { ThemeProvider } from 'styled-components';
import { COLORS } from '../constants';
// This element wraps our entire application,
// to make the theme available via context.
const App = ({ children }) => {
  return (
    <ThemeProvider theme={{ colors: COLORS }}>
      {children}
    </ThemeProvider>
  );
};
// Elsewhere…
const Button = styled.button`
  background: ${(props) => props.theme.colors.primary};
`;

CSS 변수와 createGlobalStyle을 사용합니다. (Best)

import { createGlobalStyle } from 'styled-components';
const GlobalStyles = createGlobalStyle`
  html {
    --color-text: black;
    --color-background: white;
    --color-primary: rebeccapurple;
  }
`;
const App = ({ children }) => {
  return (
    <>
      <GlobalStyles />
      {children}
    </>
  );
};


변수 대신 값을 바꾸자

아래 컴포넌트에 반응형 스타일을 추가하고 싶음. 모바일이면 높이를 키우자. (44px and 48px tall)

CSS 변수가 없으면?

const Button = styled.button`
  /* Omitted other styles for brevity */
  min-height: 32px;
  @media (pointer: coarse) {
    min-height: 48px;
  }
`;
이 코드에 대한 두 가지 간단한 참고 사항:
  • 필요한 경우 버튼이 커질 수 있도록 height 대신 min-height를 사용하고 있습니다.
    • 사용자가 기본 글꼴 크기를 늘리거나 텍스트를 줄 바꿈해야 하는 경우 높이가 커질 수 있습니다.
  • 너비 기반 미디어 쿼리를 사용하는 대신 pointer:coarse를 사용하고 있습니다.
    • 우리는 실제로 화면의 크기에 관심이 없습니다. 정확히 클릭하거나 탭할 수 있는지 여부에 관심이 있습니다. 
    • 이 미디어 쿼리는 사용자의 기본 입력 메커니즘이 괜찮은지 거친지 여부를 추적합니다.
    • 마우스나 트랙패드는 픽셀에 대한 위치를 제어할 수 있기 때문에 "fine"으로 간주됩니다.
    • 손가락이나 Wii 리모컨은 덜 정확합니다. (coarse)

탭할 수 있는 요소는 버튼만이 아닙니다. TextInput도 수정해 줍니다.

 

DRY를 위해 ThemeProvider를 이용하기

const App = ({ children }) => {
  return (
    <ThemeProvider
      theme={{
        colors: COLORS,
        coarseTapHeight: 48,
        fineTapHeight: 32,
      }}
    >
      {children}
    </ThemeProvider>
  );
};

아래와 같이 사용합니다만, 이제 탭 할 수 있는 모든 컴포넌트에 아래와 같은 코드를 작성해야 합니다...

const Button = styled.button`
  min-height: ${(props) => props.theme.fineTapHeight}px;
  @media (pointer: coarse) {
    min-height: ${(props) => props.theme.coarseTapHeight}px;
  }
`;
const TextInput = styled.input`
  min-height: ${(props) => props.theme.fineTapHeight}px;
  @media (pointer: coarse) {
    min-height: ${(props) => props.theme.coarseTapHeight}px;
  }
`;

DRY를 위해 Css Variables를 이용하기

컴포넌트 별로 미디어 쿼리를 적용하는 대신, 변수가 뷰포트 사이즈에 반응하게 하면 되지 않을까요?

const GlobalStyles = createGlobalStyle`
  html {
    --min-tap-target-height: 32px;
    @media (pointer: coarse) {
      --min-tap-target-height: 48px;
    }
  }
`;

이 마법 같은 CSS 변수를 사용하면 반응형 컴포넌트 구축이 더욱 간단해 집니다.
const Button = styled.button`
  min-height: var(--min-tap-target-height);
`;
const TextInput = styled.input`
  min-height: var(--min-tap-target-height);
`;

  • 중단점 항목을 한 곳에서 통합하여 이제 단일 진실 원천을 갖게 되었습니다.
    • 이전에는 방심하는 개발자가 실수로 중단점 중 하나를 삭제하여 일관성 없는 동작을 초래할 수 있었습니다. 이제 탄력적인 변수로 패키징됩니다.
  • 우리가 왜 이것을 하는지에 대해 더 명확하게 할 수 있습니다.
    • min-tap-target-height라는 이름을 지정하여 처음부터 min-height 값을 설정해야 하는 이유를 전달합니다.
  • 더 선언적입니다!
    • 포인터 유형에 따라 각 구성 요소가 어떻게(how) 변경되어야 하는지 지정하는 대신 값이 어떠해야(what) 하는지 알려줍니다.
"최소 지식의 원칙("Principle of Least Knowledge")"은
코드가 코드베이스의 완전히 다른 부분에 "도달"해서는 안되고 바로 인접한 항목에만 액세스할 수 있어야 한다는 아이디어입니다.
같은 생각이 여기에 적용되는 것 같아요.

"Principle of Least Knowledge"

 

테마 변수에도 비슷한 아이디어를 적용할 수 있습니다.

모든 창 사이즈에 대해 동일한 크기를 사용하는 대신, 각 중단점에 대한 커스텀 크기를 가질 수 있습니다.

const GlobalStyles = createGlobalStyle`
  html {
    --space-sm: 8px;
    --space-md: 16px;
    @media (min-width: 1024px) {
      --space-sm: 16px;
      --space-md: 32px;
    }
  }
`;
// Elsewhere...
const Paragraph = styled.p`
  padding: var(--space-sm);
`;


새로운 가능성

지금까지는 개발 측면에서 문제를 다루었다면, 이제 UX관점에서의 문제를 다뤄봅시다.

 

아무 속성이나 애니메이팅 하기

애니메이션화 할 수 없는 일부 CSS 속성이 있습니다.
예를 들어 선형 또는 방사형 그래디언트에 애니메이션을 시도한 적이 있다면 동작하지 않는 것을 꽤 빨리 깨달았을 것입니다.
CSS 변수를 사용하면 속성에 전환을 적용하는 것이 아니라 값에 전환을 적용하기 때문에 모든 속성에 애니메이션을 적용할 수 있습니다.
예를 들어, 다음은 CSS 변수로 가능한 재미있는 그래디언트 애니메이션입니다.
 
구경하기 : 
https://www.joshwcomeau.com/css/css-variables-for-react-devs/#:~:text=with%20CSS%20variables%3A-,Swirly,-!

해당 버튼 만들기 :

"Magical Rainbow Gradients"

다크 모드 반짝임 고치기

"다크 모드"는 서버 렌더링 컨텍스트(예: Next.js 또는 Gatsby)에서 놀라울 정도로 까다롭습니다.
HTML이 사용자의 장치에 도달하기 훨씬 전에 생성되기 때문에 사용자가 선호하는 색상 테마를 알 수 있는 방법이 없다는 것입니다.
CSS 변수와 약간의 영리함으로 이 문제를 해결할 수 있습니다.
내 블로그 게시물인 완벽한 다크 모드를 위한 퀘스트(The Quest for the Perfect Dark Mode)를 참조하세요.

값을 얻고 설정하기

위의 예에서는 GlobalStyles 컴포넌트에 테마 값을 하드코딩합니다.
하지만 JavaScript에서 이 테마 값에 액세스해야 하는 경우가 있습니다.
const GlobalStyles = createGlobalStyle`
  html {
    --color-text: black;
    --color-background: white;
    --color-primary: rebeccapurple;
  }
`;

원한다면 그것들을 constants.js 파일에 저장할 수 있습니다.
  • CSS 변수를 인스턴스화하는 데에도 사용할 수 있고,
  • JS에서 raw 값이 필요한 모든 곳에서 가져올 수도 있습니다.
const GlobalStyles = createGlobalStyle`
  html {
    --color-text: ${COLORS.text};
    --color-background:  ${COLORS.background};
    --color-primary:  ${COLORS.primary};
  }
`;
또 다른 아이디어는 CSS를 진실의 소스로 사용하는 것입니다.
약간의 JS로 값에 액세스할 수 있습니다.
getComputedStyle(document.documentElement)
  .getPropertyValue('--color-primary');
JS 내에서도 이러한 값을 설정할 수 있습니다.
// 🚨 This is an editable code snippet!! 🚨
// Try changing the value of `color-primary`,
// and check out the site's logo in the top left!
document.documentElement.style.setProperty(
  '--color-primary',
  'hsl(230deg, 100%, 67%)'
);
JS에서 CSS 변수를 가져오고 설정할 일은 거의 없습니다.
임베디드 SVG 내에서 CSS 변수를 사용할 수도 있습니다 😮

단점

타입 없음

테마에 CSS 변수를 사용하는 것의 가장 큰 단점은

Typescript 또는 Flow를 통해  타입을 정적으로 입력할 방법이 없다는 것입니다.

(스타일을 객체에 보관하고, 해당 객체에 접근하는 함수를 노출하면 되긴 합니다. Check out This tweet)

cvar라는 색상 테마에 접근할 수 있는 함수 노출
내 생각에 이것은 큰 문제가 아닙니다.
타입이 지정된 테마 개체를 갖는 것은 좋지만 많은 시간을 절약했다고 ​​말할 수는 없습니다.
일반적으로 CSS 변수의 이름을 잘못 입력하는 것은 피드백이 명백하여 빨리 수정할 수 있습니다.
 
컴파일 타임 검사를 실행하는 것이 중요하다고 생각하지만
Chromatic과 같은 도구가 훨씬 더 안정적인 검사라고 생각합니다.
CI에서 실행되고 렌더링된 시각적 출력의 차이점을 캡처합니다.

브라우저 지원

IE를 지원하지 않습니다.


느슨하지 않음

styled-components를 사용하여 미디어 쿼리 내를 포함하여 원하는 곳 어디에나 변수를 넣을 수 있습니다.
const Ymca = styled.abbr`
  font-size: 1rem;
  @media (max-width: ${(p) => p.bp.desktop}) {
    font-size: 1.25rem;
  }
`;
CSS 변수는 미디어 쿼리 내에서 사용할 수 없습니다.
사용자가 env()를 사용하여 자신의 환경 변수를 설명하도록 하는 것에 대한 논의가 있으나,
아직 구체화되지 않았습니다.
 
그러나 이러한 단점에도 불구하고 CSS 변수는 정말 흥미로운 많은 문을 열어줍니다.
나는 몇 년 동안 그것들을 사용해 왔고(바로 이 블로그를 포함해서!) 그것들을 사랑합니다.
동적 스타일을 관리하는 데 선호하는 방법입니다. 

 

 

 

반응형