拉近或分開:用親密性原則,讓版面一目了然

只要分門別類,易讀程度就能提升!一邊整理資訊,一邊分門別類,再以版面配置呈現。

BEFORE / AFTER:把馬卡龍與品名拉近或分開

為什麼要「拉近或分開」?

這是格式塔(Gestalt)的親密性原則(Proximity)

  • 相關的內容拉近一點 → 大腦自動把它們讀成「同一組」。
  • 不相干的內容分開一點 → 自然產生分界,不需要額外的線框或顏色。

上圖的 BEFORE 裡,品名(Strawberry、Citrus…)和馬卡龍的距離都一樣遠, 你會看不出哪個名字配哪個。AFTER 把插圖對應品名拉近、遠離不對的名稱, 再把相關的兩兩一組、不相關的拉開——版面瞬間清楚。

下面用同一個馬卡龍卡片,分別做出 Vanilla / Vue / React 三種版本。 切換 BEFORE / AFTER,觀察「間距」如何改變可讀性。


互動示範(Vanilla,直接在頁面跑)


三種版本的程式碼

關鍵都一樣:資料只有一份,差別只在 gap(間距)。 親密性不是加東西,而是「調整留白」。

1. Vanilla(HTML + CSS + JS)

<div class="macaron-grid" data-mode="after">
  <!-- group 0 -->
  <div class="group">
    <figure><span class="dot" style="--c:#f7a8b8"></span>
      <figcaption><i>Strawberry</i><small>草莓</small></figcaption></figure>
    <figure><span class="dot" style="--c:#f5d76e"></span>
      <figcaption><i>Citrus</i><small>香檬</small></figcaption></figure>
  </div>
  <!-- group 1 -->
  <div class="group">
    <figure><span class="dot" style="--c:#a8d08d"></span>
      <figcaption><i>Pistachio</i><small>開心果</small></figcaption></figure>
    <figure><span class="dot" style="--c:#c3a6d8"></span>
      <figcaption><i>Lavender</i><small>薰衣草</small></figcaption></figure>
  </div>
</div>

<style>
  /* AFTER:品名拉近插圖、相關兩兩成組 */
  .macaron-grid { display:flex; justify-content:center; gap:48px; }
  .group        { display:flex; gap:20px; }
  figure        { margin:0; text-align:center; }
  .dot          { display:block; width:64px; height:44px;
                  border-radius:999px; background:var(--c); margin:0 auto; }
  figcaption    { margin-top:6px; }      /* 拉近:留白變小 */
  figcaption i  { font-style:italic; color:#666; }
  figcaption small { display:block; font-size:14px; color:#999; }

  /* BEFORE:全部均勻散開,名字離插圖一樣遠 */
  .macaron-grid[data-mode="before"]            { gap:20px; }
  .macaron-grid[data-mode="before"] .group     { gap:20px; }
  .macaron-grid[data-mode="before"] figcaption { margin-top:28px; } /* 分開 */
</style>

2. Vue 3(<script setup>

<script setup>
import { ref, computed } from 'vue'

const after = ref(true)
const macarons = [
  { name: 'Strawberry', flavor: '草莓',   color: '#f7a8b8', group: 0 },
  { name: 'Citrus',     flavor: '香檬',   color: '#f5d76e', group: 0 },
  { name: 'Pistachio',  flavor: '開心果', color: '#a8d08d', group: 1 },
  { name: 'Lavender',   flavor: '薰衣草', color: '#c3a6d8', group: 1 },
]
// AFTER 才分組;BEFORE 全部攤在同一組
const groups = computed(() =>
  after.value
    ? [0, 1].map(g => macarons.filter(m => m.group === g))
    : [macarons]
)
</script>

<template>
  <button @click="after = !after"></button>

  <div class="macaron-grid" :style="{ gap: after ? '48px' : '20px' }">
    <div class="group" v-for="(group, i) in groups" :key="i">
      <figure v-for="m in group" :key="m.name">
        <span class="dot" :style="{ background: m.color }"></span>
        <figcaption :style="{ marginTop: after ? '6px' : '28px' }">
          <i></i><small></small>
        </figcaption>
      </figure>
    </div>
  </div>
</template>

<style scoped>
.macaron-grid { display:flex; justify-content:center; }
.group        { display:flex; gap:20px; }
figure        { margin:0; text-align:center; }
.dot          { display:block; width:64px; height:44px;
                border-radius:999px; margin:0 auto; }
figcaption i  { font-style:italic; color:#666; }
figcaption small { display:block; font-size:14px; color:#999; }
</style>

3. React(function component + hooks)

import { useState } from 'react'

const macarons = [
  { name: 'Strawberry', flavor: '草莓',   color: '#f7a8b8', group: 0 },
  { name: 'Citrus',     flavor: '香檬',   color: '#f5d76e', group: 0 },
  { name: 'Pistachio',  flavor: '開心果', color: '#a8d08d', group: 1 },
  { name: 'Lavender',   flavor: '薰衣草', color: '#c3a6d8', group: 1 },
]

export default function MacaronGrid() {
  const [after, setAfter] = useState(true)

  // AFTER 才分組;BEFORE 全部攤在同一組
  const groups = after
    ? [0, 1].map(g => macarons.filter(m => m.group === g))
    : [macarons]

  return (
    <>
      <button onClick={() => setAfter(a => !a)}>{after ? 'AFTER' : 'BEFORE'}</button>

      <div style=>
        {groups.map((group, i) => (
          <div key={i} style=>
            {group.map(m => (
              <figure key={m.name} style=>
                <span style= />
                <figcaption style=>
                  <i style=>{m.name}</i>
                  <small style=>
                    {m.flavor}
                  </small>
                </figcaption>
              </figure>
            ))}
          </div>
        ))}
      </div>
    </>
  )
}

重點整理

狀態 品名與插圖的間距 群組之間的間距 結果
BEFORE 一樣遠(28px) 均勻平鋪 看不出歸屬
AFTER 拉近(6px) 相關成組、拉開(48px) 一目了然

一句話: 親密性靠的不是裝飾,而是「該近的近、該遠的遠」。 三種框架寫法不同,但邏輯一致——資料一份,間距決定意義。