CSS錨點定位與Popover API:2026年終於不用JS算彈出層位置了

前端工程

CSS Anchor Positioning:前端開發的"解放宣言"

每個前端工程師都經歷過這個痛苦:用JS計算Tooltip位置,處理邊界溢出,監聽滾動和resize事件……現在,CSS原生就能搞定這一切。

歷史回顧:2016年我們用Popper.js,2020年用Floating UI,2026年終於可以用CSS原生錨點定位。10年了,彈出層定位終於不再是JS的活。

從JS定位到CSS定位的進化

2016    jQuery offset() + 手動計算
        每次滾動/resize都要重新計算

2020    Floating UI / Popper.js
        自動翻轉、偏移、邊界檢測
        但仍需JS初始化和銷燬

2024    CSS Anchor Positioning(Chrome 125+)
        純CSS宣告式定位
        瀏覽器原生處理邊界和滾動

2026    Anchor Positioning + Popover API
        純CSS彈出層:零JS開關、零JS定位
        完整的宣告式彈出層方案

anchor-name與position-anchor

基礎用法

<div class="card">
  <button class="trigger">更多操作</button>
  <div class="dropdown">下拉選單內容</div>
</div>
.trigger {
  anchor-name: --my-anchor;
}

.dropdown {
  position: fixed;
  position-anchor: --my-anchor;
  top: anchor(bottom);
  left: anchor(left);
}

anchor()函式詳解

.tooltip {
  position: fixed;
  position-anchor: --trigger-anchor;

  /* 相對於錨點的各個邊緣 */
  top: anchor(bottom);       /* 錨點底部 */
  bottom: anchor(top);       /* 錨點頂部 */
  left: anchor(left);        /* 錨點左邊 */
  right: anchor(right);      /* 錨點右邊 */

  /* 錨點中心點 */
  left: anchor(50%);         /* 錨點水平中心 */
  top: anchor(50%);          /* 錨點垂直中心 */

  /* 帶偏移 */
  top: calc(anchor(bottom) + 8px);
  left: calc(anchor(left) - 4px);
}

position-area九宮格定位

九宮格語法

┌─────────────┬─────────────┬─────────────┐
│  top left   │   top center│  top right  │
│             │             │             │
├─────────────┼─────────────┼─────────────┤
│ center left │   center    │ center right│
│             │             │             │
├─────────────┼─────────────┼─────────────┤
│ bottom left │bottom center│ bottom right│
│             │             │             │
└─────────────┴─────────────┴─────────────┘
/* Tooltip在錨點下方居中 */
.tooltip-below {
  position-area: bottom center;
}

/* Tooltip在錨點右側居中 */
.tooltip-right {
  position-area: center right;
}

/* Dropdown在錨點下方左對齊 */
.dropdown-below-left {
  position-area: bottom left;
}

實戰:全方位Tooltip

.trigger { anchor-name: --tooltip-anchor; }

.tooltip {
  position: fixed;
  position-anchor: --tooltip-anchor;
  inset-area: bottom center;
  margin: 8px;

  /* 基礎樣式 */
  padding: 8px 12px;
  background: #1f2937;
  color: white;
  border-radius: 6px;
  font-size: 14px;
  white-space: nowrap;
  pointer-events: none;
}

position-fallback與多級回退策略

邊界溢出自動翻轉

.dropdown {
  position: fixed;
  position-anchor: --trigger;

  /* 首選:錨點下方 */
  top: anchor(bottom);
  left: anchor(left);

  /* 回退策略 */
  position-fallback: --dropdown-fallback;
}

@position-fallback --dropdown-fallback {
  /* 回退1:錨點上方 */
  @try {
    bottom: anchor(top);
    left: anchor(left);
  }

  /* 回退2:錨點下方右對齊 */
  @try {
    top: anchor(bottom);
    right: anchor(right);
  }

  /* 回退3:錨點上方右對齊 */
  @try {
    bottom: anchor(top);
    right: anchor(right);
  }
}

Popover API:原生彈出層

基礎用法

<button popovertarget="my-popover">開啟彈出層</button>

<div id="my-popover" popover>
  <p>這是原生Popover內容</p>
  <button popovertarget="my-popover" popovertargetaction="hide">關閉</button>
</div>
[popover] {
  position: fixed;
  inset: unset;
  margin: 0;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: white;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}

Popover + Anchor組合

<button class="menu-trigger" popovertarget="menu-popover">選單</button>

<div id="menu-popover" popover class="menu-dropdown">
  <a href="/profile">個人資料</a>
  <a href="/settings">設定</a>
  <a href="/logout">登出</a>
</div>
.menu-trigger {
  anchor-name: --menu-anchor;
}

.menu-dropdown {
  position-anchor: --menu-anchor;
  position-area: bottom left;
  margin: 4px;
}

anchor-size():根據錨點尺寸自適應

.trigger {
  anchor-name: --select-anchor;
}

.select-dropdown {
  position: fixed;
  position-anchor: --select-anchor;
  top: anchor(bottom);
  left: anchor(left);

  /* 下拉寬度跟隨觸發按鈕 */
  width: anchor-size(width);

  /* 或設定最小寬度 */
  min-width: anchor-size(width);
}

