LESSON-11 · 自主延伸

定位、堆疊與錨點

把「定位、堆疊與錨點」拆成 4 個可獨立完成、可重設、可驗證的自主學習 module。

1. 現在要做什麼

成果:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

需要的檔案:files/starter/index.html(整合起點,保留跨 module 的最小缺口。)

完成後:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

0 / 8 個操作已記錄

2. 先備自我檢查

逐項展開;答不出來時先走補救連結,不必硬做。

已完成本章基礎練習並能找到 Starter。

自我檢查:我能否指出本章 Starter 入口與唯一 TODO?

不足時補救:回到本章 README,先完成基礎練習與入口導覽。

能開啟本機 HTML 並讀取至少一種瀏覽器證據。

自我檢查:我能否開啟 Elements、Console 或 Network 並找到本頁?

不足時補救:先開 DevTools,重新整理一次,確認目前文件與第一筆 request。

3. 完整學習目標與 Learning Map

每一個目標只對應一個 module;成功狀態必須能指出證據。

能找出 sticky 的實際 scroll container,並判斷祖先 overflow 如何改變黏附範圍。

為什麼重要
sticky 失效常不是 z-index 太小,而是沒有捲動空間、缺少 inset,或祖先建立了另一個 scrolling mechanism。
完成條件
fixture 內有可捲動祖先與長內容,將目標由 static 切為 sticky,實際捲動祖先後量 rect。
成功狀態
computed position 由 static 變 sticky;scrollTop 增加後 sticky top 留在 container top 附近且不超出祖先範圍。
證據位置
現有 runtime before=static、after=sticky;required facts 包含 scrollTop、targetTop、containerTop

能辨認 position/z-index、transform、opacity 等建立 stacking context 的條件,並以 hit-test 判斷真正前景元素。

為什麼重要
子元素的巨大 z-index 不能跳出祖先 stacking context;只把數字加大常無法解決被遮住的根因。
完成條件
兩張卡實際重疊,將前卡 opacity 由1改為0.99建立 stacking context,並以 elementFromPoint 記錄前景 id。
成功狀態
runtime opacity 由1變0.99;required probe 回傳 frontId 與 before/after stacking 狀態。
證據位置
現有 runtime before=1、after=0.99;required facts 包含 frontId 與 before/after

能用 inset-inline-end/inset-block-start 讓角落 badge 隨 LTR/RTL 書寫方向翻轉。

為什麼重要
固定 right/top 把版面綁在左到右假設;logical properties 能讓同一元件支援不同語言方向。
完成條件
badge 初始 inset-inline-end:0,執行後切為16px,再由 probe 比較 LTR/RTL offset。
成功狀態
runtime inset-inline-end 由 0px 變16px;required facts 顯示 LTR 靠右、RTL 靠左。
證據位置
現有 runtime before=0px、after=16px;required facts 包含 ltrOffset、rtlOffset

能用 scroll-margin-top 為錨點保留 sticky header 高度,避免目標標題被遮住。

為什麼重要
頁內連結成功改變 hash 不代表內容可見;固定頁首遮住目標會讓鍵盤與讀者失去位置線索。
完成條件
目標初始 scroll-margin-top:0,執行後改72px,再觸發 hash/scrollIntoView 量 target top。
成功狀態
runtime 由0px變72px;required facts 顯示 targetTop 大於等於 headerHeight。
證據位置
現有 runtime before=0px、after=72px;required facts 包含 targetTop、headerHeight、scrollTop

4. 4 個知識模組與 Micro Labs

依序展開;每次只改一項,操作後確認該模組自己的 DOM、progress 與 evidence。

1sticky 與祖先 overflow

完整解釋

白話:sticky 會在自己的捲動容器裡黏住,不一定黏在整個瀏覽器視窗。

正式說法:A sticky positioned box is offset relative to its nearest ancestor with a scrolling mechanism and its containing block;an inset such as top is required to establish the sticky threshold。

何時使用:章節導覽、表格標題或頁內工具列需要在局部捲動區停留時。

修改前後:Before:sticky 不動就一直加 z-index。After:先找 overflow ancestor、確認可捲動高度與 top threshold。

