Circle Progressbar란?
이거 참 검색하기 난해한데요
PPT에 익숙한 분들이라면 원형차트로 인식할 수도 있고
저런 동그라미가 빙글빙글 돌아가는 로딩 스피너에 익숙한 분들은
circle progress bar라고도 인식할 수 있습니다.
https://www.npmjs.com/package/react-circular-progressbar
이렇게 이미 만들어진 라이브러리가 제공되기도 하는데요
간단한 UI 기능이 필요할때 이런 라이브러리를 쓰기 꺼려지는 이유는
관리해야할 종속성이 추가되는 것과 outdated에 대한 걱정입니다.
(링크한 저 라이브러리도 코어 모듈은 4년전에 작성되었고 내부적으로 클래스컴포넌트를 사용하고있어요!)
따라서 요구사항이 크게 까다롭지않다면 / 태스크에 투자할 시간이 넉넉하다면
직접 UI KIT을 구현하는 것이 선호되곤 하는데요
제가 다니고 있는 회사의 경우에는 아래와 같은 UI들이 필요합니다.
기존에는 emotion 기반으로 프로젝트가 작성된 덕에
emotion을 코어로하는 chakra라는 ui 라이브러리를 사용하는 상태였습니다.
다만 커스터마이징에 한계가 있는 ui 라이브러리에 의존하게 되는 것으로 인하여
디자이너의 시안을 완벽히 반영하기 힘든 문제와
팀 내부적으로 아직 디자인시스템이 잘 약속되지않은 탓에 디자인의 규칙성을 찾기 어렵다는 문제가 있었습니다.
또한 장기적으로 emotion을 걷어내고
빌드타임 css인 tailwindcss로의 마이그레이션을 생각하는 중이었기에
chakra 역시도 버릴수 밖에 없는 상황이었습니다.
직접 구현이 난해하게 느껴질수있지만
차근차근 생각해보면 어렵지 않게 구현할 수 있습니다.
conic-gradient
먼저 자연스럽게 차오르는 Circle 애니메이션을 위해 알아두어야하는 css 속성이 있습니다.
바로 conic-gradient라는 css function 인데요
https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient
MDN에서 자세한 정의와 사용방법을 알아볼 수 있습니다.
각도와 중지점의 색상 등도 커스터마이징할 수 있을정도로 높은 자유도를 보여줍니다.
각도는 deg라는 단위를 통하여 사용하게되는데요
conic-gradient(red, orange, yellow, green, blue);
conic-gradient(red 0deg, orange 90deg, yellow 180deg, green 270deg, blue 360deg);
각도에 대해서는 default값이 존재하기 때문에 꼭 입력해주지 않아도 괜찮습니다.
위 두줄의 코드는 모두 동일한 동작을 하는 코드에요
<div
className=" w-[300px] h-[300px] flex justify-center items-center transition-all duration-200"
style={{
background: `conic-gradient(red 70% , gray 0%, gray 0%)`,
borderRadius: '50%',
}}
>
<div className=" w-[80%] h-[80%] rounded-full bg-white flex justify-center items-center">
2단계
</div>
</div>
그럼 이제 실제 코드를 작성해봅시다.
편의상 그라디언트부분을 제외한 다른 코드는 tailwindcss로 가볍게 작성했는데요
css framework에 대한 의존성을 0으로 만들고 싶다면
모든 스타일링을 style 객체에 넣거나 별도의 css 파일을 만들어 관리해주시면 되겠습니다.
위 코드를 잘 작성했다면 화면에는 다음과 같은 도형이 그려질것입니다.
원리는 간단한데요 우선 내부에 들어가있는 div 박스를 제거해주면 다음과 같은 형태가 됩니다.
이렇게 생긴 원형차트와 같은 도형의 가운데부분을 좀 더 작은 원형도형으로 채워넣어주면
도넛같아 보이는 UI를 만들 수 있습니다.
<div className=" w-[80%] h-[80%] rounded-full bg-white flex justify-center items-center">
2단계
</div>
이때에 보여지게될 선의 굵기는 내부의 도형으로 결정하게되는데요
내부 요소들을 px로 관리하게되면 css 유지보수에 어려움이 있을 수 있으니
%와 같은 상대단위로 관리해주시면 되겠습니다.
이제 차오르는 UI를 작성하면 되겠습니다
background: `conic-gradient(red 70% , gray 0%, gray 0%)`,
이 conic-gradient의 값에서 red의 %부분을 올려주는것으로 수행할 수 있습니다.
뒤의 두 값은 각각 중단점의 색상 / 다른면의 색상입니다.
그럼 이제 할일은 명확합니다. 저 red 뒤에 들어갈 값을 동적으로 변화시켜주면 되는 것이죠!
const [gradient, setGradient] = useState(0);
React.useEffect(() => {
const timerId = setInterval(() => {
if (gradient < 80) {
setGradient((state) => state + 1);
}
}, 10);
return () => clearInterval(timerId);
}, [gradient]);
간단한 setInterval 코드를 다음과 같이 작성할 수 있습니다.
이렇게만 작성해두어도 동작은 하기때문에 사용해줄 수 있지만
성능최적화를 고려한다면 requestAnimationFrame 이라는 web api를 사용하는 것을 고려할 수 있습니다.
https://xionwcfm.tistory.com/395
requesetAnimationFrame에 대한 정보가 궁금하다면 위 게시물을 참고하세요
다만 여기에 좀 더 자연스러운 애니메이션을 넣어주고싶다면
framer-motion이라는 리액트 기반의 애니메이션 라이브러리를 사용하는 것을 고려할 수 있습니다.
npm i framer-motion
을 통하여 프레이머모션을 설치하고 아래와 같은 코드를 작성해줍시다.
'use client';
import { motion, useMotionValue, useTransform, animate } from 'framer-motion';
import React from 'react';
const CircleProgressContainer = () => {
const count = useMotionValue(0);
const rounded = useTransform(count, (latest) => {
return Math.round(latest);
});
React.useEffect(() => {
animate(count, 70);
}, [count]);
return (
<div className=" flex justify-center items-center bg-gray-100">
<motion.div
className=" w-[300px] h-[300px] flex justify-center items-center transition-all duration-200 rounded-full"
initial={{
background: 'conic-gradient(red 0% , gray 0%, gray 0%)',
}}
animate={{
background: `conic-gradient(red 70% , gray 0%, gray 0%)`,
}}
transition={{
duration: 1,
type: 'spring',
}}
>
<motion.div className=" w-[80%] h-[80%] rounded-full bg-white flex justify-center items-center">
{rounded}
</motion.div>
</motion.div>
</div>
);
};
export default CircleProgressContainer;
지금은 임의의 값을 넣어주었지만
컴포넌트 외부에서 값을 주입 받도록 고쳐주면 어디서나 사용할 수 있겠죠?
마치며
편의를 위해 프레이머모션의 사용법 / conic-gradient에 대해 자세히 다루지는 않았습니다.
다만 두가지 모두 쉽게 레퍼런스를 찾아볼 수 있으니 필요하다면 따로 학습하시는 것을 추천드립니다.
프론트 개발을 하다보면 의외로 자바스크립트보다 css가 정말 어렵다는 것을 매번 깨닫게되곤 하는것 같아요
특히 css는 잘못된 방법으로 작성해도 동작은 하는것처럼 보인다는 점 때문에 더더욱 그런 것 같습니다.
사실 위 예제 코드들 역시 css 관점에서 좋은 코드라고 보기는 어렵지만
예제 코드인만큼 너그러이 이해해주시길 부탁드립니다
'프로젝트 진행기' 카테고리의 다른 글
[연픽] SSG 되지 않아도 되는 컴포넌트 격리하기 (1) | 2023.09.27 |
---|---|
[연픽] interface 보강 기법을 통한 window 객체 확장 (0) | 2023.09.25 |
[연픽] 성공적인 프론트엔드 리팩토링을 위한 사전 준비 (3) | 2023.09.24 |
[Plip] 이메일 요청은 되도록 한번만 보내주세요 (0) | 2023.07.24 |
[PliP] 로그인의 restful 한 설계와 토큰 관리 전략 (1) | 2023.07.19 |