📌 오늘의 작업 핵심 요약
- 마우스 커서 모양 변경
- 투표 취소 시 selectedRegionId값을 null로 초기화
- RegionSelect컴포넌트에서 value 속성을 통해 외부 상태 반영
- 토스트바에서 라이트/다크 모드에 따라 배경 및 텍스트 색상 변경, 반응형 크기 조정
- 공약 총정리 - 토글 애니메이션 적용 및 상태 전환 개선
🐀 마우스 커서 모양 변경 하는 방법
전역 css로 들어가서 cursor: url('주소')를 입력하면 된다.
/*커서 변경*/
body {
cursor: url('/src/assets/cursor.svg'), auto;
}
/*마우스 포인터 유지하고 싶으면 밑에 코드 지우면 됩니다*/
button,
a,
[class*='cursor-pointer'] {
cursor: url('/src/assets/cursor.svg'), auto !important;
}
🧾 RegionSelect 개선 - 지역 선택 초기화 문제
import React from 'react';
import toast from 'react-hot-toast';
import { REGIONS } from '@/utils/constants.js';
const RegionSelect = ({ selectedRegionId, onRegionSelect }) => {
return (
<div className="mx-auto flex w-full max-w-[700px] items-center justify-between">
<label
@@ -11,11 +26,12 @@ const RegionSelect = ({ onRegionSelect }) => {
</label>
<select
id="region"
className="w-34 rounded-md border p-2 text-sm sm:w-34 dark:bg-black dark:text-white"
value={selectedRegionId ?? ''}
onChange={(e) => {
const regionId = Number(e.target.value);
onRegionSelect(regionId);
toast.success('후보자를 투표하세요!');
}}
>
<option value="" disabled>
문제 상황
사용자가 한 번 지역을 선택한 후 투표를 취소하면, 지역 선택 select 박스는 여전히 이전 선택된 지역을 유지하고 있었다.
UX측면에서 투표가 취소되면 선택된 지역도 리셋되어야 자연스럽고 일관된 흐름을 제공한다.
문제 원인
기존 RegionSelect 컴포넌트는 내부적으로만 defaultValue=""를 사용했었다.
<select defaultValue="" onChange={...}>
이 방식은 최초 렌더링 시에만 ""(기본 값)를 적용하고, 외부 상태가 바뀌어도 컴포넌트는 변화하지 않았다.
해결 방법
1. 지역 선택 상태를 외부에서 제어하도록 변경했다.
- selectedRegionId를 props로 전달받고, value 속성을 명시한다.
const RegionSelect = ({ selectedRegionId, onRegionSelect }) => {
..
<select
value={selectedRegionId ?? ''}
onChange={(e) => onRegionSelect(Number(e.target.value))}
>
2. 투표 취소 시 상태 초기화
- 상위 컴포넌트에서 selectedRegionId를 null로 초기화함으로써 → select 박스의 선택값도 ""로 리셋됨
- UI 상에서도 선택이 해제된 상태처럼 보이게 되었다. (localStorage에도 삭제된다.)
🖌️Toast UI 스타일 반영
토스트 라이브러리를 toast-hot-react를 사용했었다.
(그 이유는?
- 복잡한 컴포넌트 없이 바로 사용
- 스타일 커스터 마이징 자유도 높음
- 다크 모드, 반응형 대응 쉬움 (커스터마이징을 통해 사용)
)
커스터마이징된 토스트 스타일(toast-responsive) 적용 및 전역 설정
- CSS로 토스트의 디자인 직적 정의
- main.jsx에서 <Toaster>에 전역 옵션(tostOptions)을 지정해 모든 성공/실패 토스트에 통일된 스타일을 적용
main.jsx
<StrictMode>
<RouterProvider router={router} />
{/* 토스트바 나타나게 해줌 */}
<Toaster
position="bottom-center"
reverseOrder={false}
toastOptions={{
success: {
className: 'toast-responsive',
},
error: {
className: 'toast-responsive',
},
}}
/>
</StrictMode>
);
옵션 | 설명 |
position="bottom-center" | 토스트가 화면 하단 중앙에 표시됨 |
reverseOrder={false} | 나중에 발생한 알림이 아래에 쌓임 |
toastOptions | 전역 설정 → 모든 토스트에 toast-responsive 클래스 적용 |
index.css
/* 토스트 */
.toast-responsive {
font-size: 14px;
padding: 12px 16px;
max-width: 90vw;
width: 100%;
border-radius: 8px;
backdrop-filter: blur(4px); /* 약간 유리 느낌 */
background-color: rgba(0, 0, 0, 0.8); /* 반투명 배경 */
color: #fff;
z-index: 9999;
position: relative;
}
@media (min-width: 768px) {
.toast-responsive {
font-size: 16px;
padding: 16px 24px;
max-width: 400px;
}
}
/* 다크 모드 대응 */
html.dark .toast-responsive {
background-color: rgba(255, 255, 255, 0.85);
color: #000;
}
@media (prefers-color-scheme: dark) {
.toast-responsive {
background-color: rgba(255, 255, 255, 0.85);
color: #000;
}
}
속성 | 설명 |
max-width: 90vw | 뷰포트 기준으로 너무 커지지 않도록 제한 |
z-index: 9999 | 화면 최상단에 떠 있도록 설정 |
/* 다크 모드 대응 */
- Tailwind의 html.dark 클래스 대응
- 시스템 설정 기반 다크모드 (prefers-color-scheme: dark)도 이중 대응
- 다크모드일 땐 밝은 반투명 배경 + 검은 텍스트로 반전
🧾 공약 총정리 - 토글 애니메이션 적용 및 상태 정환 개선
import { useState, useEffect } from 'react';
import { FiChevronDown, FiChevronUp } from 'react-icons/fi';
import CategoryIcon from './CategoryIcon';
import '../../../styles/policy.css';
const PolicyItem = ({ category, items }) => {
const [isOpen, setIsOpen] = useState(false);
const [visible, setVisible] = useState(false); // 애니메이션 제어
const toggleOpen = () => setIsOpen(!isOpen);
useEffect(() => {
if (isOpen) {
const timer = setTimeout(() => setVisible(true), 10);
return () => clearTimeout(timer);
} else {
setVisible(false);
}
}, [isOpen]);
return (
<div className="border-primary border-t-5 pt-6 md:pt-8">
<button onClick={toggleOpen} className="flex w-full items-center justify-between">
</button>
{isOpen && (
<div
className={`fade-in-up mt-6 border-t border-[#B7B7B7] pt-6 md:mt-8 md:pt-8 ${
visible ? 'show' : ''
}`}
>
팀원이 했던 공약 총정리 페이지에 부드럽게 토글(?)이 열리는 애니메이션이 적용되면 좋을 거같아 적용해봤다.
1. 등장 애니메이션 추가
기존에는 리스트가 열리면 콘텐츠가 툭하고 나타났지만 fade-in-up 클래스와 useEffect 훅을 활용해 부드럽게 스르륵 등장하는 효과를 추가했다.
2. 애니메이션 상태 제어를 위한 상태 추가
const [visible, setVisible] = useState(false);
isOpen만으로는 CSS 트랜시젼이 바로 적용되지 않았다. 그래서 useEffect를 통해 visible 상태를 약간 지연시켜 CSS 트랜지션이 자연스럽게 적용되도록 조절시켰다.
useEffect(() => {
if (isOpen) {
const timer = setTimeout(() => setVisible(true), 10);
return () => clearTimeout(timer);
} else {
setVisible(false);
}
}, [isOpen]);
setTimeout을 통해 다음 렌더 프레임에서 .show클래스가 붙게 하여 자연스럽게 opacity, translateY 변화가 일어난다.
policy.css
.fade-in-up {
opacity: 0;
transform: translateY(24px);
transition:
opacity 0.9s cubic-bezier(0.25, 0.8, 0.25, 1),
transform 0.9s cubic-bezier(0.25, 0.8, 0.25, 1);
will-change: opacity, transform;
}
.fade-in-up.show {
opacity: 1;
transform: translateY(0);
}
속성 | 설명 |
opacity: 0 → 1 | 천천히 나타나는 느낌 |
transform: translateY(24px) → 0 | 아래에서 위로 슬쩍 올라오는 느낌 |
transition | 애니메이션 지속 시간과 부드러운 커브 정의 (0.9s, cubic-bezier) |
will-change | 렌더러에게 미리 알려 퍼포먼스 최적화 유도 |
opacity와 Y축 위치를 함께 조절하여 아래에서 위로 슬쩍 올라오는 느낌,
cubic-bezier 커브를 사용해 물리적인 감각에 가까운 트랜지션 구현
will-change란?
.fade-in-up {
will-change: opacity, transform;
}
will-change를 통해서 브라우저가 opacity, transform 속성이 앞으로 변경될거라 예상하고 CPU나 별도의 렌더링 계층(layer)으로 미리 준비해둔다.
will-change가 없으면 결과가 어떻게 나올까?
브라우저가 스타일 변경을 감지한 후에야 리페인트나 리플로우를 계산한다.
will-change가 있으면 결과가 어떻게 나올까?
브라우저가 미리 대비함 → 애니메이션 시작할때 더 빠르고 부드럽게 동작한다.
하지만, will-change는 성능 최적화 도구이지만 남용하면 역효과가 날 수 있다. (너무 많은 요소에 적용해) 렌더링 리소스 낭비가 생겨서 오히려 느려진다. 그래서 정말 애니메이션이 필요한 곳에 짧은 시간만 쓰는게 좋다.
즉, will-change는 성능 최적화를 위한 선언적 힌트이고
앞으로 변할 속성을 브라우저에 미리 알려 부드러운 애니메이션을 가능하게 해주는 속성이다.
리페인트나 리플로우가 무엇인가?
(주소)
🤔 고민했던 점
isOpen만으로 transtition이 적용되지 않아 visible 상태를 새로 추가했다. useEffect내에서 setTimeout을 활용해 CSS 클래스가 한 프레임 늦게 처리되도록 했다. 그렇게 하면 첫 렌더링에서 fade-in-up이 먼저 적용되고, show가 늦게 붙으며 자연스럽게 애니메이션이 발동한다.
(결과)
사용자가 정책 항목을 열때 자연스럽고 부드럽운 인터렉션 경험을 제공, 딱딱한 토글에서 정책 콘텐츠에 대한 접근성 높였다.
이 효과를 통해서
콘텐츠가 열릴때 갑자기 열림 → 콘텐츠가 스르륵 올라오며 자연스럽게 나타남
UX에 딱딱한 느낌 → 부드럽고 신뢰감 있는 UI연출
🎠 회고
디테일에 신경을 쓴 하루였다. 화려한 기능 추가는 없었지만 유저의 입장에서 하나하나 생각해봤다. 작고 부드러운 변화들을 만들어 내도록 집중을 했다.
지역 선택 초기화도 단순한 기능같지만 투표 흐름안에서 자연스러운 ux를 만드는데 큰 역할을 했다. 내부 상태에서 외부 상태 제어로의 전환은 작지만 중요한 설계의 전환점 이었다. 스르륵 펼쳐지는 애니메이션도 효과적으로 볼때는 예쁜 효과를 줄 순 있지만 사용자가 정보를 접할 대 좀 더 부드럽고 몰입감을 주는 요소이다.
다크 모드 역시 모드, 디바이스를 고려해 만들어봤다. 경험을 더 부드럽게 만들어 가는 날이 오늘이었따.
'💡 URECA > 📽️ 프로젝트' 카테고리의 다른 글
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #10 (0) | 2025.05.20 |
---|---|
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #9 (2) | 2025.05.19 |
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #5 (0) | 2025.05.13 |
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼 #4 (0) | 2025.05.13 |
[URECA] 픽미업: 복잡한 정보를 쉽게 전달하는 정치 참여 플랫폼(기획_최최종일까요?) #3 (2) | 2025.05.09 |