# Day 54 verifyPassword 的第一個 Vitest 測試（續）；CSS 留白與定位屬性

By [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake) · 2026-02-05

---

今日閱讀：《單元測試的藝術》3/e 2.9 p.65 - 2.11 p.71 第二章完

參數化的測試程式
--------

測試相同的情境，但工作單元的輸入有一些小變化時使用 先前練習題已經做過，略讀

Act API：`test.each([])` 用陣列封裝輸入的參數，Vitest 會自動映射到對應位置，對每個陣列執行一次 `[ parameter, asserertion ]`完成相同情境不同輸入值的測試

使用JS 原生 for loop 也可以做到，但要考慮可讀性 另外，使用參數化測試要注意是否把不同條件包在一起測試了，以書中範例為例，輸入全小寫字母與一個大寫字母就應該被視為不同情境，拆開測試

檢查預期會被拋出的錯誤
-----------

先前練習題已經做過，略讀

Assersion API：`expect().toThrowError()`

作者附註的快照測試 API 也練習過用來比對 ASCII 圖形處理結果`toMatchInlineSnapshot`

測試分類
----

### 一、Vitest 測試分類功能對照

#### Jest vs Vitest 對照表

功能

Jest

Vitest

差異說明

檔案路徑過濾

`--testPathPattern`

直接傳入檔案名稱

