1.0 需求背景
需求很常见,就是当一行文字过多时,显示省略号,然后显示展开两个字,点击,文字完全展示开,点击收起,回到省略形式,如下图
2.0 需求分析
有了上图,应该能更好理解,让我们再来细致分析下思路:
- 一行省略号,这个没啥难度,css可以实现(至于多行,差别不大)
- 展开和收起,初步构想是,收起状态时是通过css控制的省略号,那展开时,我们可以移除省略号的css,这样只需要添加、移除类名即可
- 如何确定当前行是否有省略号?这个问题,之前想过字体大小+屏幕宽度来计算一行最多能放下多少个字,实际发现,不够准确,后面想到另外一种方案;
通过两个盒子嵌套,外面大盒子只有一行文字高度,并且溢出隐藏,内层盒子就正常显示,当内层盒子高度 > 外层盒子,那么一定有省略号,故可以确定如下结构
<!-- 外层盒子,固定只显示一行 -->
<view class="outer" style="height: {{outerHeight}}{{outerHeight == 'auto' ? '' : 'px'}}"><!-- 内层盒子,正常摆放,但是要动态添加省略号类名 --><view class="inner {{ show ? '' : 'ellipsis' }} " style="padding-right: {{ show ? '0' : paddingRight }}px;">{{text}}</view>
</view>
- **如何确定一行文字的高度呢?**由于考虑到组件的通用性,笔者这里想了一个办法,写一个dom,将其定位到页面看不见的地方,然后获取一下,并且这里面的字体大小由外部传入,这样就能保证,不管怎么设置都可以准确获取
<!-- 用于获取一行高度dom -->
<view class="get-height" >获取一行高度的盒子
</view>
3.0 具体实现
上面列举了几个问题,以及解决思考,现在我们就来具体实现
首先,获取dom高度肯定是需要的,这里把它简单封装下
/*** 获取dom信息* **/ getHeight(selector) {return new Promise((resolve,reject) => {const query = wx.createSelectorQuery().in(this)query.select(selector).boundingClientRect(function(res){resolve(res) }).exec()})},
剩下的,大概就是高度差的判断,决定是否有省略号,以及动态添加、移除css类名,这个过程不算复杂
其实还有一个问题,就是,当确定要显示省略号时,右边切换的dom是需要定位到当前行的末尾,同时,当前行业需要一个padding,而这个padding就是切换按钮的宽度,所以这里也有一点点逻辑
4.0 完整如下
html
<view class="intercept" style="font-size: {{fontSize}};" ><!-- 外层盒子,固定只显示一行 --><view class="outer" style="height: {{outerHeight}}{{outerHeight == 'auto' ? '' : 'px'}}"><view class="inner {{ show ? '' : 'ellipsis' }} " style="padding-right: {{ show ? '0' : paddingRight }}px;">{{text}}</view></view><view wx:if="{{ heightInfo.realHeight > heightInfo.baseHeight }}"class="collapse {{show ? 'collapse-fold' : ''}}"bindtap="toggle" >{{show ? '收起' : '展开'}}</view><!-- 用于获取一行高度dom --><view class="get-height" >获取一行高度的盒子</view>
</view>
js
// components/interceptText/interceptText.js.js
Component({/*** 组件的属性列表*/properties: {text: {type: String,value: ''},fontSize: {type: String,value: '28rpx'// 单位rpx}},/*** 组件的初始数据*/data: {heightInfo: {baseHeight: 0,realHeight: 0},show: true,// 需要动态设置的外层盒子高度outerHeight: 0,paddingRight: 0},lifetimes: {async attached() {this.calculateHeight()},},/*** 组件的方法列表*/methods: {/*** 动态获取展开文字宽度* 确保展开、收起文字能显示* **/ async getCollapseWidth() {let res = await this.getHeight('.collapse')if(!res) returnthis.setData({paddingRight: `${res.width}` || 30})}, /*** 计算高度差,判断是否有省略号* **/ async calculateHeight() {return Promise.all([this.getHeight('.get-height'),this.getHeight('.inner')]).then((res) => {let [baseRes,realRes] = reslet baseHeight = parseInt(baseRes.height || 0)let realHeight = parseInt( realRes.height || 0)if(!baseHeight || !realHeight) {this.setData({outerHeight: 'auto',})return}this.setData({heightInfo: {baseHeight,realHeight},outerHeight: baseHeight,show: !(realHeight > baseHeight)})// 计算展开、收起wx.nextTick(() => {this.getCollapseWidth()})})},/*** 获取dom信息* **/ getHeight(selector) {return new Promise((resolve,reject) => {const query = wx.createSelectorQuery().in(this)query.select(selector).boundingClientRect(function(res){resolve(res) }).exec()})},/*** 展开状态切换* **/ toggle() {this.setData({ show: !this.data.show,outerHeight: this.data.show ? this.data.heightInfo.baseHeight : this.data.heightInfo.realHeight})},}
})
css
.intercept{position: relative;
}
.outer{overflow: hidden;
}
.get-height{position: absolute;z-index: -9999;top: -100%;left: -100%;height: auto;
}
.ellipsis{display: -webkit-box;text-overflow: ellipsis;overflow: hidden;-webkit-line-clamp: 1;-webkit-box-orient: vertical;
}
.collapse{position: absolute;right: 0;bottom: 0;width: fit-content;color: #1989fa;
}
.collapse-fold{position: unset;
}
5.0 总结
笔者认为,实现该功能的难点是如何判断当前行是否应该省略,这里采取一个高度差的办法,基本就没有兼容性问题,不过实际中发下,文字会有闪烁,这是因为高度都是动态计算导致的,展开、收起,可能改为插槽更合适点