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; }
}
Dropdown選單
<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><</button>
<span>2026年6月</span>
<button>></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);
}
}
總結
- CSS Anchor Positioning是彈出層定位的終極方案 — 零JS、宣告式、瀏覽器原生
- Popover API讓彈出層開關也零JS — popovertarget屬性一行搞定
- position-fallback解決邊界溢出 — 不再需要JS計算翻轉
- 2026年主流瀏覽器已全部支援 — 可以放心在生產環境使用
10年了,彈出層定位終於從JS回到了CSS該待的地方。這不是漸進式改進,而是範式轉移——就像Flexbox取代浮動佈局一樣。
本站提供浏览器本地工具,免注册即可试用 →
#CSS锚点定位#Anchor Positioning#Popover API#弹出层#CSS