說一下我的需求,有個寬度有限的 HTML 表格欄位,內容文字長度不一,大部分都放得下,但偶爾文字過長會折行使列高加倍,造成排版雜亂且不易閱讀。增加欄位是最簡單的解法,但但因表格欄位眾多難再增大。換個思路,既然無法增加欄寬就讓字小一點,若還是擠不下也別勉強,多行就多行;但另外有個問題,原本文字置中,遇折行時應該要改成靠左比較好。

循著以上思路,我想出「用 JavaScript 檢查 <TD> 內含文字是否折行,若是就將 font-size 設為 smaller。若縮小後還是會折行,就將 text-align 改為 left」的解法,如下圖所示。

要如何偵測文字折行呢?問 ChatGPT 跟 Github Copilot 得到用 scrollHeight、offsetHeight 相比的建議,實際上並不可行。最後我在一則 Stackoverflow 討論的冷門回覆找到通關密碼 - getClientRects

getClientRects() 可找出元素在頁面上所佔的矩形區塊大小及位置,當 SPAN 折行時,便會出現兩個以上的矩形區塊,因此我們可以用 spanElement.getClientRects().length > 1 偵測 SPAN 是否折行。如下圖所示,SPAN 「管理員PC」未折行,getClientReacts() 只傳回一個 DOMRect [1]、SPN [辦公區網路印表機]過長折行,getClientReacts() 傳回兩個 DOMRect,分別對映到「辦公區網路印」及「表機」兩個矩形區塊的座標與大小:

自動調字形大小的 JavaScript 範例如下,若需要縮小字形,會在該 TD 加上 data-sf Attribute 註記,稍後再檢查一次這些 TD (td[data-sf]),若縮小後內容還是折行便其 text-align 切換成 left:【2024-1-26 更新】換行時靠左對齊可使用 CSS 實現,見文末補充

const isWordWrapped = (elem) => (elem.getClientRects && elem.getClientRects().length > 1);
function AutoSmallerFont() {
    const elems = [...document.querySelector('tbody').querySelectorAll('tr td:first-child')];
    const flagName = 'data-sf';
    // set smaller font size if any word wrapped
    elems.forEach((td) => {
        td.removeAttribute(flagName);
        if ([...td.querySelectorAll('span')].some(isWordWrapped)) {
            td.style.fontSize = 'smaller';
            td.setAttribute(flagName, '');
        }
    });
    // set text-align to left if still word wrapped after font size reduced
    elems.filter(e => e.hasAttribute('data-sf')).forEach(td => {
        if ([...td.querySelectorAll('span')].some(isWordWrapped)) 
            td.style.textAlign = 'left';
    });
}

實測效果如下:線上展示

又學會一個 JavaScript 小把戲。

【2024-01-26 更新】
感謝讀者 Red 分享,元素置中,折行時靠左對齊可使用 Flex 排版 + justify-content: center 實現。程式再改良,靠左改用 CSS 實現可再省下用 JavaScript 處理的工夫。

首先,設定表格第一欄位樣式如下,justify-content: center 可做到元素置中佔滿,內部折行時靠左對齊的效果。

td:first-child {
      display: flex;
      justify-content: center;
}

TD 欄位內的兩個 SPAN 用一個 SPAN 或 DIV 包起來:

<td>
  <span>
    <span class="net">{{entry.net}}</span> /
    <span class="desc">{{entry.desc}}</span>
  </span>
</td>

JavaScript 簡化,偵測折行縮小字體即可:

const isWordWrapped = (elem) => (elem.getClientRects && elem.getClientRects().length > 1);
function AutoSmallerFont() {
    const elems = [...document.querySelector('tbody').querySelectorAll('tr td:first-child')];
    // set smaller font size if any word wrapped
    elems.forEach((td) => {
        if ([...td.querySelectorAll('span > span')].some(isWordWrapped)) {
            td.style.fontSize = 'smaller';
        }
    });
}

線上展示

Detecting word-wrapped HTML elements and reduce font-size to prevent word wrapping by JavaScript


Comments

# by Red

當文字折行則靠左, 可以用純 css 解決: https://jsfiddle.net/yL41qewh/1/ 僅請參考。

# by Jeffrey

to Red, 請教,當 TD 內容為多個 SPAN,是否無法套用 jsfiddle 範例中的 flex + justify-content: center 技巧?

# by Red

to Jeffery,不確定您的需求情況,可能需要您提供範例了。

# by George

回報:Chrome 可以,FireFox 沒變。

# by Jeffrey

to Red, 可以用文章線上展示的 jsbin 當例子,TD 內含兩個 SPAN,第二個 SPAN 折行。

# by Jeffrey

to George,我實測 FF 也可運作,差別在「客服 Open API」是整個 SPAN 在第二行,自己沒折行所以沒處理到。調整文字讓 SPAN 折行的對照版:https://output.jsbin.com/vakoxaw

# by Red

to Jeffery, 若情況允許, 可以考慮在兩個 span 的外面再包一層 div 或是 span 都可以, 請參考: https://jsfiddle.net/sp0b9auo/1

# by Jeffrey

to Red,這招讚! 謝謝分享~

# by HsiangYu

剛想到個問題, 如果是 Angular、React 這類前端框架函式庫, 也能如法炮製嗎? 用前端框架似乎就只能操縱 Virtual DOM Node, 無法像黑暗大的文章這樣純 HTML、CSS、JavaScript 直接操縱原生實際DOM Node。

# by Jeffrey

to HsiangYu, Virtual DOM 問題可透過 setTimeout 解決,以 Vue 為例 https://output.jsbin.com/dudoyuv app.directive('auto-smaller-font', { mounted(el, binding, vnode, oldVnode) { const td = el; setTimeout(() => { if ([...td.querySelectorAll('span')].some(isWordWrapped)) { td.style.fontSize = 'smaller'; } }, 0); } })

Post a comment