前言
也是long long ago,设计师想要将一串名单显示在一行,当文本超出一行时,中间部分名单用省略号代替。例如:
// 名单
const names = [
'吴某凡', '房某名', '柯某东', '黄某波', '张某某某墨',
'高某松', '李某迪', '李某峰', '范某冰', '陈某凡凡'
]
组成一行文字是:
平平无奇吴某凡、房某名、柯某东、黄某波、张某某某墨、高某松、李某迪、李某峰、范某冰、陈某凡凡等人为狱警点赞
当不能一行显示完整的文字时,溢出的名单用省略号代替,如:
平平无奇吴某凡、房某名、柯某东、黄某波、...陈某凡凡等人为狱警点赞
很多开发者在面对这样的需求时,第一反应就是觉得不合理,主观的认为省略号不应该显示在中间,应该显示在行尾。
为什么会这样觉得呢?很大程度上是因为css本身赋予了我们在行尾显示省略号的功能:text-overflow: ellipsis;。
所以在面对触及自己知识盲区的需求时,本能地表现出抵触心理,不能在第一时间给出解决方案,最终表现就是和设计、产品一顿怼:这sb需求不合理,做不了,不做,谁爱做谁做,bbbbbbbbbb......
我们掌握技术,是为了去解决具体实际问题,不是看技术给我们带来了什么,我们才能做什么。
我认为css是一个工具,只了解工具本身而不懂得使用工具去创造,无疑是一种悲哀,况且谁能保证自己就100%了解css呢?
回到需求本身,我们来分析一下其中的难点,逐一把问题解决后,就不存在困难了。(实在不行了就实行B计划:解决给我们制造困难的人)
难点
- 省略号不在行尾,不能直接用
text-overflow: ellipsis;; - 省略号后面还带有名单的最后一个元素:
陈某凡凡; - 名单元素文本要完整显示;
- 名单字符长短、文本所在的容器宽度都不一,无法总结规律;
- 纯CSS实现。没错,为了有更好的性能,纯CSS实现无疑是更好的办法,用js实现是很多人都能想到的方法,虽然其也有优点,后面我们会对比纯CSS实现与js实现的优缺点。
解决方案
- 拆解。首先,我们来拆解一下要显示的文案,可分为5个部分:文本前缀、文本数组、文本后缀、文本分隔符、文本替换符。如下图:
其中,文本前缀、文本后缀、文本分隔符、文本替换符是固定显示的,文本数组根据容器宽度自动调整显示的个数。
- 隐藏超出的文本。将每个文本数组元素设置为行内块,利用行内块元素会换行的特性,将超出的文本元素隐藏。如下图(为了直观,设置偶数文本元素背景色为不同颜色):
如上图,只要利用一个只能显示1行文本高度的容器,再内置一个position: absolute;的容器,就可以隐藏换行后的文本元素了。
- 补充末尾文本。上图少了行尾的“...陈某凡凡等人为狱警点赞”文本,我们来补充进去。
行尾的文本由文本替换符、文本数组最后一个元素、文本后缀组成,可以看到,这是一个固定组合的文本。由于我们不知道一行到底会显示多少个文本数组元素,所以用绝对定位的方式把这段文本加到每一个文本元素后面,再利用与容器背景色一致的颜色将其隐藏。如下图:
- 补充占位行尾文本。看下图,蓝色区域是内容宽度,这个宽度显然不对,内容应该撑满父容器的宽度才对;而且,如果内容占不满一行,不就露馅了吗?
解决方案也很简单,使用同样的行尾文本,设置透明度opacity: 0;,把位置占着就行。
最终效果
缺点分析
- 由于有行尾文本占位,行尾文本中有省略符,就会多占用一点宽度,相对就牺牲了一点显示宽度。如果使用js实现的话,精确度就会高一点,但会牺牲性能;
- 注意上面最终效果图刚开始的部分,范某冰 与 陈某凡凡之间会短暂出现省略符。这是因为文本数组元素陈某凡凡掉到了第二行,然后范某冰后面的文本就显示出来了。
完整代码
<template>
<view class="ellipsis">
<view class="ellipsis-content">
<view class="ellipsis-content-first" :style="bgColorStyle">
<view class="ellipsis-content__text"><text space="nbsp">{{prefix}}</text></view>
<view
v-for="(text, index) in texts"
:key="index"
class="ellipsis-content__text">
<text space="nbsp">{{text.main}}</text>
<view class="ellipsis-content-outside" :class="[suffixClass]" :style="suffixStyle">
<text space="nbsp">{{text.tail}}</text>
<view class="ellipsis-content__mask"></view>
</view>
</view>
</view>
<view class="ellipsis-content-last"
:class="[suffixClass]"
:style="suffixStyle"><text space="nbsp">{{lastEllipsisText}}</text></view>
</view>
</view>
</template>
<script>
/**
* 纯CSS实现
* 指定文本的位置超长时执行省略处理
* @example 显示第4、5、6、7、8、9、10、11题
* prefix = 显示第
* suffix = 题
* words = [4, 5, 6, 7, 8, 9, 10]
* retain = 1
* separator = 、
* placeholder = ...
* 当显示到words的元素7后,发现已经超长,则最终显示的文本为:
* 显示第4、5、6、7、...11题
* @param {String} prefix 文本前缀
* @param {Array} words 文本数组
* @param {String} suffix 文本后缀
* @param {String} suffixClass 文字后缀类名,用于拓展
* @param {String} suffixStyle 文字后缀样式,用于拓展
* @param {Number} retain 文本超长时保留多少个文本数组的最后元素
* @param {String} separator 文本分隔符
* @param {String} placeholder 文本超长后的替换符
* @param {String} backgroundColor 文本背景色
*/
export default {
name: 'super-ellipsis-css',
props: {
prefix: {
type: String,
default: ''
},
words: {
type: Array,
default: ()=> []
},
suffix: {
type: String,
default: ''
},
suffixClass: {
type: String,
default: ''
},
suffixStyle: {
type: String,
default: ''
},
retain: {
type: Number,
default: 1
},
separator: {
type: String,
default: '、'
},
placeholder: {
type: String,
default: '...'
},
backgroundColor: {
type: String,
default: 'white'
}
},
computed: {
bgColorStyle() {
return `background-color: ${this.backgroundColor};`;
},
texts() {
const {words, separator, retain, suffix, lastEllipsisText} = this;
const texts = [];
const first = words.slice(0, words.length - retain);
for (let i = 0; i < first.length; i++) {
if (i + 1 === words.length) {
texts.push({
main: `${first[i]}`,
tail: suffix
});
} else {
texts.push({
main: `${first[i]}${separator}`,
tail: lastEllipsisText
});
}
}
if (retain > 0) {
const last = words.slice(words.length - retain);
texts.push({
main: `${last.join(separator)}`,
tail: suffix
});
}
return texts;
},
lastEllipsisText() {
const last = this.words.slice(this.words.length - this.retain);
return `${this.placeholder}${last.join(this.separator)}${this.suffix}`;
}
}
};
</script>
<style lang="less" scoped>
.ellipsis {
position: relative;
display: flex;
width: 100%;
font-size: inherit;
color: inherit;
line-height: inherit;
font-weight: inherit;
overflow: hidden;
&::before {
content: '\00A0';
display: block;
width: 0;
white-space: nowrap;
overflow: hidden;
opacity: 0;
}
}
.ellipsis-content {
position: absolute;
top: 0;
left: 0;
display: flex;
}
.ellipsis-content-first {
position: relative;
flex: 1;
}
.ellipsis-content__text {
position: relative;
display: inline-block;
vertical-align: top;
background-color: inherit;
z-index: 1;
}
.ellipsis-content__mask {
position: absolute;
top: 0;
right: 0;
display: none;
width: 9999px;
height: 100%;
background-color: inherit;
transform: translateX(100%);
}
.ellipsis-content__text:last-child .ellipsis-content__mask {
display: block;
}
.ellipsis-content-last {
flex-shrink: 0;
opacity: 0;
}
.ellipsis-content-outside {
position: absolute;
right: 0;
top: 0;
white-space: nowrap;
background-color: inherit;
transform: translateX(100%);
}
</style>
结语
虽然已经足够应付需求了,但是还有优化的空间,空闲时再解决一下吧,说不定哪天产品就来提bug了,到那时用1秒钟修复,剩下的时间摸鱼,爽歪歪!!!