前言
不知不覺鐵人賽已經推進到第 10 天了!三分之一的里程碑達成~
前面幾天我們把專案的外觀、互動音效和過場動畫都打磨得很漂亮,就像是把一間診所的裝潢跟門面都弄好了一樣。不過,既然這是一個「賽博紓壓診所與解碼器」,真正的靈魂當然是裡面的心理測驗本身。
一開始在構思測驗畫面的時候,最暴力的做法就是直接在 HTML 裡面寫 10 個 到 ,然後把它們隱藏起來慢慢切換。但只要想到之後如果要改題目、加選項或是調整順序,光是找對應的 HTML 標籤絕對會改到懷疑人生。
更重要的是,我不想讓這 10 題都只是無聊的「A, B, C」單選題!為了扣合這個專案的惡搞設定,我決定在 10 題裡面安插 2 到 3 題的「求籤筒」機制——你不只要回答問題,還要交給命運來抽出隨機選項!今天我們就用 JavaScript 來實現這套混搭風的動態題庫系統。
核心觀念:資料驅動畫面與條件渲染
與其把文字寫死在 HTML 裡,不如把 HTML 當成一個「空殼」,讓 JavaScript 把資料填進去。
我們只需要準備兩個東西:
1.題庫陣列(Array of Objects): 裡面包含題目文字、選項陣列,還要多加一個屬性 type。「一般」的題目設為 normal,「求籤」的題目設為 draw。
2.渲染函式(Render Function): 每次換題時,判斷這一題的 type 是什麼。如果是 normal,就乖乖印出 A, B, C 三個按鈕;如果是 draw,就先印出一顆大大的「搖籤筒」按鈕,點擊後再用亂數決定抽到哪一個選項!
這樣一來,不管題庫長怎樣,HTML 結構永遠只有乾淨的一組,後續維護起來簡直身心舒暢!
動手寫 Code:建置題庫陣列與動態渲染邏輯
為了聚焦在今天的核心,這段程式碼我們單獨抽出了「測驗畫面」來做獨立示範。裡面包含了一道一般情境題、一道求籤題,以及如何把它們印到畫面上並處理抽籤的完整邏輯:
/* 一般選項按鈕樣式 */
.option-btn {
background-color: #f5f5f5;
border: 2px solid transparent;
border-radius: 14px;
padding: 14px;
width: 100%;
margin-bottom: 12px;
text-align: left;
cursor: pointer;
transition: all 0.2s;
font-size: 0.95rem;
color: #263238;
}
.option-btn:hover {
border-color: #1e88e5;
transform: scale(1.02);
background-color: #e3f2fd;
}
/* 搖籤筒專屬按鈕樣式 (已改為廟宇紅) */
.draw-btn {
background-color: #d32f2f; /* 經典廟宇紅 */
color: white;
border: none;
border-radius: 50px;
padding: 15px 40px;
font-size: 1.2rem;
cursor: pointer;
transition: 0.2s;
font-weight: bold;
box-shadow: 0 4px 15px rgba(211, 47, 47, 0.4); /* 紅色光暈 */
}
.draw-btn:hover {
background-color: #b71c1c; /* 移上去變深紅色 */
transform: translateY(-2px);
}
/* 抽籤結果顯示區 */
#draw-result {
margin-top: 20px;
font-size: 1.1rem;
font-weight: bold;
color: #d32f2f;
min-height: 30px;
}
</style>
<div class="card" id="quiz-screen">
<!-- HTML 容器空殼 -->
<span id="progress-text" class="progress">Question 1 of 10</span>
<h2 id="question-text">題目載入中...</h2>
<div id="options-container"></div>
<div id="draw-result"></div>
</div>
<script>
// 1. 建立題庫資料庫 (包含一般題型與求籤題型)
const quizData = [
{
type: "normal",
question: "早上鬧鐘響了,但你昨天熬夜趕專題,現在的你會:",
options: [
{ text: "A. 直接按掉,催眠自己教授今天會請假", scores: { "日常": -2, "放鬆": 3 } },
{ text: "B. 痛苦地爬起來,靠三杯黑咖啡續命", scores: { "效率": 3, "心態": -2 } },
{ text: "C. 傳訊息跟同學求救「幫點名」", scores: { "社交": 2, "心態": 1 } }
]
},
{
type: "draw",
question: "來到廟宇,面對未知的死線,請誠心搖動籤筒抽出今日運勢:",
options: [
{ text: "【大吉】教授突然宣布延期交件!", scores: { "放鬆": 5 } },
{ text: "【中吉】剛好找到神級開源程式碼,無痛套用。", scores: { "效率": 4 } },
{ text: "【大凶】電腦無預警死機,而且你沒存檔。", scores: { "心態": -5 } }
]
}
];
let currentQuestionIndex = 0;
// 2. 動態渲染題目的核心函式
function loadQuestion(index) {
const qData = quizData[index];
const optionsContainer = document.getElementById('options-container');
const drawResult = document.getElementById('draw-result');
// 更新進度與文字,並清空上一題的殘留內容
document.getElementById('progress-text').innerText = `Question ${index + 1} of ${quizData.length}`;
document.getElementById('question-text').innerText = qData.question;
optionsContainer.innerHTML = '';
drawResult.innerText = '';
// 3. 判斷題型,決定要長出什麼按鈕
if (qData.type === "normal") {
// 一般題型:直接印出所有選項
qData.options.forEach((opt) => {
const btn = document.createElement('button');
btn.className = 'option-btn';
btn.innerText = opt.text;
btn.onclick = () => handleSelectOption(opt.scores);
optionsContainer.appendChild(btn);
});
} else if (qData.type === "draw") {
// 求籤題型:印出一顆搖籤筒按鈕
const drawBtn = document.createElement('button');
drawBtn.className = 'draw-btn';
drawBtn.innerText = '🎲 搖動籤筒';
drawBtn.onclick = () => handleDraw(qData.options);
optionsContainer.appendChild(drawBtn);
}
}
// 處理求籤邏輯
function handleDraw(options) {
const drawResult = document.getElementById('draw-result');
// 隨機選出一個選項
const randomOption = options[Math.floor(Math.random() * options.length)];
// 顯示抽籤結果,稍微停頓一下再跳下一題,製造驚喜感
drawResult.innerText = `鏘鏘!你抽到了:\n${randomOption.text}`;
setTimeout(() => {
handleSelectOption(randomOption.scores);
}, 2000); // 停頓 2 秒讓玩家看清楚結果
}
// 處理選項點擊與換題邏輯
function handleSelectOption(scores) {
if (currentQuestionIndex < quizData.length - 1) {
currentQuestionIndex++;
loadQuestion(currentQuestionIndex);
} else {
alert("測驗結束!準備進入結果報表。");
}
}
// 網頁載入時立刻渲染第一題
window.onload = () => {
loadQuestion(currentQuestionIndex);
};
</script>
開發心得與設計考量
在想這個專題時,我一直覺得如果只是一直點 A, B, C,做到第 5 題大概就會開始想打哈欠了。混入「求籤」這個機制,不僅很符合我們主打的紓壓風格,也把單向的心理測驗變成有點像小遊戲的體驗(惡言消滅器同理)。
技術層面上,雖然用 JavaScript 建立 DOM 元素(document.createElement())看起來程式碼稍微長了一點點,但在綁定點擊事件時真的非常乾淨,不會發生引號卡來卡去、字串串接錯誤的慘劇。而且只要維護好 quizData 這個陣列,想隨時擴充到 10 題甚至 20 題都輕鬆的很!
另外,大家應該有注意到我在資料裡預先埋了 scores 這個物件,這就是為了之後複雜的分析報表鋪路。每個人抽到的籤跟做的選擇,都會影響到不同維度的心理健康值。
今日結語與明日預告
今天我們寫出了整個專題的核心資料結構,成功讓 JavaScript 負責產出題目,還實作了充滿驚喜感的抽籤系統!
既然今天埋了 scores 的伏筆,明天 Day 11:維度計分機制:如何將零碎選項匯聚成六角雷達圖的數據,我們就要把這些點擊背後的分數真正「存起來」,建立一套多維度的累加系統,為之後的診斷報告做好萬全準備!我們明天見!