在 Web 開發中,讓使用者能像 Excel 一樣拖曳調整表格欄寬,是提升資料呈現體驗的關鍵功能。早期常見的做法利用 IE 專屬的 setCapture() 與 expression(),雖然能跑,但相容性極差、操作手感也不好。本文將帶你從經典舊碼出發,重構出一個現代、跨瀏覽器、觸控友善的表格欄寬拖曳方案。
拖曳調整寬度本質上是「按下 → 移動 → 釋放」的三階段流程:
<th>、<col> 或 CSS 變數。<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>表格欄寬拖曳調整 Demo</title>
<style>
table { border-collapse: collapse; width: 100%; table-layout: fixed; }
th, td { border: 1px solid #ddd; padding: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
th { background: #f5f5f5; position: relative; user-select: none; }
.resize-handle {
position: absolute; top: 0; right: 0; width: 6px; height: 100%;
background: transparent; cursor: col-resize; z-index: 1;
transition: background 0.15s;
}
.resize-handle:hover, .resize-handle.dragging { background: #1890ff; opacity: 0.6; }
/* 觸控裝置加大熱區 */
@media (pointer: coarse) { .resize-handle { width: 14px; } }
</style>
</head>
<body>
<table id="dataTable">
<thead>
<tr>
<th>姓名<span class="resize-handle" data-col="0"></span></th>
<th>部門<span class="resize-handle" data-col="1"></span></th>
<th>職稱<span class="resize-handle" data-col="2"></span></th>
<th>備註<span class="resize-handle" data-col="3"></span></th>
</tr>
</thead>
<tbody>
<tr><td>張三</td><td>研發部</td><td>工程師</td><td>核心成員</td></tr>
<tr><td>李四</td><td>行銷部</td><td>專員</td><td>負責推廣</td></tr>
</tbody>
</table>
<script>
(() => {
const table = document.getElementById('dataTable');
const handles = table.querySelectorAll('.resize-handle');
let startX = 0, startWidth = 0, currentHandle = null, currentTh = null;
const onMove = (clientX) => {
if (!currentHandle) return;
const dx = clientX - startX;
const newWidth = Math.max(40, startWidth + dx); // 最小 40px
currentTh.style.width = newWidth + 'px';
// 若使用 <colgroup><col> 也要同步更新對應 col.style.width
};
const onUp = () => {
if (currentHandle) currentHandle.classList.remove('dragging');
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('mouseup', onUp);
document.removeEventListener('touchmove', touchMoveHandler, { passive: false });
document.removeEventListener('touchend', onUp);
currentHandle = currentTh = null;
};
const mouseMoveHandler = e => onMove(e.clientX);
const touchMoveHandler = e => { e.preventDefault(); onMove(e.touches[0].clientX); };
handles.forEach(handle => {
const th = handle.closest('th');
const colIndex = +handle.dataset.col;
const start = (clientX) => {
currentHandle = handle; currentTh = th;
startX = clientX; startWidth = th.offsetWidth;
handle.classList.add('dragging');
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('mouseup', onUp);
document.addEventListener('touchmove', touchMoveHandler, { passive: false });
document.addEventListener('touchend', onUp);
};
handle.addEventListener('mousedown', e => { e.preventDefault(); start(e.clientX); });
handle.addEventListener('touchstart', e => { start(e.touches[0].clientX); }, { passive: false });
});
})();
</script>
</body>
</html>
requestAnimationFrame 節流。localStorage,下次載入自動套用。role="separator"、aria-orientation="vertical"、tabindex="0",並支援鍵盤 ←/→ 微調。<ResizableTable> 元件,透過 v-model:columnWidths 雙向綁定。| 現象 | 原因 | 解法 |
|---|---|---|
| 拖曳時文字被選取 | 瀏覽器預設行為 | CSS user-select: none 或 e.preventDefault() |
| 欄寬調整後表格總寬度變了 | table-layout: auto | 強制 table-layout: fixed 並指定總寬 |
| 手機上無法拖曳 | 缺少 touch 事件或 passive: false | 同步綁定 touchstart/move/end 並 preventDefault |
拋棄舊式 IE 專屬寫法,改用標準事件模型與 CSS 變數,不只相容性大幅提升,程式碼也更易維護。下次需要表格欄寬拖曳時,直接套用上述模式即可快速上線。