React

Scale Feedback

커스텀 컴포넌트에 SEED의 Scale Feedback을 적용하는 방법을 알아봅니다.

SEED 컴포넌트는 눌렸을 때 요소가 살짝 줄어드는 Scale Feedback을 기본으로 제공합니다. 작은 아이콘 버튼과 뷰포트 좌우를 채우는 버튼의 눌린 느낌이 같도록 만들기 위해, 축소 배율을 요소마다 다르게 계산합니다. 직접 만든 컴포넌트에도 같은 효과를 적용할 수 있습니다.

배율이 정해지는 방식

축소 배율을 고정값으로 정하는 대신, 요소의 실제 크기에서 배율을 계산합니다.

요소높이 × 폭계산된 배율실제 축소
아이콘 버튼32 × 320.938가로 2px, 세로 2px
텍스트 버튼48 × 1200.958가로 5px, 세로 2px
가로로 긴 버튼48 × 3430.977가로 8px, 세로 1.1px
작은 버튼22 × 220.917가로 1.8px, 세로 1.8px

배율은 렌더된 크기에서 계산되므로, 폰트 스케일링, 화면 폭 차이, 줄바꿈 등으로 인해 요소가 예상과 다른 크기로 그려져도 별도로 값을 조정할 필요가 없습니다.

적용하기

요소의 크기를 측정하는 일은 ScaleFeedback이 담당하고, 계산된 배율을 언제 어떤 요소에 적용할지는 각 사용처에서 직접 정의합니다.

요소 크기 측정하기

축소할 요소를 ScaleFeedback으로 감쌉니다.

MyButton.tsx
import { ScaleFeedback } from "@seed-design/react";

function MyButton({ className, ...props }) {
  return (
    <ScaleFeedback>
      <button className={className} {...props} />
    </ScaleFeedback>
  );
}

ScaleFeedback은 DOM 요소를 렌더하지 않고, 하위 요소에 refclassName을 전달합니다. 하위 요소가 이미 가진 refclassName은 유지됩니다.

자식 요소는 하나여야 하며, 받은 refclassName을 DOM 요소까지 전달해야 합니다. 직접 만든 컴포넌트를 자식으로 넘긴다면 Composition 문서의 조합 원칙을 함께 확인하세요.

ScaleFeedback 없이 사용하기

ScaleFeedback을 사용할 수 없는 경우 useScaleFeedback을 사용합니다. scaleFeedbackRefscaleFeedbackClassName을 같은 요소에 전달하세요.

MyButton.tsx
import { useScaleFeedback } from "@seed-design/react";
import clsx from "clsx";

function MyButton({ className, ...props }) {
  const { scaleFeedbackRef, scaleFeedbackClassName } = useScaleFeedback();

  return (
    <button
      ref={scaleFeedbackRef} 
      className={clsx(scaleFeedbackClassName, className)} 
      {...props}
    />
  );
}

계산된 배율 사용하기

계산된 배율은 --seed-feedback-scale, 트랜지션은 --seed-feedback-scale-transition CSS 변수로 공개됩니다.

JS로 스타일을 작성한다면 @seed-design/css/scale-feedback에서 feedbackScale, feedbackScaleTransition으로 가져올 수 있습니다.

MyButton.tsx
<ScaleFeedback>
  <button
    className={clsx(
      "bg-bg-brand-solid p-x3 rounded-r2",
      "[transition:background-color_0.2s,var(--seed-feedback-scale-transition)]", 
      "active:scale-(--seed-feedback-scale)", 
    )}
  />
</ScaleFeedback>

유틸리티 클래스 활용하기

배율을 여러 컴포넌트에 적용한다면 프로젝트에 유틸리티를 등록해두세요. active:scale-(--seed-feedback-scale) 대신 active:feedback-scale로 쓸 수 있습니다.

style.css
@import "tailwindcss";

@utility feedback-scale {
  scale: var(--seed-feedback-scale);
}
MyButton.tsx
<ScaleFeedback>
  <button
    className={clsx(
      "bg-bg-brand-solid p-x3 rounded-r2",
      "[transition:background-color_0.2s,var(--seed-feedback-scale-transition)]",
      "active:feedback-scale", 
    )}
  />
</ScaleFeedback>

