明天持续推敲播放器的用户体验细节,发现支流播放器的歌单、歌手、专辑、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, '&lt;') // 本义<符号    // 如果不应用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曾经更新结束。