使用 Alpine.js 實作星級評分分佈 – 詳細教學
更新日期: 2025 年 1 月 7 日
本文為 留言評分功能 系列教學,第 13 篇:
- 使用 Django + Tailwind + Alpine.js 實作「五顆星評分」功能教學
- Django validators 驗證器完整教學
- Django PositiveSmallIntegerField 新手指南
- 在 esbuild 專案中整合 Alpine.js 的完整指南
- 使用 Alpine.js 建立星星評分表單 — 新手指南
- 深入理解 Alpine.js 中的 template 標籤使用指南
- Django 網站如何新增「星星評分」功能 — 後端接收邏輯
- Django 表單:如何讓使用者選擇性提交星星評分與留言
- Django 如何限制同一使用者只能對同一服務留言一次?
- Django 留言軟刪除邏輯|程式碼解析
- Django 計算評分摘要教學 — 使用 ORM 進行星等統計
- Python Django 使用 annotate、aggregate 統計教學
- 使用 Alpine.js 實作星級評分分佈 – 詳細教學 👈 所在位置
- Alpine.js 與 Tailwind CSS 動態樣式解析:為什麼有些樣式無法生效?
- Django 中使用 annotate() 與 Avg() 進行平均評分計算
這篇文章將詳細解釋如何使用 Alpine.js 搭配 Django 模板,實作一個互動式的星級評分分佈元件。
本範例展示了如何顯示評論的平均分數、總評論數,以及每個星等的分佈情況,同時使用動態計算進度條的寬度來視覺化呈現數據。
程式碼總覽
以下是完整的 Alpine.js 與 Django 模板代碼,用於渲染星級評分分佈:
<!-- ✅ 使用 Alpine.js 正確渲染星級分佈 -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-6"
x-data="{
averageRating: {{ average_rating|default:'null' }},
totalReviews: {{ total_reviews|default:0 }},
starsCount: {{ stars_count|default:'{}' }},
calculatePercentage(count, total) {
if (!total || !count) return 0;
const percentage = (count / total) * 100;
return percentage.toFixed(1);
},
formatRating(rating) {
return rating ? parseFloat(rating).toFixed(1) : '0.0';
}
}">
<!-- ⭐ 標題 -->
<h2 class="text-2xl font-bold mb-4">評分摘要</h2>
<!-- ⭐ 平均分數 -->
<div class="flex items-center mb-4">
<span
class="font-bold"
x-text="averageRating.toFixed(1)"
style="font-size: 64px;"
>0.0</span>
<div class="ml-4">
<!-- 渲染星星 -->
<div class="flex text-yellow-400 text-3xl">
<template x-for="n in 5">
<span x-text="n <= Math.round(averageRating) ? '★' : '☆'"></span>
</template>
</div>
<p class="text-gray-600" x-text="`${totalReviews} 則評論`"></p>
</div>
</div>
<!-- ⭐ 評分分佈 (調整進度條寬度和對齊) -->
<template x-for="i in 5" :key="i">
<div class="flex items-center mb-2">
<!-- 星級文字 -->
<span class="text-lg font-semibold w-6 text-right" x-text="6-i"></span>
<span class="text-yellow-400 text-2xl ml-2">★</span>
<!-- 進度條 -->
<div class="flex-grow mx-4">
<div class="w-full bg-gray-200 h-4 rounded-lg overflow-hidden">
<div
class="transition-all duration-300"
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
></div>
</div>
</div>
</div>
</template>
</div>
外層容器:x-data
<div
class="bg-white rounded-lg shadow-lg p-6 mb-6"
x-data="{
averageRating: {{ average_rating|default:'null' }},
totalReviews: {{ total_reviews|default:0 }},
starsCount: {{ stars_count|default:'{}' }},
calculatePercentage(count, total) {
if (!total || !count) return 0;
return ((count / total) * 100).toFixed(1);
},
formatRating(rating) {
return rating ? parseFloat(rating).toFixed(1) : '0.0';
}
}"
>
解釋:
x-data
:Alpine.js 的核心,用於初始化元件狀態。averageRating
:使用 Django 模板語法將average_rating
傳入 Alpine.js 內部作為數據綁定變數。totalReviews
:同樣透過 Django 模板變數將總評論數綁定進來。starsCount
:儲存每個星等的數量,格式為一個字典 (如{5: 10, 4: 5, 3: 3}
)。
定義的兩個方法:
calculatePercentage(count, total)
:計算星等比例 (用於進度條)。formatRating(rating)
:將評分數值格式化為小數點後一位數。
calculatePercentage()
方法
calculatePercentage(count, total) {
if (!total || !count) return 0;
const percentage = (count / total) * 100;
return percentage.toFixed(1);
}
- 計算星等的比例,用於進度條的寬度控制。
- 將 某星等的評論數 (
count
) 與 總評論數 (total
) 做比例計算,轉換成百分比格式。 - 格式化為 小數點後一位數,方便前端渲染更精確的比例。
📌 步驟拆解
- 輸入檢查
if (!total || !count) return 0;
- 如果
total
或count
為0
或未定義,直接回傳0
%。 - 這是為了避免「除以零」的錯誤,確保數據安全。
- 如果
- 計算百分比
const percentage = (count / total) * 100;
- 將該星等的數量
count
除以總評論數total
,再乘以100
,計算出百分比。
- 將該星等的數量
- 格式化數值
return percentage.toFixed(1);
- 使用 JavaScript 內建的
.toFixed(1)
將數字保留 小數點後一位。 - 範例:
4.66667
→4.7
- 使用 JavaScript 內建的
formatRating()
方法
formatRating(rating) {
return rating ? parseFloat(rating).toFixed(1) : '0.0';
}
- 將 平均評分 轉換為標準的 小數點後一位數 格式,適合視覺化顯示評分結果。
- 如果
rating
為空或null
,則回傳預設值"0.0"
。
📌 步驟拆解
- 檢查數值是否存在
return rating ? ...
- 如果
rating
有值,繼續執行轉換。 - 如果
rating
為null
或undefined
,則回傳"0.0"
。
- 如果
- 轉換為浮點數 (小數格式)
parseFloat(rating)
- 將
rating
轉換為浮點數,確保數值正確計算。 - 範例:
"4.678"
→4.678
- 將
- 限制小數位數
.toFixed(1)
- 使用
.toFixed(1)
將結果限制為小數點後 1 位數。 - 範例:
4.678
→4.7
- 使用
- 預設值處理
: '0.0'
- 如果
rating
為空值 (null
或undefined
),直接回傳"0.0"
。
- 如果
顯示平均分數
<div class="flex items-center mb-4">
<span
class="font-bold"
x-text="averageRating.toFixed(1)"
style="font-size: 64px;">
0.0
</span>
解釋:
x-text
(Alpine.js 綁定屬性)
x-text="averageRating.toFixed(1)"
x-text
是 Alpine.js 的屬性,用於將變數的值直接插入到 HTML 元素中並顯示。- 這裡的
averageRating.toFixed(1)
會將averageRating
格式化為小數點後 1 位數,並顯示在這個<span>
元素內。
0.0
(預設值)
0.0
是 靜態 HTML 內容,它僅作為預設值,用於:- 頁面載入時 JavaScript 尚未執行前。
- 如果
x-data
尚未初始化完成,這個靜態數值會先顯示。
頁面載入流程 (渲染階段)
載入時發生的流程:
- 頁面初始狀態 (未執行 JavaScript)
- 瀏覽器會直接渲染靜態 HTML。
- 這時候畫面上會顯示
0.0
,因為 JavaScript 還未執行。
- Alpine.js 初始化 (
x-data
)- Alpine.js 解析
x-data
並將數據 (averageRating
) 綁定到畫面上。
- Alpine.js 解析
- 數據更新
- 如果
averageRating
有值,x-text
會立即將0.0
替換成計算後的分數,例如4.5
。
- 如果
渲染範例 (逐步變化)
<!-- 初始狀態 (JavaScript 尚未執行) -->
<span class="font-bold" style="font-size: 64px;">0.0</span>
<!-- JavaScript 加載完成後 -->
<span class="font-bold" style="font-size: 64px;">4.5</span>
渲染星星
<div class="flex text-yellow-400 text-3xl">
<template x-for="n in 5">
<span x-text="n <= Math.round(averageRating) ? '★' : '☆'"></span>
</template>
</div>
解釋:
它的目的是用 Alpine.js 來根據平均評分 (averageRating
) 動態渲染「實心星 (★)」或「空心星 (☆)」。
<template x-for>
(建立一個循環)
<template x-for="n in 5">
➡️ 這行的作用:
x-for="n in 5"
表示建立一個從1
到5
的循環 (5 顆星)。- 會自動生成 5 個
<span>
元素,每個元素對應一個星星。 n
代表目前的星星索引,從1
開始數到5
。
x-text
(根據條件渲染星星)
<span x-text="n <= Math.round(averageRating) ? '★' : '☆'"></span>
➡️ 這行的作用:
x-text
是 Alpine.js 的屬性,用於將動態數據插入到元素中。- 這裡的判斷條件:
n <= Math.round(averageRating) ? '★' : '☆'
意思是:- 如果
n
(第幾顆星) 小於或等於 四捨五入後的平均分數 (Math.round(averageRating)
),就顯示實心星 (★
)。 - 否則,顯示空心星 (
☆
)。
- 如果
範例說明 (平均分數 4.2)
averageRating = 4.2
渲染流程:
n = 1
→1 <= 4
✅ → 顯示★
n = 2
→2 <= 4
✅ → 顯示★
n = 3
→3 <= 4
✅ → 顯示★
n = 4
→4 <= 4
✅ → 顯示★
n = 5
→5 <= 4
❌ → 顯示☆
最終渲染結果:
★★★★☆
範例說明 (平均分數 2.7)
averageRating = 2.7
渲染流程:
n = 1
→1 <= 3
✅ → 顯示★
n = 2
→2 <= 3
✅ → 顯示★
n = 3
→3 <= 3
✅ → 顯示★
n = 4
→4 <= 3
❌ → 顯示☆
n = 5
→5 <= 3
❌ → 顯示☆
最終渲染結果:
★★★☆☆
顯示總評論數
<p class="text-gray-600" x-text="`${totalReviews} 則評論`"></p>
解釋:
- 使用
x-text
將totalReviews
綁定到畫面中,顯示總評論數。 - 例如:
150 則評論
。
渲染星等分佈
<template x-for="i in 5" :key="i">
<div class="flex items-center mb-2">
<span class="text-lg font-semibold w-6 text-right" x-text="6-i"></span>
<span class="text-yellow-400 text-2xl ml-2">★</span>
<!-- 進度條 -->
<div class="flex-grow mx-4">
<div class="w-full bg-gray-200 h-4 rounded-lg overflow-hidden">
<div
class="transition-all duration-300"
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
></div>
</div>
</div>
</div>
</template>
解釋:
這段程式碼的功能是根據星等的分佈,使用 Alpine.js 動態生成一個進度條,顯示每個星等的佔比。
第一層容器:進度條的父元素
<div class="flex-grow mx-4">
解釋:
flex-grow
:讓這個元素可以在 Flexbox 中自動填滿剩餘空間。mx-4
:這是 Tailwind CSS 的類別,表示「左右外距 (margin) 各 1rem (16px)」。
第二層容器:進度條背景
<div class="w-full bg-gray-200 h-4 rounded-lg overflow-hidden">
解釋:
w-full
:寬度設為100%
(父元素的全部寬度)。bg-gray-200
:灰色背景,作為進度條的底色。h-4
:高度設定為4
(對應於1rem = 16px
)。rounded-lg
:讓進度條有圓角 (大約8px
的圓角效果)。overflow-hidden
:將超出範圍的內容隱藏,確保進度條不會溢出邊界。
第三層容器 (關鍵):動態控制的進度條
<div
class="transition-all duration-300"
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
></div>
這裡是進度條的核心邏輯:
class="transition-all duration-300"
transition-all
:表示所有 CSS 屬性都將有動畫效果。duration-300
:動畫時間設定為300ms
。
動態綁定樣式 (:style
)
這裡使用了 Alpine.js 的動態綁定屬性 :style
,用來根據數據變化調整進度條的寬度與顏色。
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
這段程式碼拆解:
- 寬度 (width)
- 使用
calculatePercentage()
方法計算進度條寬度。 starsCount[6-i]
:取第6-i
顆星的數量 (因為是反向渲染從5
星到1
星)。|| 0
:如果starsCount[6-i]
沒有值 (undefined),預設為0
。
- 使用
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
- 高度 (height)
- 將高度固定為
16px
,這與h-4
是一樣的效果,但這裡透過內聯樣式設定。
- 將高度固定為
height: 16px;
- 顏色 (background-color)
- 進度條的填充顏色設定為黃色 (
#FBBF24
),對應於 Tailwind CSS 中的text-yellow-400
。
- 進度條的填充顏色設定為黃色 (
background-color: #FBBF24;
範例說明 (假設 5 星有 50 人,總評論 100 人)
calculatePercentage(50, 100) // ➡️ 50%
width: 50%
→ 進度條會填滿一半寬度。
範例 (視覺效果)
假設有以下資料:
starsCount = {5: 50, 4: 30, 3: 10, 2: 5, 1: 5}
totalReviews = 100
最終渲染結果 (進度條視覺化):
- 5 星:50% →
★★★★☆
+ [███████▒▒▒▒▒] - 4 星:30% →
★★★☆☆
+ [████▒▒▒▒▒▒▒▒] - 3 星:10% →
★★☆☆☆
+ [██▒▒▒▒▒▒▒▒▒▒] - 2 星:5% →
★☆☆☆☆
+ [█▒▒▒▒▒▒▒▒▒▒▒] - 1 星:5% →
★☆☆☆☆
+ [█▒▒▒▒▒▒▒▒▒▒▒]
總結
這段 Alpine.js 代碼完成了以下功能:
- 平均分數與星星渲染
- 顯示總評論數
- 計算並渲染星等分佈進度條
- 透過 Django 模板將後端數據傳遞到前端
這樣的設計簡潔且互動性高,適合用於服務評價、電商商品評論等情境。