實戰:純CSS實現常見UI元件

Tooltip

<span class="tooltip-trigger" tabindex="0">
  幫助
  <span class="tooltip-content" role="tooltip">這是幫助提示資訊</span>
</span>
.tooltip-trigger {
  anchor-name: --tt-anchor;
  position: relative;
  cursor: help;
  text-decoration: underline dotted;
}

.tooltip-content {
  position: fixed;
  position-anchor: --tt-anchor;
  position-area: top center;
  margin: 8px;
  padding: 6px 10px;
  background: #1f2937;
  color: white;
  border-radius: 4px;
  font-size: 13px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;

  position-fallback: --tt-fallback;
}

.tooltip-trigger:hover .tooltip-content,
.tooltip-trigger:focus .tooltip-content {
  opacity: 1;
}

@position-fallback --tt-fallback {
  @try { position-area: bottom center; }
  @try { position-area: center right; }
  @try { position-area: center left; }
}
<button class="dropdown-trigger" popovertarget="dd-menu">
  選項 ▾
</button>
<div id="dd-menu" popover class="dropdown-menu">
  <button class="dropdown-item">編輯</button>
  <button class="dropdown-item">複製</button>
  <button class="dropdown-item">刪除</button>
</div>
.dropdown-trigger {
  anchor-name: --dd-anchor;
}

.dropdown-menu {
  position-anchor: --dd-anchor;
  position-area: bottom left;
  margin: 4px;
  width: anchor-size(width);
  min-width: 160px;
  padding: 4px;
  border-radius: 8px;
  background: white;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);

  position-fallback: --dd-fallback;
}

.dropdown-item {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: none;
  background: none;
  text-align: left;
  border-radius: 4px;
  cursor: pointer;
}

.dropdown-item:hover {
  background: #f3f4f6;
}

@position-fallback --dd-fallback {
  @try { position-area: top left; }
}

Date Picker

<div class="date-picker">
  <input type="text" class="date-input" popovertarget="calendar-popover" readonly placeholder="選擇日期">
  <div id="calendar-popover" popover class="calendar">
    <div class="calendar-header">
      <button>&lt;</button>
      <span>2026年6月</span>
      <button>&gt;</button>
    </div>
    <div class="calendar-grid">
      <!-- 日期格子 -->
    </div>
  </div>
</div>
.date-input {
  anchor-name: --date-anchor;
}

.calendar {
  position-anchor: --date-anchor;
  position-area: bottom left;
  margin: 4px;
  padding: 16px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
  width: 320px;
}

與Floating UI/Popper.js對比

維度 CSS Anchor + Popover Floating UI
JS依賴 零JS 12KB gzip
效能 瀏覽器原生 JS計算
宣告式 CSS宣告 JS命令式
邊界翻轉 position-fallback flip() middleware
偏移計算 margin/anchor() offset() middleware
箭頭 CSS偽元素 SVG/HTML元素
虛擬元素 不支援 支援
動態內容 自動跟隨 需update()
瀏覽器支援 Chrome 125+ 全瀏覽器
SSR友好 完全友好 需客戶端JS

何時仍需要JS庫

仍需Floating UI的場景:
1. 需要支援舊瀏覽器(Safari < 17.5, Firefox < 131)
2. 需要虛擬元素定位(游標位置跟隨)
3. 需要複雜的middleware鏈(autoUpdate, size, hide)
4. React/Vue的宣告式包裝需求

可以用CSS Anchor的場景:
1. Tooltip、Dropdown、Popover等常規彈出層
2. 只需支援現代瀏覽器
3. 追求零JS依賴
4. 效能敏感場景

瀏覽器相容性與Polyfill

支援情況

瀏覽器 Anchor Positioning Popover API
Chrome 125+
Edge 125+
Safari 17.5+
Firefox 131+
iOS Safari 17.5+

Polyfill方案

<!-- 使用CSS Anchor Positioning Polyfill -->
<script src="https://cdn.jsdelivr.net/npm/@oddbird/css-anchor-positioning@0.3.0/dist/css-anchor-positioning.min.js"></script>

<!-- 使用Popover API Polyfill -->
<script src="https://cdn.jsdelivr.net/npm/@oddbird/popover-polyfill@0.4.0/dist/popover.min.js"></script>

與@starting-style配合實現彈出動畫

[popover] {
  opacity: 0;
  transform: translateY(-8px) scale(0.95);
  transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete;
  overlay: none;
}

[popover]:popover-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  overlay: auto;
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: translateY(-8px) scale(0.95);
  }
}

總結

  1. CSS Anchor Positioning是彈出層定位的終極方案 — 零JS、宣告式、瀏覽器原生
  2. Popover API讓彈出層開關也零JS — popovertarget屬性一行搞定
  3. position-fallback解決邊界溢出 — 不再需要JS計算翻轉
  4. 2026年主流瀏覽器已全部支援 — 可以放心在生產環境使用

10年了,彈出層定位終於從JS回到了CSS該待的地方。這不是漸進式改進,而是範式轉移——就像Flexbox取代浮動佈局一樣。

本站提供浏览器本地工具,免注册即可试用 →

#CSS锚点定位#Anchor Positioning#Popover API#弹出层#CSS