小数点ありの点数で中途半端に星を塗りつぶす（fontawesome利用）


# この記事で解説すること
まずは絵を見てください

<img width="400" alt="star画像" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605568/9519fd6a-6363-e2c7-ef4f-ab04ea0ee922.png">

こんな感じで星を中途半端な位置まで塗りつぶすオレオレコードを紹介します😎

FontAwesomeには半分だけの星アイコンが用意されているため、0.5刻みで塗りつぶすのは割と簡単にできます。
（調べるとそれなりの数の記事がヒットすると思います。）

ただ0.3とか0.8とかも表現したいとなると少し複雑なことをする必要があり、せっかくなので記事にしてみました。

# とりあえずコードを見せろ（デモ）
お急ぎの方はこちらのコードをコピペしてお使いください⭐️

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="XWxLOdy" data-user="koki-73" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/koki-73/pen/XWxLOdy">
  Untitled</a> by koki-73 (<a href="https://codepen.io/koki-73">@koki-73</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>



# 軽く説明
以下の要領で実装しています

1. 塗りつぶした星と枠線のみの星をpositionを使って重ねる
2. 入力値を取得して、整数部分と小数部分を抽出する
3. 整数の数だけ星を塗りつぶす
4. 小数の割合だけ(整数+1)個目の星を塗りつぶす

※ 塗りつぶしは`枠線の星の幅`を基準にwidthを設定します

コードを見るとずいぶんまわりくどいやり方だなぁと感じると思います😕
いくつか疑問があるかもなので、少しQ&Aにしてみました

## Q: `star-background`のwidthを指定すれば、foregroundのwidthはそれ基準でいいのでは？
**A: font-sizeを指定しても星の大きさはその値と一致しないためです**

以下`fa-star`クラスのfont-size、widthと、`star-background`クラスのwidthを50pxで指定した時の、アイコンのbefore擬似要素の大きさです。
<img width="400" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605568/d6dca57b-d58b-77d9-5adc-ad2e528ecd9c.png">

```css
// こんな感じに指定されてる
.fa-star {
  font-size: 50px;
  width: 50px;
}
.star-background {
  width: 50px;
}
```

疑似要素の幅が56.25pxになるので、これ基準で塗りつぶしの幅を設定する必要があります。

## Q: それなら56.25pxを調べておいて、それを使えばいいじゃん
**A: 星の大きさを変更した時に変更が面倒なので...**

もちろん星の大きさが変わることがないという要件であれば固定幅基準にしても良いかと思います。
（そんな要件を信用できるかは置いておきます）

## Q: 星5つまとめてインラインブロックにしないの？
**A: 星同士の隙間分塗りつぶし漏れになるので非推奨です**

こんな感じで`stars-foreground`の幅を変えて塗りつぶし幅を変えれば、一つの要素のみの操作で簡単にできそうです。
```html
<div class="stars-foreground">
  <i class="fas fa-star"></i><i class="fas fa-star"><!-- 5個分続く... -->
</div>
<div class="stars-background">
  <i class="far fa-star"></i><i class="far fa-star"></i><!-- 5個分続く... -->
</div>
```
しかしこれだと星同士の隙間の中間が`n.0`の位置になるので、例えば1.8個分塗りつぶしたいのに2.0に見えてしまいます。（0.9ぐらい塗りつぶしと、星の枠線があることで全て塗りつぶされて見える）

↓1.8のとき
<img width="400" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605568/416995f6-a54f-4b27-c298-3b9f9f0c60a7.png">

↓2.0のとき
<img width="400" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605568/9f788a02-7f72-8799-aea3-1682e3cba921.png">



<details><summary>（参考）星5つをまとめるやり方のデモはこちら</summary>

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="dyQyGPR" data-user="koki-73" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/koki-73/pen/dyQyGPR">
  star rating test</a> by koki-73 (<a href="https://codepen.io/koki-73">@koki-73</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
</details>

星同士の隙間を作らずに並べる場合はこちらのほうが良いかもしれません


# テンプレートエンジン使うなら
私は普段Railsを触っているので、ERBテンプレートならこんな感じになるよというのを一応載せておきます。
（cssはデモで使っているものを利用すれば大丈夫なはずです）

```erb
<div class="rating">
  <% 1.upto(5) do |i| %>
    <div class="star">
      <!-- ratingは3.7などの数字が入った変数が入っている想定 -->
      <% is_full = rating >= i %>
      <% is_empty = i - rating >= 1 %>
      <% fraction = is_empty ? 0 : rating - rating.floor %>
      <!-- JSで各星をどのくらい塗りつぶすか判定するためにdata属性に入れておく -->
      <div class="star-foreground" data-width-ratio="<%= is_full ? 1 : fraction %>">
        <i class="fas fa-star"></i>
      </div>
      <div class="star-background">
        <i class="far fa-star"></i>
      </div>
    </div>
  <% end %>
</div>
<script>
  const backgroundStarWidthPx = document.querySelector('.star-background').offsetWidth
  document.querySelectorAll('.star-foreground').forEach((elem) => {
    const ratio = parseFloat(elem.dataset.widthRatio)
    elem.style.width = `${backgroundStarWidthPx * ratio}px`;
  })
</script>
```

※ data-width-ratioを計算するロジックなんかはヘルパーに書いたほうがわかりやすいです
※ JavaScriptは本来別ファイルに分けた方が良いですが、デモ用に簡易的に書いてます

# おまけ

デモではインラインブロックで並べていますが、それぞれの星ごとに塗りつぶししているので、フレックスボックスを使って隙間を大きく取ることも可能です

塗りつぶしアイコンと枠アイコンがあれば同じように塗りつぶしできるので、もちろん別のアイコンでも使えます

というわけで、こんな感じにもできます↓（死にかけ😇）
<img width="400" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/605568/872da62f-3ccc-559d-0e3c-488d455d062c.png">


# おわりに
「俺はもっと良いやり方を知ってるぜ」という方がいれば是非コメントくださいませ。

「役に立った😊」という方いらっしゃいましたらイイねくれると嬉しいです。