這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近,我剛剛完成了一個閱讀器的txt文件閱讀功能,但在處理大文件時,遇到了文本內容過多導致瀏覽器崩潰的問題。 一般情況下,沒有任何樣式渲染時不會出現什麼問題,15MB的文件大約會有3秒的空白時間。 <div id="content"></ ...
這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
最近,我剛剛完成了一個閱讀器的txt文件閱讀功能,但在處理大文件時,遇到了文本內容過多導致瀏覽器崩潰的問題。
一般情況下,沒有任何樣式渲染時不會出現什麼問題,15MB的文件大約會有3秒的空白時間。
<div id="content"></div>
fetch('./dp.txt').then(resp => resp.text()).then(text => { document.getElementById('content').innerText = text })
儘管目前還沒有嚴重的問題,但隨著文件繼續增大,肯定會超過瀏覽器記憶體限制而導致崩潰。
在開發閱讀器的過程中,我添加了下麵的樣式,結果導致瀏覽器直接崩潰:
* { margin: 0; padding: 0; } html, body { width: 100%; height: 100%; overflow: hidden; } body { column-fill: auto; column-width: 375px; overflow-x: auto; }
預期結果應該是像下麵這樣分段顯示:
然而,實際出現了下麵的問題:
因此,文件內容太多會導致瀏覽器崩潰。即使進行普通的渲染,我們也要考慮這個問題。
如何解決
解決這個問題的方法有點經驗的前端開發工程師應該都知道可以使用虛擬滾動,重點是怎麼對文本分段分段,最容易的可能就是按照一定數量字元劃分,但是這個導致文本銜接不整齊出現文本跳動。如圖,橙色和藍色表示兩端文本的銜接,虛擬滾動必然會提前移除橙色那塊內容,那麼就會導致藍色文本位置發生改變。
要解決這個問題,我們需要想辦法用某個元素替代原來的位置。當前頁橙色部分刪除並計算位置,問題會變得複雜並且誤差比較大,因此這一部分直接保留,把這部分前面的內容移除,然後用相同長度的元素占據,接下來重點就是怎麼獲取到橙色部分與前面內容的分界點。
獲取分界點可以使用document.createRange()
,document.createRange()
是 JavaScript 中用於創建Range
對象的方法。Range對象表示一個包含節點與文本節點之間一定範圍的文檔片段。這個範圍可以橫跨單個節點、部分節點或者多個節點。
// 創建 Range 對象 const range = document.createRange(); range.setStart(textNode, 0); // 第一個參數可以是文本節點,第二個參數表示偏移量 range.setEnd(textNode, 1); const rect = range.getBoundingClientRect(); // 獲取第一個字元的位置信息
利用Range對象的特性,我們可以從橙色部分的最後一個字元開始計算,直到找到分界點的位置。
閱讀器如果僅僅只是從左往右閱讀,按照上面的方法已經可以實現,但是閱讀器可能會出現頁面直接跳轉,跳轉之後的文本起點你並不知道,並且頁面總頁碼你也無法知道。因此從一開始就要知道每一頁的分界點,也就是需要做預渲染。以下是一個簡單的示例:
let text = '...' const step = 300 let end = Math.min(step, value.length) // 獲取結束點 while (text.length > 0) { node.innerText = value.substring(0, end) // 取部分插入節點 const range = document.createRange() range.selectNodeContents(node) const rect = range.getBoundingClientRect() // 獲取當前內容的位置信息 if (rect.height > boxHeight) { // 判斷當前內容高度是否會超出顯示區域的高度 // 如果超出,從 end 最後一個字元開始計算,直到不超出範圍 while (bottom > boxHeight) { // node.childNodes[0] 表示文本節點 range.setStart(node.childNodes[0], end - 1) range.setEnd(node.childNodes[0], end) bottom = range.getBoundingClientRect().bottom end-- } } else { // 如果沒有超出,end 繼續增加 // ... } }
上面只是簡單的實現原理,可以達到精確區分每一頁的字元,但是計算量有點太大,15MB文本大約500多萬字,迴圈次數估計也在幾十萬到上百萬。在本人的電腦上測試大約需要20秒,每個人設備的性能不同,所需時間也會有所不同。很明顯,這種實現方式並不太理想。
後來我對這個方案進行了優化,實際上我們不需要計算每一頁的分界點,可以計算出多頁的分界點,例如10頁、20頁、50頁等。優化後的代碼是將step
增大,比如設為10000,然後將不能組成一頁的尾部內容去掉。優化後,15MB的文本大約需要4秒左右。需要註意的是,step
並不是越大越好,太大會導致渲染頁面占用時間過長。
這就是我目前用來解決頁面渲染大量文本的方法。如果你有更好的方案,歡迎留言。