-
[React] 별점 기능 구현React 2022. 12. 14. 17:19
별점 기능에 대해 참고할만한 글이나 영상들을 찾아보다가
리액트로 별 반개 씩 별점을 주는 기능은 별로 없어서 써본다.
import { useState } from "react"; import StarInput from "./StarInput"; const StarRating = () => { const [rating, setRating] = useState(0); const handleClickRating = (value) => { setRating(value); }; return ( <section> <h1>별점</h1> <fieldset> <StarInput onClickRating={handleClickRating} value={5} isHalf={false} /> // 생략 <StarInput onClickRating={handleClickRating} value={0.5} isHalf={true} /> </fieldset> <span>{rating}</span> </section> ); }; export default StarRating;
대략 전체적인 구조로 useState를 사용하여 rating이란 이름으로 상태를 관리하였고
라디오 인풋으로 5점부터 ~ 0.5점까지 0.5 단위 씩 별점을 value 값으로 주어 StarInput 컴포넌트를 만들었다.
isHalf props은 별점이 지금 0.5 단위인지 진리값으로 주었다. 값이 true이면 별의 반개인 아이콘이 나타난다.
import { FaStar, FaStarHalf } from "react-icons/fa"; const StarInput = ({ onClickRating, value, isHalf }) => { const handleClickRatingInput = (value) => { onClickRating(value); }; return ( <> <input type="radio" name="rating" id={`star${value}`} value={value} /> <label onClick={handleClickRatingInput} isHalf={isHalf} htmlFor={`star${value}`} > {isHalf ? <FaStarHalf /> : <FaStar />} </label> </> ); }; export default StarInput;
css로 스타일을 어떻게 구현해야할지 가장 고민이었다.
지금은 별 반별 별 반별 별 반별 별 반별 별 반별 이렇게 나열되고 있지만
(반별 별) (반별 별) (반별 별) (반별 별) (반별 별)과 같이 요소가 겹쳐지게 해야했다.
이에 대해 바닐라 자바스크립트로는 fontawesome으로 ::after 요소에 반개의 별 아이콘 유니코드를 넣어 position: absolute로 위치를 잡아서 구현한 경우도 많이 보였다. 하지만 한 번 시도해보았으나 지원 하지 않는 건지 되지 않았고 원하는 아이콘이 아니었기 때문에 이 방법으로는 사용하지 않았다.
생각을 여러 번 해도 좋은 해결책이 나오지 않아서 어쩔 수 없이 transform을 이용하여 조정을 하였다.
half의 상태가 true인 컴포넌트에 position: absolute와 transform을 이용해 width에 맞게 조정을 했다.
전체코드
import styled from "@emotion/styled"; import { useState } from "react"; import StarInput from "./StarInput"; const Base = styled.section` display: flex; align-items: center; gap: 8px; `; const Name = styled.span` font-size: 1.4rem; line-height: 100%; `; const RatingValue = styled.span` font-size: 1.2rem; line-height: 100%; `; const RatingField = styled.fieldset` position: relative; display: flex; align-items: center; flex-direction: row-reverse; border: none; transform: translateY(2px); input:checked ~ label, labeL:hover, labeL:hover ~ label { transition: 0.2s; color: orange; } `; const StarRating = () => { const [rating, setRating] = useState(0); const handleClickRating = (value) => { setRating(value); }; return ( <Base> <Name>별점</Name> <RatingField> <StarInput onClickRating={handleClickRating} value={5} isHalf={false} /> <StarInput onClickRating={handleClickRating} value={4.5} isHalf={true} /> <StarInput onClickRating={handleClickRating} value={4} isHalf={false} /> <StarInput onClickRating={handleClickRating} value={3.5} isHalf={true} /> <StarInput onClickRating={handleClickRating} value={3} isHalf={false} /> <StarInput onClickRating={handleClickRating} value={2.5} isHalf={true} /> <StarInput onClickRating={handleClickRating} value={2} isHalf={false} /> <StarInput onClickRating={handleClickRating} value={1.5} isHalf={true} /> <StarInput onClickRating={handleClickRating} value={1} isHalf={false} /> <StarInput onClickRating={handleClickRating} value={0.5} isHalf={true} /> </RatingField> <RatingValue>{rating}</RatingValue> </Base> ); }; export default StarRating;
import styled from "@emotion/styled"; import { css } from "@emotion/react"; import { FaStar, FaStarHalf } from "react-icons/fa"; const Input = styled.input` display: none; `; const Label = styled.label` cursor: pointer; font-size: 1.5rem; color: lightgray; ${({ isHalf }) => isHalf && css` position: absolute; width: 12px; overflow: hidden; &:nth-of-type(10) { transform: translate(-108px); } &:nth-of-type(8) { transform: translate(-84px); } &:nth-of-type(6) { transform: translate(-60px); } &:nth-of-type(4) { transform: translate(-36px); } &:nth-of-type(2) { transform: translate(-12px); } `} `; const StarInput = ({ onClickRating, value, isHalf }) => { const handleClickRatingInput = (value) => { onClickRating(value); }; return ( <> <Input type="radio" name="rating" id={`star${value}`} value={value} /> <Label onClick={handleClickRatingInput} isHalf={isHalf} htmlFor={`star${value}`} > {isHalf ? <FaStarHalf /> : <FaStar />} </Label> </> ); }; export default StarInput;
StarInput 컴포넌트에서 별점을 5부터 시작하는 이유가 있다.
별에 마우스를 대거나 클릭을 할 경우, 만약 3점을 클릭하면 나머지 0.5~2.5 선택이 모두 되어야 한다.
~ 선택자는 요소의 다음 형제 요소들을 선택하기 때문에 순서를 반대로 하였다.
그리고 flex-direction:row-reverse;로 모습이 반대로 바뀌게 하였다.
const RatingField = styled.fieldset` position: relative; display: flex; align-items: center; flex-direction: row-reverse; input:checked ~ label, labeL:hover, labeL:hover ~ label { transition: 0.2s; color: orange; } `;
구현된 모습
반응형'React' 카테고리의 다른 글
[React] useCallback으로 useEffect 무한루프 방지 (0) 2023.01.12 [React] 검색어 자동완성 키보드 이벤트 기능 구현 (0) 2023.01.05 [React] 함수형 컴포넌트와 함수 컴포넌트 (0) 2022.11.09 [React] useEffect의 dependency array에 object (0) 2022.11.01 [React] 객체 key에 접근 (0) 2022.09.21