常見混淆:position:fixed 相對 viewport,sticky 仍保留 normal-flow 空間並受 containing block 邊界限制。

名詞與最小範例

sticky 與祖先 overflow Sticky positioning and ancestor overflow

白話
sticky 會在自己的捲動容器裡黏住,不一定黏在整個瀏覽器視窗。
正式定義
A sticky positioned box is offset relative to its nearest ancestor with a scrolling mechanism and its containing block;an inset such as top is required to establish the sticky threshold。
何時使用
章節導覽、表格標題或頁內工具列需要在局部捲動區停留時。
最小範例
.scroll-panel{overflow:auto;max-height:12rem}.panel-title{position:sticky;top:0}
驗收證據
現有 runtime before=static、after=sticky;required facts 包含 scrollTop、targetTop、containerTop
常混淆
position:fixed 相對 viewport,sticky 仍保留 normal-flow 空間並受 containing block 邊界限制。

Micro Lab|sticky 與祖先 overflow

情境:側欄標題設 sticky 卻沒有留在預期位置,頁面同時有一個 overflow:auto 面板。

起始狀態:面板可局部捲動;標題 position:static,尚未套 top:0 sticky。

  1. 執行實驗切換 sticky-active class,讀取 position;完整 probe 再捲動 data-sticky-root 並量 targetTop/containerTop。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:sticky 與祖先 overflow

服務分類

長內容
尚未執行;DOM data-state=starter。

預期:computed position 由 static 變 sticky;scrollTop 增加後 sticky top 留在 container top 附近且不超出祖先範圍。

證據:現有 runtime before=static、after=sticky;required facts 包含 scrollTop、targetTop、containerTop

第一個檢查:由 sticky 元素往上找第一個 overflow 非 visible 的祖先。

重設:重新載入 module,scrollTop 回0並移除 sticky-active。

卡住時

  • 症狀:computed position 是 sticky,但畫面完全不動。
    可能原因:缺少 top/inset、內容不足以捲動,或測試捲錯容器。
    先檢查:讀 top、scrollHeight/clientHeight 與實際 scrollTop。
    修正:補 top:0、增加本機長內容,並捲動最近 overflow ancestor。

理解檢查

sticky 首先要找哪個參考範圍?

尚未作答。

查看解釋

sticky threshold 與移動範圍受最近 scrolling ancestor/containing block 影響。

本模組官方來源:MDN|position

2stacking context 成因

完整解釋

白話:stacking context 像各自封閉的樓層;子元素只能在自己的樓層內排序,不能拿超大號碼跳到別棟樓上。

正式說法:A stacking context is an atomic stacking unit created by defined triggers including positioned z-index, transform, or opacity below 1;descendant z-order is resolved within that context before the context participates in its parent。

何時使用:下拉選單、modal、badge 或重疊卡片的 z-index 看似無效時。

修改前後:Before:直接把 z-index 加到999999。After:先列出每個祖先的 context trigger,再用 hit-test 驗前景。

常見混淆:z-index 值只在同一 stacking context 比較;數值大不代表跨 context 全域優先。

名詞與最小範例

stacking context 成因 Stacking context creation

白話
stacking context 像各自封閉的樓層;子元素只能在自己的樓層內排序,不能拿超大號碼跳到別棟樓上。
正式定義
A stacking context is an atomic stacking unit created by defined triggers including positioned z-index, transform, or opacity below 1;descendant z-order is resolved within that context before the context participates in its parent。
何時使用
下拉選單、modal、badge 或重疊卡片的 z-index 看似無效時。
最小範例
.card-front{position:relative;z-index:2;opacity:.99}.card-back{transform:translateZ(0)}
驗收證據
現有 runtime before=1、after=0.99;required facts 包含 frontId 與 before/after
常混淆
z-index 值只在同一 stacking context 比較;數值大不代表跨 context 全域優先。

Micro Lab|stacking context 成因

情境:推薦 badge 設 z-index:9999 仍被鄰卡遮住,因其祖先 opacity 建立另一個 context。

起始狀態:兩卡使用 absolute offset 重疊;目標卡 opacity:1。

  1. 執行實驗切換 create-stacking class,讀取 opacity;完整 probe 在重疊中心執行 elementFromPoint。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:stacking context 成因

