.planet
)。<div class="planet">
→ 永遠有 planet
這個樣式,不會改變。:class
動態綁定:根據條件切換造型(例如被選取就高亮、可居住就加徽章)。:style
動態綁定:用程式計算細緻效果(例如依危險等級調整發光強度)。:class="{ active: isActive, habitable: p.habitable }"
:class="['planet', isGas && 'gas-giant']"
:style="{ boxShadow: '0 0 20px #ffd', transform: 'scale(1.05)' }"
:style="[baseStyle, dynamicStyle]"
<style scoped>
限定在此元件生效;複雜條件→用 computed/函式產生 class/style 可讀性更高。修改 src/App.vue
測試(Vue 3,<script setup>
)
<template>
<main class="cosmos">
<header class="head">
<h2>🪐 星球標籤雲</h2>
<p>點一下選取喜歡的星球。</p>
</header>
<ul class="cloud">
<li
v-for="t in tags"
:key="t.label"
class="chip"
:class="chipClasses(t)"
:style="{
'--h': hue(t.theme), // 色相控制整套色彩
'--g': glow(t), // 光暈強度
}"
@click="toggle(t)"
>
<span class="dot" aria-hidden="true"></span>
<span class="text">{{ t.label }}</span>
</li>
</ul>
<footer class="pick">
<span>Selected:</span>
<code v-if="selected.size">{{ [...selected].join(', ') }}</code>
<em v-else>(尚未選取)</em>
</footer>
</main>
</template>
<script setup>
import { ref } from 'vue'
const tags = ref([
{ label: 'Andromeda', theme: 'violet', hot: true },
{ label: 'Orion', theme: 'blue', hot: false },
{ label: 'Pegasus', theme: 'cyan', hot: false },
{ label: 'Lyra', theme: 'pink', hot: true },
{ label: 'Cassiopeia',theme: 'teal', hot: false },
{ label: 'Auriga', theme: 'amber', hot: false },
{ label: 'Draco', theme: 'purple', hot: true },
])
const selected = ref(new Set())
function isActive(tag) {
return selected.value.has(tag.label)
}
// 動態 class(物件語法)
function chipClasses(tag) {
return {
active: isActive(tag),
pulse: tag.hot,
}
}
function toggle(tag) {
const s = selected.value
s.has(tag.label) ? s.delete(tag.label) : s.add(tag.label)
// 觸發更新(Set 是可變的,重指派一下)
selected.value = new Set(s)
}
// 動態 style:色相 & 光暈
function hue(theme) {
switch (theme) {
case 'violet': return 268
case 'purple': return 285
case 'pink': return 320
case 'blue': return 220
case 'cyan': return 195
case 'teal': return 170
case 'amber': return 42
default: return 230
}
}
function glow(tag) {
// 被選取或熱門時提高光暈強度(0~1)
return (tag.hot ? 0.4 : 0.18) + (isActive(tag) ? 0.35 : 0)
}
</script>
<style scoped>
:root { color-scheme: light dark; }
/* 星幕背景(純 CSS) */
.cosmos {
--bg: radial-gradient(1200px 600px at 20% -10%, #3b0764 0%, transparent 55%),
radial-gradient(900px 450px at 90% 0%, #1d4ed8 0%, transparent 60%),
radial-gradient(700px 350px at 60% 80%, #0f766e 0%, transparent 55%);
position: relative;
min-height: 60vh;
padding: 36px 18px 28px;
background:
#0b1020
/* 星點 */
,radial-gradient(1px 1px at 20% 30%, #ffffff88 30%, transparent 31%)
,radial-gradient(1px 1px at 65% 60%, #ffffff66 30%, transparent 31%)
,radial-gradient(1px 1px at 85% 25%, #fff 30%, transparent 31%)
,radial-gradient(1px 1px at 35% 75%, #ffffff55 30%, transparent 31%);
overflow: hidden;
}
.cosmos::before{
content:"";
position:absolute; inset:0;
background: var(--bg);
opacity:.35; pointer-events:none;
}
.head { position: relative; z-index: 1; max-width: 960px; margin: 0 auto 8px; }
.head h2 { margin: 0 0 6px; font: 800 22px/1.2 ui-sans-serif, system-ui; color: #e5e7eb; }
.head p { margin: 0; color: #9ca3af; }
.cloud {
position: relative; z-index: 1;
max-width: 960px; margin: 14px auto 0;
display: flex; flex-wrap: wrap; gap: 10px 12px;
padding: 0; list-style: none;
}
/* 靜態 class:chip 基底造型 */
.chip {
--h: 220; /* 動態: 色相 */
--g: .15; /* 動態: 光暈強度 0~1 */
--bg: hsl(var(--h) 65% 14%);
--ring: hsl(var(--h) 85% 70% / .65);
position: relative;
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 12px; border-radius: 999px;
border: 1px solid hsl(var(--h) 45% 40% / .5);
background:
linear-gradient(180deg, hsl(var(--h) 40% 16% / .9), hsl(var(--h) 35% 10% / .9));
color: #e5e7eb;
cursor: pointer;
transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease, background .18s ease, opacity .18s ease;
box-shadow:
0 0 calc(22px * var(--g)) hsl(var(--h) 90% 65% / calc(.35 * var(--g))),
inset 0 0 0 999px hsl(0 0% 100% / 0); /* 用於 active 的內發光 */
}
.chip:hover { transform: translateY(-1px); }
/* 霓虹小行星點 */
.dot {
width: 8px; height: 8px; border-radius: 999px;
background: hsl(var(--h) 95% 70%);
box-shadow: 0 0 10px hsl(var(--h) 90% 65% / .9);
}
/* 文字 */
.text { font-weight: 700; letter-spacing: .2px; }
/* 動態 class:被選取 */
.chip.active {
border-color: var(--ring);
box-shadow:
0 0 calc(28px * var(--g)) hsl(var(--h) 95% 70% / .45),
inset 0 0 0 999px hsl(var(--h) 60% 40% / .12);
}
/* 動態 class:點擊後 → 邊框發亮 */
@keyframes nebula {
0%, 100% { box-shadow: 0 0 calc(22px * var(--g)) hsl(var(--h) 90% 65% / .32) }
50% { box-shadow: 0 0 calc(34px * var(--g)) hsl(var(--h) 90% 65% / .55) }
}
.chip.pulse { animation: nebula 2.2s ease-in-out infinite; }
/* 底部選取顯示 */
.pick {
position: relative; z-index: 1;
max-width: 960px; margin: 16px auto 0;
color: #808080;
}
.pick code { color: #808080; }
.pick em { color: #ff8888; }
</style>
class="chip"
。:class="{ active: ..., pulse: ... }"
控制選取/熱門狀態。:style="{ '--h': hue(tag.theme), '--g': glow(tag) }"
傳 CSS 變數,統一管理色相與光暈強度。@click="toggle(tag)"
切換 selected
集合,決定哪些標籤亮起。active
→ 邊框/光暈加強,顯示已選取。pulse
→ 套用動畫,熱門標籤有霓虹脈動。radial-gradient
疊出星雲與星點,不用圖片就能做出宇宙氛圍。今天我們學會了動態樣式綁定,畫面終於不再死板。明天 Day 11,要更進一步認識 Vue 的 slot 與元件組合:如何做一個「星球卡片元件」,能重複使用又能塞進不同的內容!
參考資源
https://vuejs.org/guide
https://www.runoob.com/vue3