Vitest 簡化語法 [vitest](https://vitest.dev/guide/filtering)

測試名稱過濾

`-t, --testNamePattern`

`-t, --testNamePattern`

完全相同 [vitest](https://vitest.dev/guide/cli)

配置檔案

`jest.config.js`

`vitest.config.js`

格式類似 [vitest](https://vitest.dev/config/)

指定測試模式

`testMatch`

`include`

語意相同 [lutece.github](https://lutece.github.io/vitest.kr/config/)

排除測試

`testPathIgnorePatterns`

`exclude`

語意相同 [lutece.github](https://lutece.github.io/vitest.kr/config/)

多專案配置

\-

`projects`

Vitest 獨有 [vitest](https://vitest.dev/guide/projects)

測試標籤

\-

`--tags-filter`

Vitest 4.1+ 獨有 [main.vitest](https://main.vitest.dev/guide/test-tags)

#### CLI 指令範例

    # 檔案路徑過濾
    vitest basic                    # 執行包含 "basic" 的測試檔案
    vitest auth login               # 執行包含 "auth" 和 "login" 的檔案
    
    # 測試名稱過濾
    vitest -t "user login"          # 根據測試名稱過濾
    vitest --testNamePattern="auth.*"  # 使用正則表達式
    
    # 指定檔案與行號(Vitest 3+)
    vitest basic/foo.test.ts:10     # 執行特定檔案的特定行測試
    

#### 配置檔案:使用 Projects 分類

    // vitest.config.js
    import { defineConfig } from 'vitest/config'
    
    export default defineConfig({
      test: {
        // 基本配置
        include: ['**/*.{test,spec}.{js,ts}'],
        exclude: ['node_modules', 'dist', '**/*.e2e.test.js'],
        
        // 多專案配置
        projects: [
          {
            // 單元測試專案
            test: {
              name: 'unit',
              include: ['**/*.unit.test.ts'],
              isolate: false,  // 提升速度
            },
          },
          {
            // 整合測試專案
            test: {
              name: 'integration',
              include: ['**/*.integration.test.ts'],
              testTimeout: 30000,  // 較長超時
            },
          },
        ],
      },
    })
    

#### package.json 腳本設定

    {
      "scripts": {
        "test": "vitest --project=unit",           // 開發時持續執行
        "test:integration": "vitest --project=integration",
        "test:all": "vitest",                      // CI/CD 完整測試
        "test:auth": "vitest auth",                // 測試特定功能模組
        "precommit": "vitest run --project=unit --changed"  // Git hook
      }
    }
    

* * *

### 二、為什麼需要測試分類?

#### 1\. 執行速度差異

**核心差異**

*   **單元測試**:隔離測試個別元件,純邏輯運算,執行時間 < 10ms [repeato](https://www.repeato.app/effective-strategies-for-separating-unit-tests-from-integration-tests/)
    
*   **整合測試**:驗證多元件協作,涉及外部資源(資料庫、網路、檔案系統),執行時間 > 500ms [stackoverflow](https://stackoverflow.com/questions/37141304/unit-tests-become-integration-tests-with-tdd)
    

    // 單元測試:快速(< 10ms)
    describe('calculateDiscount', () => {
      it('應計算九折優惠', () => {
        expect(calculateDiscount(100, 0.9)).toBe(90);
      });
    });
    
    // 整合測試:較慢(> 500ms)
    describe('UserRepository', () => {
      it('應從資料庫讀取使用者資料', async () => {
        const user = await userRepo.findById(123);
        expect(user.name).toBe('John');
      });
    });
    

**測試套件膨脹問題**

專案成長後測試數量可達數百至數千個: [repeato](https://www.repeato.app/effective-strategies-for-separating-unit-tests-from-integration-tests/)

*   不分類:全部測試可能需要 30 分鐘以上
    
*   分類後:單元測試 2-3 分鐘,整合測試按需執行
    

#### 2\. TDD 紅綠重構循環需求

**幾秒鐘完成一輪循環**

TDD 講究「紅燈 → 綠燈 → 重構」的快速迭代,理想循環時間為數秒鐘 。如果測試套件包含慢速整合測試,會打斷開發心流,失去即時回饋優勢。 [itential](https://www.itential.com/blog/netdevops/unit-tests-integration-tests-and-test-driven-design/)

**開發節奏對比**

*   **有測試分類**:改代碼 → 2 秒後看到單元測試結果 → 繼續開發
    
*   **無測試分類**:改代碼 → 等待 5 分鐘全部測試跑完 → 心流中斷
    

#### 3\. 測試穩定性與維護成本

**隔離測試的穩定性**

*   **單元測試**:不受外部環境影響,測試結果穩定可靠 [vocus](https://vocus.cc/article/64f469d3fd89780001a5ab7e)
    
*   **整合測試**:可能因網路波動、資料庫狀態、第三方 API 變更產生「片狀測試」(flaky test),需額外維護 [stackoverflow](https://stackoverflow.com/questions/37141304/unit-tests-become-integration-tests-with-tdd)
    

**測試失敗的診斷效率**

*   **單元測試失敗**:精準定位到特定函式問題
    
*   **整合測試失敗**:需檢查多個元件互動,診斷時間較長 [codefresh](https://codefresh.io/learn/unit-testing/unit-testing-vs-integration-testing-5-key-differences-and-why-you-need-both/)
    

#### 4\. 測試演化與重構

**單元測試可能變成整合測試**

隨著重構,原本的單元測試可能演變成整合測試: [stackoverflow](https://stackoverflow.com/questions/37141304/unit-tests-become-integration-tests-with-tdd)

    // 最初:單一類別的單元測試
    describe('OrderProcessor', () => {
      it('應計算訂單總金額', () => {
        const processor = new OrderProcessor();
        expect(processor.calculateTotal([{price: 100}])).toBe(100);
      });
    });
    
    // 重構後:多個類別協作的整合測試
    describe('OrderService', () => {
      it('應處理完整訂單流程', () => {
        // 現在涉及 OrderProcessor、PaymentGateway、InventoryService
        const result = orderService.processOrder(orderData);
        expect(result.status).toBe('success');
      });
    });
    

此時應為新拆分的類別撰寫新的單元測試,並將原測試重新分類為整合測試。

#### 判斷整合測試的標準

符合以下任一條件即為整合測試: [ithelp.ithome.com](https://ithelp.ithome.com.tw/articles/10229734)

*   與外部環境互動(資料庫、檔案系統、網路、時間)
    
*   需要多個真實元件協作
    
*   執行時間明顯較長
    

* * *

### 三、測試分類對 CI/CD 的好處

#### 1\. 快速回饋循環

**階段性品質閘門**

設計多層測試閘門,越快的測試越早執行: [codefresh](https://codefresh.io/learn/ci-cd-pipelines/ci-cd-process-flow-stages-and-critical-best-practices/)

    # .github/workflows/ci.yml
    name: CI Pipeline
    
    on: [push, pull_request]
    
    jobs:
      # 第一階段:單元測試(1-3 分鐘)
      unit-tests:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v3
          - run: npm install
          - run: npm run test:unit
    
      # 第二階段:整合測試(5-10 分鐘)
      integration-tests:
        needs: unit-tests  # 單元測試通過才執行
        runs-on: ubuntu-latest
        steps:
          - run: npm run test:integration
    
      # 第三階段:E2E 測試(15-30 分鐘)
      e2e-tests:
        needs: integration-tests
        runs-on: ubuntu-latest
        steps:
          - run: npm run test:e2e
    

**即時問題定位**

*   單元測試 2 分鐘內失敗 → 記憶猶新時修正,成本低
    
*   混合測試 30 分鐘後失敗 → 已切換任務,修復成本高 [provar](https://provar.com/blog/uncategorized/benefits-continuous-testing-ci-cd-pipeline/)
    

#### 2\. 資源使用效率

**快速失敗策略**

CI/CD 平台通常按運算時數計費,測試分類可避免浪費: [33rdsquare](https://www.33rdsquare.com/speed-up-ci-cd-pipelines-with-parallel-testing/)

    // vitest.config.js - 設定失敗即停止
    export default defineConfig({
      test: {
        bail: 1,  // 第一個失敗就停止
      },
    })
    

**效益範例**

*   單元測試失敗:2 分鐘 × 成本 → 立即停止
    
*   不分類全跑:30 分鐘 × 成本 → 浪費 15 倍資源 [codefresh](https://codefresh.io/learn/ci-cd-pipelines/ci-cd-testing-why-its-essential-and-how-to-make-it-great/)
    

#### 3\. 並行執行最佳化

**Vitest Sharding 分片執行**

    # CI 環境分配到 3 台機器
    # Machine 1
    vitest --project=unit --shard=1/3 --reporter=blob
    
    # Machine 2
    vitest --project=unit --shard=2/3 --reporter=blob
    
    # Machine 3
    vitest --project=unit --shard=3/3 --reporter=blob
    vitest --project=integration --reporter=blob
    
    # 最後合併報告
    vitest --merge-reports --reporter=junit
    

**時間節省計算**

假設測試套件:

*   單元測試:300 個(共 10 分鐘)
    
*   整合測試:50 個(共 20 分鐘)
    

執行策略

時間

不分類順序執行

30 分鐘

分類並行執行

23.3 分鐘

智慧分配(單元與整合同時跑)

20 分鐘 [33rdsquare](https://www.33rdsquare.com/speed-up-ci-cd-pipelines-with-parallel-testing/)

#### 4\. 不同觸發情境的測試策略

**精準匹配測試範圍**

觸發情境

執行測試

時間

目的

本地提交前

單元測試

1-2 分鐘

快速驗證邏輯正確性 [dzone](https://dzone.com/articles/testing-in-cicd)

Push 到分支

單元 + 整合

5-15 分鐘

確保元件協作正常 [codefresh](https://codefresh.io/learn/ci-cd-pipelines/ci-cd-process-flow-stages-and-critical-best-practices/)

Pull Request

全部測試

30-60 分鐘

完整品質驗證 [codefresh](https://codefresh.io/learn/ci-cd-pipelines/ci-cd-testing-why-its-essential-and-how-to-make-it-great/)

定時任務(每晚)

全部 + 效能測試

數小時

深度回歸測試 [provar](https://provar.com/blog/uncategorized/benefits-continuous-testing-ci-cd-pipeline/)

    // package.json - 不同場景的腳本
    {
      "scripts": {
        "precommit": "vitest run --project=unit --changed",
        "test:ci-push": "vitest run --project=unit --project=integration",
        "test:ci-pr": "vitest run",
        "test:nightly": "vitest run && npm run test:e2e && npm run test:perf"
      }
    }
    

#### 5\. 測試穩定性管理

**針對不同測試類型設定重試策略**

    // vitest.config.js
    export default defineConfig({
      test: {
        projects: [
          {
            name: 'unit',
            include: ['**/*.unit.test.ts'],
            retry: 0,  // 單元測試不重試,失敗即有問題
          },
          {
            name: 'integration',
            include: ['**/*.integration.test.ts'],
            retry: 2,  // 整合測試允許重試 2 次
            testTimeout: 30000,  // 較長超時時間
          },
        ],
      },
    })
    

**穩定性監控目標**

*   單元測試成功率:應維持 99.9%+ [katalon](https://katalon.com/resources-center/blog/how-to-accelerate-your-ci/cd-pipeline-with-parallel-testing)
    
*   整合測試成功率:目標 95%+,需持續優化片狀測試
    

#### 6\. 不同環境的測試配置

**環境隔離策略**

    # CI 配置範例
    jobs:
      unit-tests:
        runs-on: ubuntu-latest  # 輕量環境
        services: {}  # 不需要額外服務
    
      integration-tests:
        runs-on: ubuntu-latest
        services:
          postgres:  # 需要資料庫
            image: postgres:14
          redis:     # 需要快取服務
            image: redis:7
    

**成本控管策略** [codefresh](https://codefresh.io/learn/ci-cd-pipelines/ci-cd-testing-why-its-essential-and-how-to-make-it-great/)

*   單元測試:使用免費 CI runner
    
*   整合測試:使用付費 runner 但限制並行數量
    
*   E2E 測試:僅在 PR 與 merge 時執行
    

* * *

### 四、實務建議

#### 檔案命名慣例

用檔案名稱區分測試類型,方便 Vitest 過濾: [vitest](https://vitest.dev/guide/migration.html)

    src/
    ├── utils/
    │   ├── math.ts
    │   ├── math.unit.test.ts          // 單元測試
    │   └── math.integration.test.ts   // 整合測試
    ├── services/
    │   ├── orderService.ts
    │   ├── orderService.unit.test.ts
    │   └── orderService.integration.test.ts
    

#### 測試金字塔原則

維持「70% 單元測試 + 20% 整合測試 + 10% E2E 測試」的比例,確保: [codefresh](https://codefresh.io/learn/unit-testing/unit-testing-vs-integration-testing-5-key-differences-and-why-you-need-both/)

*   大部分問題在單元測試階段(1-2 分鐘)被攔截
    
*   整合測試驗證關鍵互動(5-10 分鐘)
    
*   E2E 測試保障使用者情境(15-30 分鐘)
    

#### 監控 CI 管道效能

定期檢視測試執行時間趨勢: [octopus](https://octopus.com/devops/ci-cd/ci-cd-pipeline/)

*   單元測試平均時間 < 5 分鐘
    
*   Pull Request 完整測試 < 15 分鐘
    
*   主分支全量測試 < 30 分鐘
    

**當測試時間持續增長時**:

1.  檢查是否有測試分類錯誤(整合測試誤放在單元測試)
    
2.  啟用並行執行與分片功能
    
3.  移除冗餘或重複的測試案例
    

#### 測試覆蓋率分層追蹤

針對不同測試類型設定合理覆蓋率目標:

    // vitest.config.js
    export default defineConfig({
      test: {
        projects: [
          {
            name: 'unit',
            coverage: { lines: 90 },  // 單元測試應覆蓋大部分邏輯
          },
          {
            name: 'integration',
            coverage: { lines: 60 },  // 整合測試聚焦關鍵路徑
          },
        ],
      },
    })
    

* * *

### 五、核心結論

測試分類是現代軟體工程的必備實踐,讓 CI/CD 流程從「全有全無」變成「漸進式驗證」: [getscandium](https://getscandium.com/software-development-the-transformative-power-of-test-automation-in-ci-cd-integration/)

1.  **開發體驗**:TDD 紅綠重構保持數秒鐘快速循環
    
2.  **品質保證**:多層閘門確保不同層級的問題都能被攔截
    
3.  **成本效益**:避免浪費運算資源,快速失敗節省時間與金錢
    
4.  **維護性**:穩定的單元測試 + 可控的整合測試,降低長期維護成本
    

Vitest 提供比 Jest 更現代化的測試分類功能(`projects`、`tags`、`sharding`),讓測試組織更靈活彈性,是前端工程師值得深入學習的工具。

* * *

**參考資料**

*   [Vitest 官方文件 - Test Filtering](https://vitest.dev/guide/filtering)
    
*   [Vitest 官方文件 - Test Projects](https://vitest.dev/guide/projects)
    
*   [CI/CD Testing Best Practices](https://codefresh.io/learn/ci-cd-pipelines/)
    

CSS 留白與定位屬性
===========

**學習目標**:理解傳統 CSS(不用 Flex/Grid)如何用留白 + 定位打造精準佈局

* * *

一、對話中提到的所有 CSS 屬性清單
-------------------

### 1.1 盒模型留白屬性

屬性

作用範圍

特性

`margin`

元素與元素之間

不會有背景色,可以用負值,會發生合併(collapse)

`padding`

元素內容到邊框

會保留背景色,撐大可點擊範圍,影響總寬度

`border`

元素邊框

佔據空間,可設定顏色/樣式/粗細

`gap`

Flex/Grid 子元素間距

只作用於現代佈局,不會在容器邊緣產生空隙

### 1.2 文字留白屬性

屬性

作用對象

使用情境

`line-height`

文字行高

段落可讀性,單行置中,用純數字最佳(如 1.6)

`letter-spacing`

字元間距

標題視覺效果,中英文混排,Logo 設計

`word-spacing`

單詞間距

英文排版(對中文無效)

`text-indent`

段落首行縮排

傳統文章排版

`white-space`

空白字符處理

控制換行與空白顯示

### 1.3 盒模型計算屬性

屬性

計算邏輯

推薦值

`box-sizing: content-box`

width 只算 content,padding/border 會額外增加

預設值(不推薦)

`box-sizing: border-box`

width = content + padding + border,往內推擠

全站設定(推薦)

### 1.4 定位屬性

屬性

定位基準

是否脫離文件流

`position: static`

預設,按文件流排列

否

`position: relative`

自己的原始位置

否(保留原空間)

`position: absolute`

最近的已定位父元素

是(不佔空間)

`position: fixed`

瀏覽器視窗

是(不佔空間)

### 1.5 定位座標屬性

屬性

作用

搭配對象

`top` / `bottom` / `left` / `right`

指定元素距離定位基準的距離

`position: relative/absolute/fixed`

`transform: translate()`

相對自身尺寸位移

常與 `position: absolute` 搭配置中

### 1.6 其他相關屬性

屬性

作用

使用情境

`display: table-cell`

假裝元素是表格儲存格

搭配 `vertical-align: middle` 垂直置中

`vertical-align`

行內元素垂直對齊

只對 inline/inline-block/table-cell 有效

* * *

二、定位屬性的核心概念
-----------

### 2.1 position: relative(相對定位) [steam.oxxostudio](https://steam.oxxostudio.tw/category/css/content/position.html)

**比喻**:像在地上畫記號,告訴物件「從你原本的位置移動」。 [steam.oxxostudio](https://steam.oxxostudio.tw/category/css/content/position.html)

    .box {
      position: relative;
      top: 20px;  /* 從原位置往下移 20px */
      left: 30px; /* 從原位置往右移 30px */
    }
    

**特性**: [web](https://web.dev/learn/css/spacing?hl=zh-tw)

*   **保留原空間**:元素移走後,原位置仍保留空位(其他元素不會遞補)
    
*   **不影響其他元素**:位移只是視覺效果,不會推擠鄰居
    
*   **常用於建立定位參考點**:讓子元素的 `absolute` 以它為基準 [vocus](https://vocus.cc/article/61ac2085fd89780001d15166)
    

### 2.2 position: absolute(絕對定位) [eudora](https://www.eudora.cc/posts/31497)

**比喻**:像飛出文件流的氣球,可以飄到任何地方,不佔地面空間。 [vocus](https://vocus.cc/article/61ac2085fd89780001d15166)

    .parent {
      position: relative; /* 建立參考點 */
    }
    
    .child {
      position: absolute;
      top: 0;    /* 距離父元素上緣 0px */
      right: 0;  /* 距離父元素右緣 0px */
    }
    

**定位基準規則**: [eudora](https://www.eudora.cc/posts/31497)

1.  **有定位的父元素**:找最近的 `position: relative/absolute/fixed` 的父元素
    
2.  **沒有定位的父元素**:以 `<body>` 或初始包含區塊為基準 [vocus](https://vocus.cc/article/61ac2085fd89780001d15166)
    

**特性**: [eudora](https://www.eudora.cc/posts/31497)

*   **脫離文件流**:不佔原本空間,其他元素會遞補上來
    
*   **不推擠其他元素**:怎麼移動都不影響鄰居排列
    
*   **寬高自動收縮**:沒指定寬高時,會縮到內容大小 [eudora](https://www.eudora.cc/posts/31497)
    

* * *

三、留白屬性 × 定位屬性的搭配技巧
------------------

### 3.1 margin 與 top/left 的差異 [code2study.blogspot](https://code2study.blogspot.com/2013/04/topmargin-topleftmargin-left.html?m=1)

當元素使用 `position: relative` 時:

屬性組合

效果

適用情境

`margin-top: 20px`

推擠其他元素,會影響佈局流

需要調整整體佈局

`top: 20px`

只移動自己,不影響其他元素

微調單一元素位置

    /* 範例對比 */
    .method-1 {
      position: relative;
      margin-top: 20px; /* 會把下方所有元素往下推 */
    }
    
    .method-2 {
      position: relative;
      top: 20px; /* 只有自己往下移,保留原空間 */
    }
    

**當元素使用** `position: absolute` **時**: [code2study.blogspot](https://code2study.blogspot.com/2013/04/topmargin-topleftmargin-left.html?m=1)

*   margin 和 top/left **效果相同**,因為元素已脫離文件流
    
*   **推薦用 top/left**:語意更清楚,表示「定位座標」
    

### 3.2 padding 在 absolute 元素中的應用 [zero-plus](https://zero-plus.io/media/css-position-absolute/)

    .parent {
      position: relative;
      padding: 50px; /* 父元素的 padding 會影響子元素的定位起點 */
      border: 20px solid orange;
    }
    
    .child {
      position: absolute;
      top: 0;   /* 起點是父元素 padding 內緣,不是 border 內緣 */
      left: 0;
      width: 100px;
      height: 100px;
    }
    

**關鍵規則**: [zero-plus](https://zero-plus.io/media/css-position-absolute/)

*   **absolute 子元素的定位起點 = 父元素的 padding 內緣**
    
*   父元素的 padding 會「內推」子元素的 (0, 0) 起點
    
*   父元素的 margin 對子元素定位**無影響**
    

### 3.3 margin: auto 置中法(需搭配 absolute) [realnewbie](https://realnewbie.com/posts/css-position-center)

    .outer {
      position: relative;
      height: 300px;
    }
    
    .inner {
      position: absolute;
      top: 0;
      bottom: 0;    /* 上下都設為 0 */
      left: 0;
      right: 0;     /* 左右都設為 0 */
      margin: auto; /* 瀏覽器會自動計算剩餘空間 */
      
      width: 200px;  /* 必須有固定寬高 */
      height: 100px;
    }
    

**運作原理**: [realnewbie](https://realnewbie.com/posts/css-position-center)

1.  同時設定 `top: 0; bottom: 0;` 會產生「垂直方向的拉扯」
    
2.  `margin: auto` 會平均分配上下剩餘空間
    
3.  水平方向同理
    

**限制**:子元素必須有明確的寬高,不適合響應式內容。 [realnewbie](https://realnewbie.com/posts/css-position-center)

### 3.4 transform 位移不佔空間

    .box {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%); /* 百分比相對自身尺寸 */
    }
    

**關鍵差異**: [pjchender.github](https://pjchender.github.io/css/css-center-center/)

*   `top: 50%`:相對**父元素**高度的 50%
    
*   `transform: translateY(-50%)`:相對**自身**高度的 50%
    
*   **transform 不會改變元素佔據的空間**,只是視覺位移
    

* * *

四、實戰案例:留白 + 定位組合拳
-----------------

### 4.1 卡片角標設計

    .card {
      position: relative; /* 建立定位參考點 */
      padding: 20px;      /* 內容留白 */
      border: 1px solid #ddd;
      margin-bottom: 16px; /* 卡片間距 */
    }
    
    .badge {
      position: absolute;
      top: -10px;    /* 從卡片上緣往上偏移(負值讓它跑出去) */
      right: 10px;   /* 從卡片右緣往左 10px */
      padding: 5px 10px; /* 角標內部留白 */
      background: red;
    }
    

**留白作用分析**:

*   **card 的 padding**:保護內文不貼邊
    
*   **card 的 margin-bottom**:卡片之間保持距離
    
*   **badge 的 padding**:讓文字不貼齊角標邊緣
    
*   **badge 的 top(負值)**:讓角標突出卡片上方
    

### 4.2 彈跳視窗置中 + 內容留白

    /* 遮罩層(不用定位,用固定定位覆蓋全螢幕) */
    .overlay {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.5);
    }
    
    /* 彈跳視窗本體 */
    .modal {
      position: fixed;    /* 相對視窗定位 */
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%); /* 完美置中 */
      
      width: 90%;
      max-width: 500px;
      padding: 30px;      /* 內容與邊框留白 */
      background: white;
      box-sizing: border-box; /* 確保 padding 不會讓寬度爆掉 */
    }
    
    .modal-title {
      margin-bottom: 20px; /* 標題與內文間距 */
      line-height: 1.3;    /* 標題行高 */
    }
    
    .modal-content {
      line-height: 1.6;    /* 內文行高 */
    }
    

**技巧拆解**:

1.  **position: fixed + transform**:讓視窗永遠在畫面中央,不受捲動影響 [pjchender.github](https://pjchender.github.io/css/css-center-center/)
    
2.  **padding + box-sizing: border-box**:確保內容留白不會破壞寬度計算
    
3.  **margin-bottom**:用於內部元素之間的間距
    
4.  **line-height**:提升文字可讀性
    

### 4.3 固定導覽列 + 內容偏移

    /* 固定在頂部的導覽列 */
    .navbar {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 60px;
      padding: 0 20px;  /* 左右內邊距 */
      background: white;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    
    /* 主要內容區 */
    .main-content {
      margin-top: 60px; /* 留出導覽列的高度,避免被遮住 */
      padding: 20px;    /* 內容區留白 */
    }
    

**為什麼用 margin-top 而非 padding-top?**

*   navbar 使用 `position: fixed` 會脫離文件流
    
*   主內容區會跑到畫面頂部被遮住
    
*   **用 margin-top 推擠主內容區**,讓它從導覽列下方開始顯示 [web](https://web.dev/learn/css/spacing?hl=zh-tw)
    

* * *

五、易錯提醒與除錯技巧
-----------

### 5.1 定位基準找錯

    /* ❌ 錯誤:子元素會以 body 為基準 */
    .parent {
      /* 沒有設定 position */
    }
    .child {
      position: absolute;
      top: 20px; /* 會從 body 頂部開始算 */
    }
    
    /* ✅ 正確:子元素以父元素為基準 */
    .parent {
      position: relative; /* 建立參考點 */
    }
    .child {
      position: absolute;
      top: 20px; /* 從父元素頂部開始算 */
    }
    

### 5.2 padding 讓 absolute 子元素位移

    .parent {
      position: relative;
      padding: 30px; /* 注意這個 padding */
    }
    
    .child {
      position: absolute;
      top: 0;
      left: 0;
      /* 實際位置會距離父元素 border 內緣 30px(受 padding 影響) */
    }
    
    /* 如果想讓子元素貼齊 border 內緣,用負值抵消 */
    .child {
      position: absolute;
      top: -30px;   /* 往上抵消 padding */
      left: -30px;  /* 往左抵消 padding */
    }
    

### 5.3 box-sizing 影響定位計算

    .box {
      box-sizing: border-box;
      width: 200px;
      padding: 20px;
      border: 10px solid black;
      /* 實際可定位區域 = 200px(包含 padding 和 border) */
    }
    
    .box-child {
      position: absolute;
      width: 100%; /* 這裡的 100% = 200px,不是 content 寬度 */
    }

---

*Originally published on [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/day-54)*