前卡
後卡
尚未執行;DOM data-state=starter。

預期:runtime opacity 由1變0.99;required probe 回傳 frontId 與 before/after stacking 狀態。

證據:現有 runtime before=1、after=0.99;required facts 包含 frontId 與 before/after

第一個檢查:由被遮元素往上檢查 position/z-index、transform、opacity,而非只看自身 z-index。

重設:重新載入 module,移除 create-stacking class。

卡住時

  • 症狀:elementFromPoint 回傳外層容器而非任一卡片。
    可能原因:取樣座標不在重疊區,或 overlay/pointer-events 介入。
    先檢查:記錄兩卡 rect 交集與取樣 x/y。
    修正:以交集中心 hit-test,移除無關 overlay。

理解檢查

為何子元素 z-index:9999 仍可能在另一張卡後面?

尚未作答。

查看解釋

descendant 先在自己的 atomic context 排序,不能直接與外部子元素比較。

本模組官方來源:MDN|Stacking context

3logical inset

完整解釋

白話:inline-end 是一句文字讀到最後的那一側;LTR 在右,RTL 會自動變左。

正式說法:Logical inset properties map block/inline start/end offsets to physical sides according to writing-mode and direction。

何時使用:多語系卡片徽章、關閉按鈕或浮動控制需要跟閱讀方向調整時。

修改前後:Before:每種語言各寫 left/right override。After:同一組 logical inset 隨 dir 自動映射。

常見混淆:inline-end 不永遠等於 right;RTL 或垂直 writing mode 的物理方向會不同。

名詞與最小範例

logical inset Logical inset properties

白話
inline-end 是一句文字讀到最後的那一側;LTR 在右,RTL 會自動變左。
正式定義
Logical inset properties map block/inline start/end offsets to physical sides according to writing-mode and direction。
何時使用
多語系卡片徽章、關閉按鈕或浮動控制需要跟閱讀方向調整時。
最小範例
.badge{position:absolute;inset-block-start:1rem;inset-inline-end:1rem}
驗收證據
現有 runtime before=0px、after=16px;required facts 包含 ltrOffset、rtlOffset
常混淆
inline-end 不永遠等於 right;RTL 或垂直 writing mode 的物理方向會不同。

Micro Lab|logical inset

情境:阿拉伯文頁面的推薦 badge 仍固定在右上角,沒有跟著閱讀方向移到左上角。

起始狀態:卡片 position:relative;badge absolute,inline-end 從 0px 基準開始。

  1. 執行實驗切換 logical-corner class,讀取 inset-inline-end;完整 probe 再切 dir=ltr/rtl 量 offset。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:logical inset

推薦
尚未執行;DOM data-state=starter。

預期:runtime inset-inline-end 由 0px 變16px;required facts 顯示 LTR 靠右、RTL 靠左。

證據:現有 runtime before=0px、after=16px;required facts 包含 ltrOffset、rtlOffset

第一個檢查:先讀 document/card 的 dir 與 writing-mode,再看 logical inset computed value。

重設:重新載入 module,dir 回ltr並移除 logical-corner。

卡住時

  • 症狀:切 dir=rtl 後 badge 仍在右側。
    可能原因:仍有 right 宣告覆蓋 logical inset,或 dir 沒套到 containing block。
    先檢查:查看 badge 的 left/right/inset-inline-* computed values 與祖先 dir。
    修正:移除物理 right,讓 containing block 的 dir 控制 logical mapping。

理解檢查

RTL 情境下 inset-inline-end 通常映射哪一側?

尚未作答。

查看解釋

RTL 的 inline progression 由右到左,inline-end 在左。

本模組官方來源:MDN|Inset properties

4scroll-margin-top

完整解釋

白話:錨點捲到上方時,先替它預留頁首的高度,不要讓標題躲在頁首後面。

正式說法:scroll-margin-top defines the target scroll snap area outset on the block-start side and affects scroll-into-view positioning。

何時使用:sticky/fixed header 搭配章節目錄、skip link 或 hash navigation 時。

修改前後:Before:hash 正確但標題被 header 蓋住。After:目標停在 header 下方且 focus/內容可見。

