IM應用中如何計算富文字的高度

黃Java發表於2018-04-29

背景

在開發IM的專案過程中,經常會有出現一些需要計算DOM高度,然後超出若干行隱藏等需求。很多時候,需要計算高度的DOM元素都是動態生成的,我們無法在資料渲染前獲取到它的高度。

如果沒有任何互動,我們可以通過CSS來實現這個需求。但是,如果我們需要使用JavaScript來實現一些互動(比如訊息渲染時,超過2行顯示某個特定按鈕等),則只能通過JavaScript來進行實現。我在這裡介紹一種通過JavaScript來對元素高度進行計算的方法,希望能夠給大家提供一些思路。

技術方案

根據前端的基本常識,在記憶體中未渲染的DOM元素是無法獲取到高度的,因此我們有兩個方向來解決這個難題:

  1. 通過字數對行數進行估算。
  2. 將元素渲染後進行高度測算 。

實現方案

以下的實現方案將根據上面所選擇的技術方案來進行實現。

通過字數進行估算

方案

此方案無需多言,就是通過字的總數、行高和每一行能夠容下的字的個數進行估算等。在專案最開始時,我採用的就是這個方案。具體實現程式碼較為簡單,因此不在本文中提供示例。

優點

此方案實現簡單,基本不需要任何技術成本。

缺點

只適用於等寬文字,如果出現富文字(比如有emoji或者圖片表情等高度不一致)的情況,則無法適用。如果字型為非等寬字型或者存在\n之類的換行符或者是\t之類的製表符時,估算的準確度也會下降。

在DOM渲染後進行操作

方案

顧名思義,此方案就是先不考慮DOM元素行數邏輯,直接將所有的DOM節點全部渲染到頁面中,渲染完成後再對進行後續邏輯判斷。獲取高度後頁面行數計算將在後面統一講解。

優點

此方案通過直接在實際場景的頁面上渲染後進行高度計算,因此計算精準,不存在任何偏差。同時,此方案實現起來也較為簡單,只需要將業務邏輯執行時間後延,並不需要開發額外的程式碼。

缺點

該方案缺點也比較明顯,由於是先渲染後處理,因此頁面DOM元素會出現重繪和重排,導致頁面閃動,從而影響使用者的體驗。

映象計算

方案

該方案的靈感來自於上一個方案。因為在實際的頁面中進行計算能夠保證頁面高度計算沒有任何誤差,因此我們需要一個實際的場景,讓瀏覽器來幫助我們進行高度計算。同時,我們又不能在具體的功能頁面中先渲染後計算,因此我們可以直接建立一個與實際頁面中一模一樣的容器來進行高度計算。這樣我們既能夠精確計算,又能夠不影響使用者體驗。

具體實現的程式碼可以參考如下示例:

export default function getLines(element = 'div', style = {}, html = '') {
    let node = document.createElement(element);//建立一個新容器
    let length;
    each(style, (element, index) => {
        node.style[index] = element;//將傳入的style遍歷後賦值給新容器
    });
    node.innerHTML = html;
    document.body.appendChild(node);//需要將新容器掛載到DOM中,瀏覽器才會進行高度計算
    let height = global.getComputedStyle(node).height;
    document.body.removeChild(node);//需要將映象DOM進行移除
    if (height.indexOf('px') > 0) {
        length = parseInt(height.split('px')[0]);
    } else {
        length = 0;
    }
    return length;
}
複製程式碼

優點

該方案基本上繼承了第二個方案的所有優點——精確計算,無誤差,並且避免了出現頁面閃動的情況。

缺點

此方案仍然存在一些問題,將新容器掛載到document元素上時,可能會引發DOM元素的重新渲染,極低概率會影響頁面佈局。同時,屬性值等需要自己手動傳入,而不是利用現成的容器,比較費時費力。

方案再優化

利用現有DOM容器

使用cloneNode方法來對現有的容器進行clone,我們可以省去輸入樣式的麻煩,同時能夠精確保證兩個容器完全一致。

隱藏映象DOM

在實踐過程中,在append以後立刻remove映象DOM節點,不會對頁面產生任何影響。如果擔心新增時會給頁面造成閃動效果,可以給映象DOM新增上position:fixed;visibility:hidden;z-index:-999;屬性,能夠讓映象DOM在append到頁面時,不會影響當前頁面的任何佈局。

為什麼我們不使用display:none來實現上述效果呢?因為在使用了該屬性後,window.getComputedStyle獲取的高度將變為auto。同理,如果元素的display屬性為inline時,也會出現類似的效果,因此我們需要將display指定為block或者inline-block

理論上我們的容器都應該為塊級元素,否則計算高度的意義也就不存在了。因此在容器clone時只需要留意即可,不需要重新指定。

兩個優化點經過實踐已經證明可行,具體程式碼就不附上了,如果有需要的可以給我留言~~

通過高度來計算行數

目前,通過高度來計算行數並沒有什麼比較好的方法,一般是通過line-height兩個屬性來進行計算。

如果line-height為倍數的話,則還需要font-size屬性來確定具體高度。

具體演算法為:總高度 / 每一行高度 = 行數

而每一行高度則通過line-height或者line-height* font-size確定。

總結

獲取動態元素的高度一直都是IM專案中的一個重要需求,自己在這個方面也踩了許多坑,因此寫了這一篇部落格來進行記錄,同時其他人如果看到了也可以避免一些常見問題。

由於此方案較為繁瑣,同時容易留下不少坑,不太推薦使用此方法,還是建議通過產品方案等其他手段規避此方案。

相關文章