Skip to content
本站總訪問量
本站訪客數 人次

彈跳視窗 (Modal)

📝 TL;DR

  • Modal = 疊在頁面上的焦點對話框,務必處理「開啟/關閉、焦點鎖定、背景遮罩、Esc/點擊背景關閉」。
  • 別用 display: none 藏焦點:加上 aria-modal="true"role="dialog",並在開啟時把焦點移到對話框。
  • 進階:多層 Modal、可捲內容、阻止背景捲動、動態插入內容。

什麼時候使用 Modal?

  • 確認類行為:刪除、離開前提示
  • 集中注意:表單、登入、快速檢視商品
  • 非阻塞資訊不該用 Modal:提示可用 Toast/Inline

最小可用版本 (原生 JS)

html
<button id="open">開啟視窗</button>

<div id="modal" class="backdrop" hidden>
	<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="title">
		<h2 id="title">確認刪除?</h2>
		<p>刪除後無法復原。</p>
		<button id="confirm">確定</button>
		<button id="close">取消</button>
	</div>
</div>

<style>
.backdrop {
	position: fixed; inset: 0; display: grid; place-items: center;
	background: rgba(0,0,0,0.45);
}
.dialog {
	min-width: 320px; padding: 24px; border-radius: 12px;
	background: white; box-shadow: 0 12px 32px rgba(0,0,0,0.2);
}
</style>

<script>
const modal = document.getElementById('modal')
const openBtn = document.getElementById('open')
const closeBtn = document.getElementById('close')
const confirmBtn = document.getElementById('confirm')

function openModal() {
	modal.hidden = false
	document.body.style.overflow = 'hidden' // 鎖定背景滾動
	modal.querySelector('button').focus()
}

function closeModal() {
	modal.hidden = true
	document.body.style.overflow = ''
	openBtn.focus()
}

openBtn.addEventListener('click', openModal)
closeBtn.addEventListener('click', closeModal)
confirmBtn.addEventListener('click', () => {
	alert('已刪除')
	closeModal()
})

modal.addEventListener('click', (e) => {
	if (e.target === modal) closeModal() // 點擊背景關閉
})

document.addEventListener('keydown', (e) => {
	if (e.key === 'Escape' && !modal.hidden) closeModal()
})
</script>

模式設計清單

  • 開啟/關閉:按鈕、Esc、背景點擊
  • 焦點管理:開啟時聚焦首個可互動元素;關閉時還原到觸發源
  • 滾動控制:開啟時 body 禁止滾動,長內容可在對話框內滾
  • 動畫:淡入/縮放即可,避免過度繁複

實戰練習

練習 1:背景點擊關閉(簡單)⭐

為現有 Modal 加上「點擊遮罩即關閉」。

💡 參考答案
javascript
modal.addEventListener('click', (e) => {
	if (e.target === modal) closeModal()
})

練習 2:阻止背景滾動(簡單)⭐

開啟 Modal 時禁止 body 滾動,關閉後恢復。

💡 參考答案
javascript
function openModal() { modal.hidden = false; document.body.style.overflow = 'hidden' }
function closeModal() { modal.hidden = true; document.body.style.overflow = '' }

練習 3:焦點圈(中等)⭐⭐

實作焦點鎖定:按 Tab 時焦點在對話框內循環,不會跑回背景。

💡 參考答案與提示

提示: 收集 dialog 內可聚焦元素,監聽 keydown Tab。

javascript
const focusables = modal.querySelectorAll('button, [href], input, select, textarea')
modal.addEventListener('keydown', (e) => {
	if (e.key !== 'Tab') return
	const first = focusables[0]
	const last = focusables[focusables.length - 1]
	if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus() }
	if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus() }
})

延伸閱讀

  • ARIA Authoring Practices: Dialog (Modal) Pattern
  • Inclusive Components: The Toggle(含焦點管理技巧)
  • MUI / Radix UI 的對話框元件實作,參考其 API 設計

FAQ

  • Q: Modal 內可滾,背景也會滾?
    • A: 開啟時設 body { overflow: hidden; },內容容器再設 max-height + overflow: auto
  • Q: 多層 Modal 怎麼處理?
    • A: 管理 z-index 階層,並只鎖定最上層的焦點與關閉行為。
  • Q: SEO 會受影響嗎?
    • A: Modal 內容通常非主要文案,重點是可及性;保留可關閉途徑即可。

Contributors

The avatar of contributor named as lucashsu95 lucashsu95

Changelog