常見混淆:scroll-padding-top 通常設在 scroll container;scroll-margin-top 設在個別 target。

名詞與最小範例

scroll-margin-top Scroll margin before an anchor target

白話
錨點捲到上方時,先替它預留頁首的高度,不要讓標題躲在頁首後面。
正式定義
scroll-margin-top defines the target scroll snap area outset on the block-start side and affects scroll-into-view positioning。
何時使用
sticky/fixed header 搭配章節目錄、skip link 或 hash navigation 時。
最小範例
.section-heading{scroll-margin-top:4.5rem}
驗收證據
現有 runtime before=0px、after=72px;required facts 包含 targetTop、headerHeight、scrollTop
常混淆
scroll-padding-top 通常設在 scroll container;scroll-margin-top 設在個別 target。

Micro Lab|scroll-margin-top

情境:點擊『服務方案』目錄後網址出現 #plans,但 h2 被72px sticky header 完全遮住。

起始狀態:目標 id 存在,scroll-margin-top:0,header 高72px。

  1. 執行實驗切換 anchor-offset class,讀取 scroll-margin-top;完整 probe 捲到目標並比較 headerHeight/targetTop。
  2. 執行下方操作,觀察獨立的 DOM 與資料狀態。
  3. 依證據欄位重新驗收。

目前主題:scroll-margin-top

頁內導覽

服務方案

尚未執行;DOM data-state=starter。

預期:runtime 由0px變72px;required facts 顯示 targetTop 大於等於 headerHeight。

證據:現有 runtime before=0px、after=72px;required facts 包含 targetTop、headerHeight、scrollTop

第一個檢查:先確認 href hash 與 target id 一致,再量 header height。

重設:重新載入 module,scrollTop 回0並移除 anchor-offset。

卡住時

  • 症狀:加入72px後目標仍被遮住。
    可能原因:實際 header 更高、捲動發生在另一個 container,或規則加到錯誤元素。
    先檢查:量 header rect.height、target computed scroll-margin-top 與實際 scroll container。
    修正:把正確 offset 套到真正 hash target,並依真實 header 高度調整。

理解檢查

scroll-margin-top 應通常設在哪裡?

尚未作答。

查看解釋

它調整該 target 的 scroll-into-view 外距。

本模組官方來源:MDN|scroll-margin-top

5. 定位、堆疊與錨點|整合 Lab

情境:在同一份章節作品中整合 4 個 module,排除單項通過但組合失敗的問題。

交付:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

尚未執行整合步驟。
  1. 1觀察整合 Starter 並找出第一個未通過 module。

    檔案:files/starter/index.html 定位:#observe

    操作:逐項讀取 outcomes,執行一次現況操作。

    // OBSERVE:不修改程式,先記錄第一個失敗 moduleId。

    預期:能指出第一個未通過 module。

    證據:第一個 moduleId 與缺少的 evidence。

    原因:先觀察可避免未重現就直接改答案。

    卡住先查:確認開啟的是 files/starter/index.html。

  2. 2依 module 順序修改,一次只加入一個責任。

    檔案:files/starter/index.html 定位:#modify

    操作:執行實驗切換 sticky-active class,讀取 position;完整 probe 再捲動 data-sticky-root 並量 targetTop/containerTop。 → 執行實驗切換 create-stacking class,讀取 opacity;完整 probe 在重疊中心執行 elementFromPoint。 → 執行實驗切換 logical-corner class,讀取 inset-inline-end;完整 probe 再切 dir=ltr/rtl 量 offset。 → 執行實驗切換 anchor-offset class,讀取 scroll-margin-top;完整 probe 捲到目標並比較 headerHeight/targetTop。

    // 11-01-sticky-overflow: 執行實驗切換 sticky-active class,讀取 position;完整 probe 再捲動 data-sticky-root 並量 targetTop/containerTop。
    // 11-02-stacking-context: 執行實驗切換 create-stacking class,讀取 opacity;完整 probe 在重疊中心執行 elementFromPoint。
    // 11-03-logical-inset: 執行實驗切換 logical-corner class,讀取 inset-inline-end;完整 probe 再切 dir=ltr/rtl 量 offset。
    // 11-04-scroll-margin-top: 執行實驗切換 anchor-offset class,讀取 scroll-margin-top;完整 probe 捲到目標並比較 headerHeight/targetTop。

    預期:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

    證據:現有 runtime before=static、after=sticky;required facts 包含 scrollTop、targetTop、containerTop;現有 runtime before=1、after=0.99;required facts 包含 frontId 與 before/after;現有 runtime before=0px、after=16px;required facts 包含 ltrOffset、rtlOffset;現有 runtime before=0px、after=72px;required facts 包含 targetTop、headerHeight、scrollTop

    原因:逐項整合能把回歸定位到明確 module。

    卡住先查:回到第一個未通過的 module,只修該處。

  3. 3驗證所有 module 在整合後仍成立。

    檔案:files/solution/index.html 定位:#verify

    操作:重新整理並依 outcomes 順序重跑所有證據。

    // VERIFY:逐項記錄 pass/fail,不以看到畫面代替 evidence。

    預期:4/4 module 通過。

    證據:每個 moduleId 都有實際結果與重設後重現紀錄。

    原因:單項通過不代表整合後沒有 selector、id 或狀態衝突。

    卡住先查:若失敗,回到第一個失敗 module,不同時修多項。

  4. 4把本章責任轉用到不同內容情境。

    檔案:files/starter/index.html 定位:#challenge

    操作:替另一個商家情境重做,仍達成:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

    // CHALLENGE:只替換內容與資料,不新增框架或跳過 self-check。

    預期:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

    證據:轉用後的作品與全數 module self-check。

    原因:能轉用才代表理解責任,而非只記住原範例。

    卡住先查:先確認共同基底與 module id 未被改壞。

