콘텐츠로 이동

spinner —— 회전 로딩 그래픽

spinner는 순수한 ’회전 그래픽’입니다. 로딩 덮개가 아니고(그것은 LhLoading입니다) 스켈레톤도 아니며(그것은 LhSkeleton입니다) 재사용 가능한 회전 요소입니다: 버튼 안의 로딩 아이콘, 메시지 알림 안의 loading 아이콘, 카드 로딩 모서리의 회전표가 모두 이것입니다.

  • 각 단말이 LhSpinner를 구현할 때, 그리고 어떤 컴포넌트 내부에서든 회전 그래픽이 필요한 곳(button loading, message loading 등).
  • 사용하면 안 되는 곳: 전체 화면 로딩, 영역 로딩. 그것은 LhLoading의 책임입니다(spinner는 그 일부일 뿐).
import { getSpinnerClassNames } from '@lh-ui/share/core/spinner'
import { LH_SPINNER_SIZE_PX, LH_SPINNER_SIZE_CONST } from '@lh-ui/share/core/spinner'
import type { LhSpinnerSize } from '@lh-ui/share/core/spinner'

spinner의 크기 체계는 전역 크기 단계와 정렬됩니다(LH_SPINNER_SIZE_CONST는 LH_SIZE_CONST를 직접 전개). 하지만 픽셀 매핑은 독립적입니다. 컴포넌트의 ’medium’은 24px이고 버튼의 medium 높이는 37px로, 그래픽과 컨테이너가 같은 크기일 필요는 없습니다. 이 독립성은 곳곳의 매직 넘버에 흩어지지 않고 상수에 기록되어 있습니다.

LH_SPINNER_SIZE_PX가 유일한 진실의 원천이며, UI 계층은 직접 size === 'small' ? 16 : 24를 쓰지 않고 이것을 읽어 SVG/요소의 width/height를 설정합니다.

내보내기 시그니처 / 값 역할
getSpinnerClassNames (state: { size: LhSpinnerSize }) => string[] 클래스명 배열
LH_SPINNER_SIZE_CONST { small: 'small', medium: 'medium', large: 'large', mini: 'mini', 'x-large': 'x-large' } 크기 열거(LH_SIZE_CONST와 정렬)
LH_SPINNER_SIZE_PX { mini: 12, small: 16, medium: 24, large: 32, 'x-large': 40 } 크기 → 지름 px
getSpinnerClassNames({ size: 'small' }) // ['lh-spinner', 'lh-spinner--small']
LH_SPINNER_SIZE_PX['large'] // 32
export type LhSpinnerSize =
(typeof LH_SPINNER_SIZE_CONST)[keyof typeof LH_SPINNER_SIZE_CONST]
// 'mini' | 'small' | 'medium' | 'large' | 'x-large'

core/spinner는 aria 속성 묶음을 제공하지 않습니다. 회전 그래픽의 접근성 처리는 어디에 나타나는지에 따라 달라집니다:

  • 버튼 안의 spinner: 버튼에 aria-busy="true"를 주고, spinner 자체는 aria-hidden.
  • 메시지 알림 안의 spinner: 메시지의 live-region role이 이미 ’처리 중’을 전달하므로, spinner는 장식화합니다.
  • 독립적으로 ’로딩 중’을 표시: 반드시 읽을 수 있는 글자(예: ‘로딩 중…’)를 함께 써야 합니다. 그래픽만 있는 것은 스크린 리더 사용자에게 정보가 없는 것이나 다름없습니다.

일반 원칙: **회전 그래픽 자체는 항상 aria-hidden**이어야 하며, 정보는 그 주변의 의미(aria-busy, role, 글자)가 담습니다.

import { getSpinnerClassNames, LH_SPINNER_SIZE_PX } from '@lh-ui/share/core/spinner'
getSpinnerClassNames({ size: 'medium' }) // ['lh-spinner', 'lh-spinner--medium']
LH_SPINNER_SIZE_PX['medium'] // 24
<!-- Vue3 -->
<svg
:class="getSpinnerClassNames({ size })"
:width="px" :height="px"
viewBox="0 0 50 50"
aria-hidden="true"
>
<circle class="lh-spinner__circle" cx="25" cy="25" r="20" fill="none" />
</svg>
// React
const px = LH_SPINNER_SIZE_PX[size]
return (
<svg className={getSpinnerClassNames({ size }).join(' ')} width={px} height={px} viewBox="0 0 50 50" aria-hidden="true">
<circle className="lh-spinner__circle" cx="25" cy="25" r="20" fill="none" />
</svg>
)
// uniapp: h5가 아닌 단말에서는 svg 애니메이션 지원이 부족하므로, 보통 CSS border 회전 방식이나 GIF로 교체합니다;
// LH_SPINNER_SIZE_PX의 지름 값은 여전히 적용됩니다(border 방식의 너비/높이)
클래스명 의미
.lh-spinner 기본 클래스: 회전 애니메이션(animation: lh-spinner-rotate)
.lh-spinner--{mini|small|medium|large|x-large} 크기 단계(너비/높이 설정)
.lh-spinner__circle SVG circle: 부분 호 테두리 + stroke-linecap: round

선 색상은 기본 currentColor(부모 글자색 상속)이며, 회전 속도는 스타일 계층에서 정의하고 prefers-reduced-motion에서 CSS 미디어 쿼리로 일시 정지합니다.

  • 크기 일관성: LH_SPINNER_SIZE_PX의 small은 16px이고 아이콘의 small은 14px입니다(아이콘 참고). 이것은 알려진 단계 불일치로 ’높이 토큰 특별 조사’에 기록되어 향후 changeset으로 통일 예정이며, 현 시점에서는 직접 계산하지 말고 상수 표를 사용하세요.
  • getSpinnerClassNames는 size의 유효성을 검증하지 않으며(타입이 보장), 범위를 벗어난 값을 넘기면 스타일 없는 더러운 클래스가 산출됩니다.
  • SVG circle의 테두리 호 각도(stroke-dasharray)는 스타일 계층에 약 75% 호로 고정되어 있습니다. 호를 바꾸려면 core가 아니라 스타일을 바꾸세요.
  • 회전 애니메이션은 CSS keyframes이며, prefers-reduced-motion의 존중은 스타일 계층에 있습니다.
@lh-ui/share/core/spinner → getSpinnerClassNames( )
返回值
(填好参数后点运行)

Copyright © 2024-2026 FengMo WenXue