從昨天文章的 FB 貼文留言學到新東西。

身為 IE 出身的開發者,用 innerHTML 讀寫元素 HTML 原始碼,用 innerText 讀寫純文字版內文已經是本能反應。讀者 Chester Fung 提醒我一件事,最好用 textContent 取代 innerText,不然遇到隱藏元素文字會消失。一查,這才後知後覺學到二者差異。

依據 MDN 的說明

Node.innerText 是一個代表節點及其後代之「已渲染」(rendered)文字內容的屬性。Node.innerText 近似於使用者利用游標選取成高亮後複製至剪貼簿之元素的內容。此功能最初由 Internet Explorer 提供,並在被所有主要瀏覽器採納之後,於 2016 年正式成為 HTML 標準。
Node.textContent 屬性是一個相似的選擇,但兩者之間仍有非常不同的差異。

而 innerText 與 textContent 的差異如下:

  • textContent 包含 <script><style>,innerText 只包含使用者會讀取到的部分
  • textContent 包含所有子孫元素,innerText 可察覺樣式,忽略隱藏元素
  • innerText 會觸發 Reflow 重繪頁面以確保得到最新樣式結果,效能成本稍高

我設計了以下實驗對照二者差異,確認 innerText 在不同情境下的行為:

如上圖,共有七組測試,由 1, 2, 3 可知,刪節點或超出範圍看不到並不影響 innerText 讀取,其結果與 textContext 相同(滑鼠選取複製得到的也是完整內容)。

由測試 4,text-transform: uppercase 改變大小寫後,innerText 會讀到全大寫內容。

測試 5 當內含元素 display: none,innerText 讀不到東西,但故意在其中放了 <script>console.log('Hi');</script>console.log('Hi'); 會被 textContent 算成 td 的內部文字。

測試 6 加了 <br />,innerText 會解析成 '\n' 換行符號,textContext 則無視。

測試 7 將 World! 包成 SPAN 設 display: none,在 innerText 會消失,但 textContent 不受影響。

線上展示

【結論】

如果元素內容有透過 CSS 隱藏部分內容、修改文字大小寫... 時,而你想擷取使用者看到的文字樣貌(註:不包含超出範圍隱藏的效果),可用 innerText。若要取未受樣式影響的原始文字內容,則用 textContent,但如果在元素內有穿插 <script><style> 等非可見內容,textContent 會將區塊內的文字也算進去,可能產非非預期的結果。

附上完整程式範例:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>innerText, textContent</title>
    <style>
        .container {
            border-collapse: collapse;
            font-family: Verdana, Geneva, Tahoma, sans-serif;
            font-size: 10pt;
        }
        td { border: 1px solid gray; padding: 3px 6px; }
        thead { text-align: center; background-color: #eee; }
        tbody tr:nth-child(even) { background-color: #ffa2; }
        tbody td:nth-child(1) { text-align: center; }
        tbody td:nth-child(3) { color: #00fd; }
        tbody td:nth-child(2) div {
            width: 70px;
            white-space: nowrap;
            border: 1px solid gray;
        }
    </style>
</head>

<body>
    <table class="container">
        <thead>
            <tr>
                <td>No</td>
                <td style="width: 120px">Element</td>
                <td>td.innerHTML</td>
                <td>td.innerText</td>
                <td>td.textContent</td>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td><div style="overflow: hidden; text-overflow: ellipsis">Hello, World!</div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>2</td>
                <td><div  style="overflow: hidden">Hello, World!</div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>3</td>
                <td><div  style="overflow-x: scroll">Hello, World!</div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>4</td>
                <td><div  style="text-transform: uppercase">Hello, World!</div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>5</td>
                <td><div  style="display: none;">Hello, World!<script>console.log('Hi');</script></div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>6</td>
                <td><div>Hello,<br /> World!</div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
            <tr>
                <td>7</td>
                <td><div>Hello,<span style="display: none"> World!</span></div></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        </tbody>
    </table>
    <script>
        const formatHtml = html => {
            return html.replace(/(&lt;div.*?&gt;)/, '$1<div style="color:brown">')
                .replace(/(&lt;\/div&gt;)/, '</div>$1');
        };
        document.querySelectorAll('tbody tr').forEach(d => {
            const tds = [...d.querySelectorAll('td')];
            tds[2].textContent = tds[1].innerHTML;
            tds[2].innerHTML = formatHtml(tds[2].innerHTML);
            tds[3].innerHTML = `<pre>${tds[1].innerText}</pre>`;
            tds[4].innerHTML = `<pre>${tds[1].textContent}</pre>`;
        });
    </script>
</body>

</html>

An explanation and an experiment on the difference between innerText and textContent in JavaScript.


Comments

Be the first to post a comment

Post a comment