라이브러리 없이 React 컴포넌트를 만들어보고 있는 중인데,
한 번 만들고 땡이 아니라 기록으로 남기면 나중에 재사용할 일이 있을지도 모르고, 타인에게 도움이 될 거 같기도 해서 기록을 남긴다.
프론트엔드 라이브러리를 써도 좋지만, 직접 만들어 쓰는 낭만이란...
개발환경은 create react app 명령어
npx create-react-app@latest --template=typescript .
으로 설정하였고, 가급적 CSS 만 이용하였다.
로딩 스피너를 만들기 위해 svg 태그를 이용할 것이며
비트맵 이미지와 벡터 그래픽에 대한 이야기는 생략한다.
children
: circle
태그가 들어간다.
svgProps
: children 은 위에서 한 번 선언했으니 이를 제외한 svg 태그에 넣을 수 있는 파라미터들.
// src/components/Svg/Svg.tsx
import { ReactNode, SVGProps } from "react";
interface Props {
children?: ReactNode | ReactNode[];
svgProps?: Omit<SVGProps<SVGSVGElement>, 'children'>;
}
function Svg({ children, svgProps }: Props) {
return (
<svg {...svgProps} >
{children}
</svg>
);
}
export default Svg;
위에서 선언한 Svg 컴포넌트를 불러온 다음 viewBox 및 width 와 height 속성을 size Prop 을 통해 받아오도록 설정하고 circle 에 필요한 속성들도 전달한다.
이 컴포넌트는 기본 가로 세로 64, 테두리 두께 8 을 가진다.
cx
: 원의 중심 x 좌표이다. 이 컴포넌트는 64x64 사이즈니, 64 / 2 = 32 가 원의 중심 x 좌표가 된다. cy
: 원의 중심 y 좌표이다. 마찬가지이다.r
: 반지름은 우선 size 에서 테두리 크기를 뺀 후 2로 나눈 값이다.fill
: 안쪽 채우기 색상은 따로 채우지 않는다.stroke
: 테두리 색상은 적당히 맘에 드는 걸로 고른다.strokeWidth
: 테두리 크기 역시 기호에 맞게 설정한다.// src/components/Loading/Loading.tsx
import Svg from "../Svg/svg";
import "./loading.css";
interface Props {
size?: number;
sw?: number;
}
function Loading({ size = 64, sw = 8 }: Props) {
return (
<Svg svgProps={{
width: size,
height: size,
x: size,
y: size,
viewBox: `0 0 ${size} ${size}`,
}}>
<circle
className="loading_circle"
cx={(size / 2)}
cy={(size / 2)}
r={(size - sw) / 2}
fill="none"
stroke="red"
strokeWidth={sw}
/>
</Svg>
);
}
export default Loading;
애니메이션을 제어하기 위해서는 원의 둘레를 알 필요가 있다.
원의 둘레는 구하는 공식은 2𝞹r
이지만, r 에서 sw (strokeWidth) 를 뺀만큼 조정해야 하므로 아래와 같은 공식으로 원의 둘레를 구할 수 있다.
2 × 3.141592 * (64 / 2 - 8) = 약 176
stroke-dasharray 속성은 테두리 선을 점선으로 바꿔준다.
아래 예제에선 점선의 크기를 76, 간격을 100 으로 설정하고 있다.
원의 둘레가 176 이므로 점선의 크기와 간격의 합을 176 으로 설정하면
하나의 점선만 그릴 수 있다.
stroke-dashoffset 속성은 테두리 선이 시작되는 위치를 변경한다.
테두리 선이 시작되는 위치는 3시 방향 (0)을 시작으로 반시계 방향으로 돈다.
12시 방향 시작은 44
,
9시 방향 시작은 88
,
6시 방향 시작은 132
,
9시 방향 시작은 176
을 입력하면 된다.
/* src/components/Loading/Loading.css */
.loading_circle {
animation: 1s linear 1ms infinite circle_loading;
stroke-dasharray: 100,76;
stroke-dashoffset: 44;
stroke-linecap: round;
}
@keyframes circle_loading {
0% {
stroke-dashoffset: 44;
}
100% {
stroke-dashoffset: 220;
}
}
마지막으로 App
에서 Loading
컴포넌트를 불러오고 size 와 sw props 를 컴포넌트에 전달하면 완성된 로딩스피너를 볼 수 있다.
// src/App.tsx
import './App.css';
import Loading from './components/Loading/Loading';
function App() {
return (
<div className="App">
<div>
<Loading/>
<Loading size={24} sw={2}/>
<Loading size={32} sw={4}/>
<Loading size={44} sw={6}/>
</div>
</div>
);
}
export default App;
그럭저럭 쓸만한 스피너가 완성되었다.