共计 2639 个字符,预计需要花费 7 分钟才能阅读完成。
序
明天持续推敲播放器的用户体验细节,发现支流播放器的歌单、歌手、专辑、mv 等详情页的形容文字如果太长的话,会主动收起,并提供一个开展按钮,点击即可开展,真不便啊,再看看本人的滚动实现
滚的快看不清,滚的慢又浪费时间,几乎了。
还是来实现这个长文本的开展和收起性能吧
设计
这个性能实现起来并不难,首先须要一个元素寄存形容注释,这个元素的高度是文字自身的高度,很多用户喜爱应用换行和空行来格式化漂亮的文字,为了保留文字自身的换行和空行内容,这里我应用 <pre> 元素作为容器,(也能够通过设置 white-space 属性来保留间断空格),而后须要一个定高的父容器,并设置溢出暗藏,再搁置一个开展 / 收起按钮,点击开展时勾销父容器的定高,点击收起时复原父容器的定高,这样次要工作就实现了,很简略,最初能够把按钮丑化一番。
实现
先来看看实现后的实际效果
点这里查看简略版演示
因为须要在很多个页面中应用到这个开展收起性能,因而思考将它抽成一个通用组件,首先须要申明须要接管的props
props: {
description: {
type: String,
default: ''
}, // 形容文字
lineNumbers: {
type: Number,
default: 3
}, // 收起状态下的最大显示行数
lineHeight: {
type: Number,
default: 22
}, // 行高
fontSize: {
type: Number,
default: 14
}, // 字体大小
btnOutOfWords: {
type: Boolean,
default: true
} // 开展按钮是否不笼罩文本
},
都是些简略的配置,接下来须要应用一些外部 data 来管制组件的运作,
data () {
return {
descHasOverflow: false, // 内容是否溢出
showMore: false, // 是否已开展
saveDescription: '' // 本义后的形容内容
}
}
组件初始化以及每次监听到形容内容变动时咱们都须要判断新内容是否溢出父容器,并将后果记录在 descHasOverflow
中,只有溢出时才会显示开展按钮,判断溢出的技巧也很简略,通过比拟元素的 clientHeight 和 scrollHeight 的大小即可,将判断逻辑封装在 init
办法中
init () {
this.descOverflow = false // 还原未溢出状态
this.showMore = false // 还原收起状态
this.saveDescription = this.saveDescription && this.description.replace(/[<]/g, '<') // 本义 < 符号
// 如果不应用 this.$nextTick 会产生什么?this.$nextTick(() => {
const desc = this.$refs.desc
if (desc.clientHeight < desc.scrollHeight) {this.descOverflow = true}
})
}
接下来在组件 mounted
时以及监听到 description
变动时调用 init
办法即可
watch: {description () {this.init()
}
},
mounted () {this.init()
}
如果父组件是通过路由加载,则在雷同路由间跳转时不会触发自身以及子组件的 mounted
钩子,如果放在了 <keep-alive>
外部,则它自身以及它的子组件一如既往只会触发 mounted
钩子一次,所以仅在 mounted
钩子外部调用 init
还不够。
定义一个办法响应按钮事件
handleShowMore () {this.showMore = !this.showMore}
在模板外部依据 shouMore
的不同,为父容器元素以及开展 / 收起按钮利用不同的款式即可
<template>
<div class="desc-box" :class="{'desc-show-full': showMore}">
<div
class="desc-content"
:style="{'height': showMore ?'auto': (lineNumbers + 1) * lineHeight +'px','font-size': fontSize +'px','line-height': lineHeight +'px'}"
ref="desc"
>
<span> 简介:</span>
<pre v-if="description && description.length > 0">{{saveDescription}}</pre>
<span v-else> 无 </span>
</div>
<div class="show-more" v-if="descOverflow" :style="{'right': btnOutOfWords ?'-30px':'6px'}">
<em></em>
<a class="more_link" @click.prevent="handleShowMore">{{showMore ? '^ 收起' : '... 开展'}}</a>
</div>
</div>
</template>
最初再加亿点 css 代码丑化一下就实现啦,点这里查看残缺代码
最初
在上文提到的 init
办法中,咱们在监听到 description
扭转后,让操作 dom 的代码在$nextTick
办法中执行,如果不应用这个办法,也就是立刻同步计算元素的 clientHeight 和 scrollHeight,此时参加计算的 dom 元素是 description
扭转前的后果(如果仅在 mounted
钩子中,则不会产生这种状况),因为 Vue 中的虚构 dom 是异步渲染的,它会在同步代码执行结束后合并对虚构 dom 的所有更新,比方
example () {for (let i = 0; i < 100; i++) {this.a = i}
}
当调用 example
办法时,只管扭转了一百次 thia.a
的值,却只会触发一次虚构 dom 更新。
咱们的目标是在 dom 更新实现后计算高度,而 Vue 提供的 $nextTick
办法正是为此而生的:
为了在数据变动之后期待 Vue 实现更新 DOM,能够在数据变动之后立刻应用
Vue.nextTick(callback)
。这样回调函数在 DOM 更新实现后就会调用。
它外部利用了 MutationObserver 来监听 dom 元素的批改,一旦 dom 元素被批改,才会调用传入的回调,以此来确保回调函数执行时 dom 曾经更新结束。