@theme이 아니라 @utility에 등록하세요. @theme 항목은 :root에서 값이 확정되므로 요소가 자기 크기에서 계산한 배율이 도달하지 못합니다. 클래스는 생성되는데 축소만 일어나지 않습니다.

트랜지션은 같은 방식으로 묶을 수 없습니다. transition-* 유틸리티는 Tailwind 자신의 duration과 timing function을 함께 적용해서 --seed-feedback-scale-transition에 담긴 값을 덮어버립니다. 위처럼 arbitrary property 문법을 그대로 사용하세요.

선택자 고르기

SEED 컴포넌트는 상호작용 가능한 요소에 호버할 때 색상을 변경하고, 마우스 왼쪽 버튼을 누르고 있는 동안 Scale Feedback을 적용합니다. 일관성을 위해 커스텀 컴포넌트에서도 이 기준을 유지하는 것을 권장합니다. 자세한 내용은 Interaction States 문서를 참고하세요.

:active는 비활성 상태의 요소에도 적용됩니다. 비활성 상태를 다루는 컴포넌트라면 선택자에서 제외하세요. SEED 컴포넌트는 :disabled, [disabled], [data-disabled]를 제외하고 있습니다.

개별 scale 속성

SEED 컴포넌트는 축소를 transform: scale()이 아니라 개별 transform 속성인 scale로 적용합니다. 커스텀 컴포넌트에서도 scale을 사용하세요.

transform은 shorthand라서 한 요소에 하나의 값만 가집니다. 축소를 여기에 적으면 그 요소의 이동이나 회전을 같은 값에 함께 써야 하고 변형을 추가할 때마다 축소 값을 다시 챙겨야 합니다. 개별 scaletranslate, rotate와 독립적으로 동작해서 이런 부담이 없고 ScaleFeedback이 이미 scale을 사용하므로 축소가 한 속성에 모입니다.

개별 scale 속성은 Chrome 104, Safari 14.1, Firefox 72부터 지원됩니다. 이보다 낮은 버전에서는 축소만 나타나지 않고 나머지 스타일은 그대로 동작합니다.

두 단계를 모두 적용하면 아래와 같이 동작합니다. 크기가 다른 두 요소를 눌러 비교해 보세요.

자동으로 처리되는 것

아래 두 가지는 별도로 처리할 필요가 없습니다.

  • 동작 줄이기 설정: prefers-reduced-motion: reduce 환경에서는 --seed-feedback-scale이 항상 1이 됩니다.
  • 크기를 알 수 없는 상황: JavaScript가 실행되지 않았거나 첫 측정 전이라면 --seed-feedback-scale1이 됩니다. 잘못된 배율이 적용되는 대신 효과만 나타나지 않습니다.

알아둘 점

중첩된 SEED 컴포넌트

커스텀 컴포넌트가 Checkmark, Radiomark, Switchmark 등을 감싸는 경우, 바깥 요소와 안쪽 요소가 함께 줄어들어 안쪽 요소가 이중으로 축소됩니다. 각 컴포넌트 문서에 끄는 방법이 안내되어 있습니다.

position: fixed 자손

scale 값이 있는 요소는 stacking context이자 position: fixed 자손의 containing block이 되므로, 이 요소 안에 position: fixed 요소가 있다면 뷰포트가 아니라 이 요소를 기준으로 배치됩니다.

ScaleFeedback이 적용된 요소는 눌리지 않은 상태에서도 scale: 1을 유지하므로, 누르는 동안 자손이 따라 움직이지는 않습니다. 다만 배치 기준 자체가 뷰포트와 달라지므로 scale을 적용한 요소 하위에 position: fixed 자손이 있다면 의도한 위치에 표시되는지 확인하세요.

프로젝트 전체에서 끄기

배율을 1로 고정하는 규칙을 @layer 없이 추가하고 SEED CSS보다 뒤에 로드하세요. 배율을 읽는 모든 곳이 이 변수 하나를 거치므로 SEED 컴포넌트와 커스텀 컴포넌트가 함께 꺼집니다.

scale-feedback-off.css
.seed-scale-feedback {
  --seed-feedback-scale: 1;
}

평상시 scale: 1은 그대로 남으므로 position: fixed 자손의 배치 기준은 달라지지 않습니다.

Last updated on

목차