IT일상

CSS로 별점 기능 만들기 본문

프론트엔드

CSS로 별점 기능 만들기

solo5star 2023. 3. 27. 16:47
리뉴얼 된 블로그로 보기: https://solo5star.dev/posts/33/

 

JavaScript를 최대한 적게 사용하여 별점 기능을 만들어보려고 합니다.

 

body {
  background: rgb(32, 32, 40);
}

.vote {
  display: flex;
}

.star { padding: 1px; }

.star::after {
  content: '☆';
  color: hsl(60, 80%, 50%);
  font-size: 20px;
}
<body>
  <div class="vote">
    <div class="star"></div>
    <div class="star"></div>
    <div class="star"></div>
    <div class="star"></div>
    <div class="star"></div>
  </div>
</body>

별 모양을 표현하는 CSS를 만들어줍니다. 그리고 위의 HTML과 같이 마크업을 합니다.

 

 

별 다섯개가 표시될 것입니다. 간단하게 하기 위해 ☆ 특수기호를 사용하였습니다.

 

 

:hover

.star:hover::after {
  content: '★';
}

마우스를 올릴 때 :hover pseudo class가 활성화될 것입니다.

 

 

 

:has(~ :hover)

갑자기 어려운 쿼리가 나타났습니다. 하나씩 해석해보자면,

 

:has() 는 괄호 안의 선택자에 해당되는 엘리먼트를 가지고 있을 때 활성화될 것입니다. 기본적으로 자식 요소를 찾습니다.

 

하지만 괄호 안에서 물결표(~)로 시작한다면, 자신의 뒤에 있는 요소를 찾습니다.

 

즉 .star:has(~ .star:hover) 는, 자신(.star)의 뒤의 요소에 :hover된 .star가 있다면 활성화됩니다.

 

.star:has(~ .star:hover)::after {
  content: '★';
}

 

 

클릭 시 별이 고정되도록 하기

아쉽게도 이 로직은 CSS로 불가능하며, JavaScript가 필요합니다.

 

document.querySelectorAll('.star').forEach(($star) => {
  $star.addEventListener('click', ({ target }) => {
    // 모든 .star에서 .active 삭제
    document.querySelectorAll('.star').forEach(($star) => {
      $star.classList.remove('active');
    });

    // 클릭한 .star에 .active 추가
    target.classList.add('active');
  });
});

클릭하였을 때 클릭된 .star에 .active를 추가하는 코드입니다.

 

.star.active::after,
.star:has(~ .star.active)::after {
  content: '★';
}

그리고 :hover에서 했던 것처럼 비슷하게 CSS를 추가하면 됩니다.

 

 

 

호버 중일 때 뒤의 별들은 모두 끄기

위의 Gif를 보고 어색하다고 느꼈을 겁니다. :hover 시 :hover된 .star를 기준으로 뒤의 .star들이 모두 꺼져야 하기 때문입니다.

 

이 또한 CSS로 간단하게 처리할 수 있습니다.

.star:hover ~ .star::after {
  content: '☆';
}

 

물결표(A ~ B) 선택자는 A 뒤에 있는 B를 선택합니다. 즉 .star:hover ~ .star 는, :hover되어있는 .star 뒤의 .star가 선택됩니다.

 

 

전체 소스코드

<!DOCTYPE html>
<html lang="ko">

<head>
  <style>
    body {
      background: rgb(32, 32, 40);
    }

    .vote {
      display: flex;
    }

    .star { padding: 1px; }

    .star::after {
      content: '☆';
      color: hsl(60, 80%, 50%);
      font-size: 20px;
    }

    .star:hover::after,
    .star:has(~ .star:hover)::after,
    .star.active::after,
    .star:has(~ .star.active)::after {
      content: '★';
    }

    .star:hover ~ .star::after {
      content: '☆';
    }
  </style>
</head>

<body>
  <div class="vote">
    <div class="star"></div>
    <div class="star"></div>
    <div class="star"></div>
    <div class="star"></div>
    <div class="star"></div>
  </div>
</body>

<script>
document.querySelectorAll('.star').forEach(($star) => {
  $star.addEventListener('click', ({ target }) => {
    // 모든 .star에서 .active 삭제
    document.querySelectorAll('.star').forEach(($star) => {
      $star.classList.remove('active');
    });

    // 클릭한 .star에 .active 추가
    target.classList.add('active');
  });
});
</script>

</html>
Comments