サムネイル

Next.jsで星のレーティング評価を作ってみた

実務で作る機会があったので今回書いてみようと思い書いています。誰かの参考になれば幸いです。

星のレーティング評価とは

聞き馴染みがそんなにないとは思いますが (少なくとも筆者は初耳)レビューなどで星で評価するやつです。

環境

  • next.js : 14.1.4
  • react : ^18
  • TypeScript : ^5
  • TailwindCSS : ^3.3.0

要件

1~5までの数字(小数点含む)をコンポーネントに渡すと、星のレーティング評価を返すものを作る

第一段階・星の配列を表示する

const StarRating = () => {
  const stars = Array.from({ length: 5 }, (_, index) => {

    // 星のスタイルと描画内容を定義します。
    const starContent = "★";
    let starClassName = "text-gray-400"; 

    // 星を表示します。
    return (
      <span key={index} className={`text-xl ${starClassName}`}>
        {starContent}
      </span>
    );
  });

  // 星を表示します。
  return <div className="flex">{stars}</div>;
};

第二段階・評価を受け取る

import React from "react";

type StarRatingProps = {
  rating: number;
};

const StarRating: React.FC<StarRatingProps> = ({ rating }) => {
  const stars = Array.from({ length: 5 }, (_, index) => {
    const starContent = "★";
    let starClassName = "text-gray-400";

    return (
      <span key={index} className={`text-xl ${starClassName}`}>
        {starContent}
      </span>
    );
  });

  return <div className="flex">{stars}</div>;
};

export default StarRating;

Reactが少しわかる方ならわかるかもしれませんが1~5の評価をプロップスで受け取れるようにしただけです。

第三段階・評価の数字分黄色に塗りつぶす

ここが少し難しいところではありますがやっていきましょう。
まずは、「評価に基づく塗りつぶしの割合を計算」します。

 const fillRatio = Math.min(Math.max(rating - index, 0), 1);

割合が分かったら、下記コードのように星が完全に塗りつぶされている星と一部しか塗りつぶされていない場合の場合分けを行っていきます。

// 星が完全に塗りつぶされている場合
if (fillRatio === 1) {
  starClassName = "text-yellow-500"; 
}
// 星が半分または一部塗りつぶされている場合
else if (fillRatio > 0) {
  return (
    <span key={index} className="relative text-xl">
      {/* 塗りつぶされた部分 */}
      <span
        className="absolute text-yellow-500"
        style={{
        width: `${fillRatio * 100}%`, // 塗りつぶしの割合
          overflow: "hidden",
        }}
      ></span>
      {/* 塗りつぶされていない星 */}
      <span className="text-gray-400"></span>
    </span>
  );
}

これで完成となります。

最後にコードの全体をご覧ください。

// StarRanking.tsx
import React from "react";

type StarRatingProps = {
  rating: number;
};

const StarRating: React.FC<StarRatingProps> = ({ rating }) => {
  const stars = Array.from({ length: 5 }, (_, index) => {
    const starContent = "★";
    let starClassName = "text-gray-400"; 
    
    const fillRatio = Math.min(Math.max(rating - index, 0), 1);

    if (fillRatio === 1) {
      starClassName = "text-yellow-500"; 
    }
    else if (fillRatio > 0) {
      return (
        <span key={index} className="relative text-xl">
          <span
            className="absolute text-yellow-500"
            style={{
              width: `${fillRatio * 100}%`, 
              overflow: "hidden",
            }}
          ></span>
          <span className="text-gray-400"></span>
        </span>
      );
    }

    return (
      <span key={index} className={`text-xl ${starClassName}`}>
        {starContent}
      </span>
    );
  });

  return <div className="flex">{stars}</div>;
};

export default StarRating;

あとは、このコンポーネントを親で呼んであげます

<StarRanking ranking={4} />


次にこのコンポーネントに「2.5」を渡すと下記のような表示になります。

<StarRanking ranking={2.5} />


うん。いい感じだと思います。
お疲れ様でした。