使用JavaScript實現(xiàn)文本收起展開(省略)功能
省略號,作為一種常見的文本處理方式,在很多情況下都十分常見。特別是當(dāng)我們需要在省略號后面添加額外文字時,這種需求更是不少見。

然而,僅僅依賴 CSS 來實現(xiàn)兼容性、流暢的文本省略效果是不現(xiàn)實的。就拿 Vant4 的 TextEllipsis 文本省略功能來說,我們可以從中學(xué)到不少。
參考Vant4:TextEllipsis 文本省略,為大家分析一波案例(感覺麻煩可以去看下面的附帶代碼)。
在整個代碼中,關(guān)鍵之處在于使用了 tail、middleTail 等關(guān)鍵手段。我們著重討論 tail,即在文本尾部插入內(nèi)容的操作。
簡單的CSS隱藏
大家或多或少都曾編寫過 CSS 省略文本的代碼。比如,要將文本限制在四行內(nèi)顯示,可以使用以下 CSS 代碼:
隱藏為四行
overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; /* 控制顯示行數(shù) */

其中的限制
盡管如此,這種方法有著一定的局限性:
- 如何在省略號后添加展開、收起等文字?
- 添加后,如何保證文字與省略號在同一行?
- 展開后,如何保證文字在合適位置顯示?
純 CSS 可以實現(xiàn)這些效果,但實際操作起來會比較困難。
既然 CSS 不夠理想,我們可以考慮使用 JavaScript 來實現(xiàn)這些功能。
首先,讓我們來分析一波:
- 收起時保留的高度是多少?
- 如何截取超出高度的文字內(nèi)容?
- 如何確保截取后的文字內(nèi)容不影響原來的渲染和展示?
完成以上三點,我們就完成了任務(wù)。
開始分析
<div class="root">
散文是一種文學(xué)形式,與之相對的稱為韻文或詩文。散文可分為廣義與狹義的解釋,其中,廣義散文最容易辨識與定義的方式,是“松散”的結(jié)構(gòu)。也就是說,扣除其它文學(xué)形式重疊部分,運用普通語法結(jié)構(gòu),不講究音韻,不講究排比,沒有任何束縛及限制的文字梳理方式,都可稱為散文。除此,狹義散文是單指文學(xué)范疇內(nèi),結(jié)構(gòu)松散之非韻文作品。文學(xué)專指的散文,歷代作品有著各時代不同流變的脈絡(luò),而正因為松散帶來的自由,散文作品表達出的思想通常有著豐富與圓滿的特色
</div>
<style>
.root {
line-height: 1.6;
overflow-wrap: break-word;
word-break: break-all;
}
</style>
首先解決:收起時要保留的高度是多少?
假設(shè)我們保留4行:
需要給予文本限高 : line-height: 1.6;
row = 4
const maxHeight = Math.ceil(
(Number(rows) + 0.5) * pxToNum(lineHeight) + "一些其他的容器高度:比如padding"
);
在這個計算中,0.5 是一個調(diào)整值,用于在計算最大高度時考慮到文本行數(shù)的不確定性。這個調(diào)整值的目的是為了確保即使行數(shù)計算稍微超過了預(yù)期的行數(shù),也能夠提前截斷文本,以防止文本超出容器的高度限制。
得到了高度,如何切割文字
注意看:tail() 函數(shù)是一個遞歸函數(shù),用于在給定的容器高度限制下,計算文本的截斷位置。讓我來詳細解釋一下 tail() 函數(shù)的工作原理:
const tail = (left, right) => {
if (right - left <= 1) {
return content.slice(0, left) + dots;
}
const middle = Math.round((left + right) / 2);
container.innerText = content.slice(0, middle) + dots + actionText;
if (container.offsetHeight > maxHeight) {
return tail(left, middle);
}
return tail(middle, right);
};
container.innerText = tail(0, end);
當(dāng)遞歸結(jié)束時,函數(shù)會返回最終的截斷位置,即最佳的截斷位置。
遞歸都有自己結(jié)束判斷,這個遞歸的結(jié)束點就在right - left <= 1,
計算中間位置 middle ,并將文本內(nèi)容從左邊界到中間位置加上省略符號,然后設(shè)置到容器元素中
檢查容器元素的高度是否超過了指定的最大高度限制。如果超過了,說明當(dāng)前截斷位置過早,需要繼續(xù)向左邊界靠近進行截斷;否則,說明當(dāng)前截斷位置過晚,需要繼續(xù)向右邊界靠近進行截斷。
最終找到,合適的middle值,在一起返回 content.slice(0, left) + dots 作為結(jié)點的 innerText 文字。
期間,一直在收縮,就算高度相同也在收縮右邊界的內(nèi)容, right - left <= 1 的作用就是剛剛好實現(xiàn)的截斷位置以滿足給定的最大高度限制,不會超出限制。
總結(jié):這個 tail() 函數(shù)使用遞歸的方式,根據(jù)給定的容器高度限制,計算出文本的截斷位置。遞歸的結(jié)束判斷在 right - left <= 1,通過不斷調(diào)整 middle 的值,最終找到合適的截斷位置以滿足給定的最大高度限制,不會超出限制。
在切割下,保證文字內(nèi)容不影響原來的渲染展示
為了不影響原文本的渲染展示,我們使用了克隆節(jié)點的方式。這里參考了 Vant 中的代碼,并做了適當(dāng)修改。
修改成了,更加適合JS寶寶體質(zhì)的代碼。
const rootNode = document.querySelector(".root");
const cloneContainer = () => {
// cloneContainer就是為了得到文本的高度
if (!rootNode || !rootNode.isConnected) return;
const originStyle = window.getComputedStyle(rootNode);
const container = document.createElement('div');
const styleNames= Array.prototype.slice.apply(originStyle);
styleNames.forEach((name) => {
container.style.setProperty(name, originStyle.getPropertyValue(name));
});
container.style.position = 'fixed'; // 不在文檔流中
container.style.zIndex = '-9999'; // 看不到
container.style.top = '-9999px'; // 看不到
container.style.height = 'auto';
container.style.minHeight = 'auto';
container.style.maxHeight = 'auto';
container.innerText = content;
document.body.appendChild(container);
return container;
};
解析:window.getComputedStyle 是一個用于獲取指定元素的所有計算樣式的方法。它返回一個 CSSStyleDeclaration 對象,其中包含了指定元素的所有計算樣式屬性及其對應(yīng)的值。
實現(xiàn)效果