重設方法:重新載入 files/starter/index.html;若 localStorage 不可用,使用頁面內記憶狀態並提供重設按鈕。

6. 自學挑戰與三層提示

挑戰:將同一組責任轉用到另一個商家情境,仍達成:完成可解釋 sticky、堆疊、RTL badge 與錨點停靠的頁面。

限制:不新增框架。;保留 module id 與驗收證據。

提交證據:提交可直接開啟的作品與逐項 self-check。

提示 1|方向提示

拆小:在「定位、堆疊與錨點」先完成第一個尚未通過的 module:sticky 與祖先 overflow,不同時修改其他責任。

提示 2|關鍵片段

串接:依 11-01-sticky-overflow → 11-02-stacking-context → 11-03-logical-inset → 11-04-scroll-margin-top 核對各自輸入與證據。

提示 3|完整解答與原因

完整解答與原因:依序執行 執行實驗切換 sticky-active class,讀取 position;完整 probe 再捲動 data-sticky-root 並量 targetTop/containerTop。 → 執行實驗切換 create-stacking class,讀取 opacity;完整 probe 在重疊中心執行 elementFromPoint。 → 執行實驗切換 logical-corner class,讀取 inset-inline-end;完整 probe 再切 dir=ltr/rtl 量 offset。 → 執行實驗切換 anchor-offset class,讀取 scroll-margin-top;完整 probe 捲到目標並比較 headerHeight/targetTop。;這個順序能把每個結果對回單一 module,避免整合後無法定位。

完整解答預設收合;先留下自己的嘗試,再開第三層。

7. 症狀式除錯指南

8. 理解測驗、驗收證據與下一步

哪個狀態代表 11-01-sticky-overflow 完成?

尚未作答。

查看解釋

sticky 失效常不是 z-index 太小,而是沒有捲動空間、缺少 inset,或祖先建立了另一個 scrolling mechanism。

哪個狀態代表 11-02-stacking-context 完成?

尚未作答。

查看解釋

子元素的巨大 z-index 不能跳出祖先 stacking context;只把數字加大常無法解決被遮住的根因。

哪個狀態代表 11-03-logical-inset 完成?

尚未作答。

查看解釋

固定 right/top 把版面綁在左到右假設;logical properties 能讓同一元件支援不同語言方向。

哪個狀態代表 11-04-scroll-margin-top 完成?

尚未作答。

查看解釋

頁內連結成功改變 hash 不代表內容可見;固定頁首遮住目標會讓鍵盤與讀者失去位置線索。

驗收清單

官方來源

回到課程地圖選擇下一章