超出容器部分文字做省略,这是基本写前端代码都会遇到的问题。

某些位置为了表现完美不额外加滚动条,都会要求多出的部分作省略,例如文章简介”今天天气真的好…”,文本之后部分点击进入详情页才能看到,超出多余的用”…”省略。

然而,一般这些简介都是用后台语言去作处理,例如只显示前140字等,如何用前端代码实现?

你会想到用css的overflow:hidden;white-space:nowrap;text-overflow:ellipsis实现省略,然而这只能省略一行的文字,如果多行文字则失效,应该如何解决?

在新版的chrome中有一个属性-webkit-line-camp属性,它允许你指定特定行数省略。

例如:3行后省略

1text-overflow:ellipsis;
2 
3display:-webkit-box;
4 
5-webkit-line-clamp:3;
6 
7-webkit-box-orient:vertical;

另外,在opera中也有相关的支持属性text-overflow:-o-ellipsis-lastline,他能识别超出容器的最后一行作省略,然而明显地,chrome的做法更优雅,更灵活。

可是,以上都是个别浏览器的实现,想要各种浏览器(包括IE哥哥)都用上,怎么办?

于是我就写了这个东西:mlellipsis.js

先猛戳我这个简陋的demo,调用方法其实很简单:

1element.mlellipsis(5);//element即dom元素

其中,5是行数,也就是和chrome的-webkit-line-clamp实现方式一样,指定特定行数显示;

若用jquery的朋友,可以在获取元素后把它转化为js的dom对象。

例如:

1var node = $(".demo > [")[0];
2 
3node.mlellipsis(5);

具体做法如下:

1 获取该调用node结点的文字大小、行高、文本的高度;

2 根据行高*想要省略的行数row,判断文本的高度是否大于需要省略的高度,如果大于则不需要隐藏(例如字数一行就能显示,那么输入row=2也只会显示一行);

3 因为会省略文字,所以先把完整文字内容写入标签的title属性,hover后可以查看全部原文本;

4 动态截断文本后面的文字,并加上”…”,直到截断后的文字高度和目标的高度一样,则不拦截;

逐步分析:

1.获取计算后属性

首先获取node节点的三个值会涉及到浏览器的兼容性,例如一般现代浏览器可用新属性getComputedStyle获取计算后的css属性,而可爱的IE6-8不支持这个属性,需要用this.currentStyle。

「ps:具体差别可点击这里参看zxx对getComputedStyle介绍的文章」

其实如果用jquery的话可以直接调用css()方法获取,然而懒得加载jquery,因此我重写了一次。

01Element.prototype.getFinalStyle=function(property){
02var s;
03if(window.getComputedStyle){
04s = window.getComputedStyle(this, null)[property];
05}
06else{
07s = this.currentStyle.property;
08}
09return s.substring(0,s.length-2);//减去元素的单位px
10}

因此,获取元素属性的就很简单了:

1//获取计算后的样式
2 var fontSize = this.getFinalStyle("fontSize");
3 var lineHeight = this.getFinalStyle("lineHeight");
4 var height = this.getFinalStyle("height");

注意了,我重写的这个只为了适这个js,因此其他的属性可能不支持,例如没有px或者单位不为px的属性值。

//路人甲:把s.substring()不写进去就行了嘛,那就可以兼容了

//tq:我就是懒……

然而,这里我还发现了一个问题,就是line-height值,一般如果不设置line-height会是什么值?1.5么?

非也,会是获得”normal”的一个string类型,我再找了一下资料{资料1,资料2}发现normal是个神奇的数值,不同浏览器、不同字体、不同系统都会有各自的解析!

//tq:omg…那怎么办

是的,我们要获取正确的高度,就一定要获得精确地line-height值。因此我看了各个line-height的大致比例,我决定把它reset掉,就是如果line-height为normal,则我把line-height设为1.5,即1.5倍的font-size;

2.获取内容文本

无独有偶,获取文本的时候也会遇到浏览器兼容的问题,除了firefox外,其他浏览器都能支持innerText属性,因此直接调用this.innerText木有问题,然而ff的innerText却为undefined,但它却用textContent可以实现相同效果。具体差别点击这里

你懂的,我继续重写:

1Element.prototype.getText=function(){
2if(this.innerText){return this.innerText;}
3else{return this.textContent;}
4}
5 
6Element.prototype.setText=function(str){
7if(this.innerText!=""){this.innerText=str||"";}
8else{this.textContent=str||"";}
9}

调用方法就简单地this.getText()即可或者this.setText(“str”)改变其值

3.设置title值

这个很简单

1var str = this.getText();
2 this.setAttribute("title",str);

4.关键步骤:动态截取文本

这里用了正则,匹配当文本高度小于目标高度(省略行数x行高=元素省略时该显示的目标高度)时,不断截取后面的文本,并加上”…”省略号。

01//减去末尾文字
02 while(dheight<this.clientHeight){
03str = this.getText();
04this.setText(str.replace(/(\s)*([a-zA-Z0-9]+|\W)(\.\.\.)?$/,"..."));
05 }
06 
07/*
08 * /(\s)*([a-zA-Z0-9]+|\W)(\.\.\.)?$/ 正则解析:
09 * (\s)* 0或多个空白
10 * ([a-zA-Z0-9]+|\W) 一个或多个字母数字 或 任意不是字母,数字,汉字的字符
11 * (\.\.\.)? 零个或一个...
12 */

5.简单剪枝

看似结束了,但是如果文本有8000+,但是只需要显示前几行,则只显示100+字呢?

那会由于需要不断截取字符,循环会疯掉。用chrome的调试行数console.time()和console.endTime()测试后发现,如果不优化直接上的话8800字需要17s!

然而这里只需要简单剪枝,就可以保持无论多少文字都几十ms的水平了。

剪枝思考:初步打算用二分法,就是当文本高度大于目标高度2倍,则截断一半文字。然而,再考虑到文本里分为三种:中文字、英文字、字符;一般来说,中文字=2英文字=2字符。因此最坏情况可能前面一半字符全部是英文,后面一半文本全部是中文,这样二分法就会截取到25%的高度,因此考虑到最坏情况,需要3倍才截断。

1while(dheight*3<this.getFinalStyle("height")){
2this.setText(str.substring(0,str.length/2));
3str = this.getText();
4 }

这里剪枝还可以大大优化,欢迎提出宝贵意见~*_*

以上,兼容到IE8+,chrome,firefox,safari,opera。

IE7以下不支持element.prototype的原型链扩展,果断鄙视之。

下载:

mlellipsis.js(完整版带注释:1.70k)

mlellipsis.min.js(压缩版:1.06k)

出处targetkiller 原文点此  http://targetkiller.net/mutiple-line-ellipsis/

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