實例代碼
html
<style>
.root {
padding: 12px;
line-height: 1.6;
overflow-wrap: break-word;
word-break: break-all;
}
</style>
<body>
<div class="root">
散文是一種文學(xué)形式,與之相對的稱為韻文或詩文。散文可分為廣義與狹義的解釋,其中,廣義散文最容易辨識與定義的方式,是“松散”的結(jié)構(gòu)。也就是說,扣除其它文學(xué)形式重疊部分,運用普通語法結(jié)構(gòu),不講究音韻,不講究排比,沒有任何束縛及限制的文字梳理方式,都可稱為散文。除此,狹義散文是單指文學(xué)范疇內(nèi),結(jié)構(gòu)松散之非韻文作品。文學(xué)專指的散文,歷代作品有著各時代不同流變的脈絡(luò),而正因為松散帶來的自由,散文作品表達出的思想通常有著豐富與圓滿的特色
</div>
</body>
<script src="TextEllipsis.js"> </script>
JS代碼
document.addEventListener("DOMContentLoaded", function() {
let expanded =true;
let collapseText = "收起";
let expandText = "展開";
const rows = 3;
let actionText = expanded ? collapseText : expandText;
let hasAction = false;
const rootNode = document.querySelector(".root");
const content = rootNode.innerText;
let text = "";
const dots = "...";
const pxToNum = (value) => {
if (!value) return 0;
const match = value.match(/^\d*(\.\d*)?/);
return match ? Number(match[0]) : 0;
};
const cloneContainer = () => {
// cloneContainer就是為了得到文本的高度
if (!rootNode || !rootNode.isConnected) return;
const originStyle = window.getComputedStyle(rootNode);
const container = document.createElement('div');
const styleNames= Array.prototype.slice.apply(originStyle);
styleNames.forEach((name) => {
container.style.setProperty(name, originStyle.getPropertyValue(name));
});
container.style.position = 'fixed';
container.style.zIndex = '-9999';
container.style.top = '-9999px';
container.style.height = 'auto';
container.style.minHeight = 'auto';
container.style.maxHeight = 'auto';
container.innerText = content;
document.body.appendChild(container);
return container;
};
const calcEllipsised = () => {
const calcEllipsisText = (
container,
maxHeight
) => {
const end = content.length;
const calcEllipse = () => {
const tail = (left, right) => {
if (right - left <= 1) {
return content.slice(0, left) + dots;
}
const middle = Math.round((left + right) / 2);
container.innerText = content.slice(0, middle) + dots + actionText;
if (container.offsetHeight > maxHeight) {
return tail(left, middle);
}
return tail(middle, right);
};
container.innerText = tail(0, end);
};
calcEllipse();
return container.innerText;
};
const container = cloneContainer();
if (!container) {
needRecalculate = true;
return;
}
let { paddingBottom, paddingTop, lineHeight } = container.style;
const maxHeight = Math.ceil(
(Number(rows) + 0.5) * pxToNum(lineHeight) +
pxToNum(paddingTop) +
pxToNum(paddingBottom),
);
if (maxHeight < container.offsetHeight) {
hasAction = true;
text = calcEllipsisText(container, maxHeight);
} else {
hasAction = false;
text = content;
}
document.body.removeChild(container);
};
const toggle = (isExpanded = !expanded) => {
expanded = isExpanded;
actionText = expanded ? collapseText : expandText;
};
const renderAction = () => {
calcEllipsised();
const container = document.createElement('span');
container.classList.add("expend_txt");
container.addEventListener('click',()=>{
toggle();
container.innerText = hasAction ? actionText : null;
rootNode.innerText = expanded ? content : text;
rootNode.appendChild(container);
});
container.innerText = hasAction ? actionText : null;;
rootNode.appendChild(container);
}
renderAction()
})
以上就是使用JavaScript實現(xiàn)文本收起展開(省略)功能的詳細內(nèi)容,更多關(guān)于JavaScript文本展開收起的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用json對象轉(zhuǎn)化為key,value的對象數(shù)組
這篇文章主要介紹了使用json對象轉(zhuǎn)化為key,value的對象數(shù)組方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
javascript中innerText和innerHTML屬性用法實例分析
這篇文章主要介紹了javascript中innerText和innerHTML屬性用法,實例分析了javascript中innerText和innerHTML屬性的作用和相關(guān)的使用技巧,需要的朋友可以參考下2015-05-05
微信小程序基于movable-view實現(xiàn)滑動刪除效果
這篇文章主要介紹了微信小程序基于movable-view實現(xiàn)滑動刪除效果,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
JavaScript語法 JSON序列化之stringify實例詳解
這篇文章主要為大家介紹了JavaScript語法 JSON序列化之stringify實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
用js實現(xiàn)層隨著內(nèi)容大小動態(tài)漸變改變 推薦
以前做谷歌的小工具時,api里提供了一個很有用的函數(shù),那就是在程序運行時可以使層動態(tài)隨內(nèi)容大小而變化,而且是平滑變換,在一些jquery的lightbox里也普遍有這種效果,看起來很酷的樣子。2009-12-12
判斷javascript的數(shù)據(jù)類型(示例代碼)
這篇文章主要是對判斷javascript的數(shù)據(jù)類型(示例代碼)進行了詳細的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12

