iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Vue.js

邊學邊做:Vue.js 實戰養成計畫系列 第 10

Day 10:CSS 星塵 — Vue 中的樣式與 class 綁定

  • 分享至 

  • xImage
  •  

為什麼要綁定樣式?

  • 靜態 class:固定造型(例如 .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]"
  • Tips:<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:固定造型,例如 class="chip"
  • 動態 class:用 :class="{ active: ..., pulse: ... }" 控制選取/熱門狀態。
  • 動態 style:用 :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


上一篇
Day 9:計算的軌道 — computed 與 watch
系列文
邊學邊做:Vue.js 實戰養成計畫10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言