重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。
一、效果展示
你看到的边框只是为了演示效果需要,实际组件是不会有任何内容的,它只提供收缩功能。此处内容可以是任何内容,组件会自动根据内容高度进行展开,需要用户关心的是否开还是关的状态。开发者也只需关心此状态,故此可以放心的使用本控件,实现页面上部分内容的收缩展示。
二、实现要点
基于css的transition功能实现。通过设置不同的视图高度,再配合一个 transition 过渡动画时长的设定,就可以实现这个功能了。不过很有趣,transition并不支持对height(高度)的变化产生预期的开合效果,但支持对 maxHeight 的变化产生开合效果。因此,咱们设置视图的高度,是通过设置 maxHeight 来实现的。
三、代码实现
1、创建文件
不妨咱们将此组件命名为:ShrinkView 吧。创建新的vue单文件:ShrinkView.vue。此控件需提供一个插槽供使用者放入自己的内容。其初始代码如下:
<template>
<div>
<slot></slot>
</div>
</template>
<style scoped>
</style>
<script>
export default {
props: { },
updated () { },
mounted () { },
methods: { },
watch: { },
data () {
return { }
}
}
</script>
2、逻辑实现
由于通过 maxHeight 限定视图高度,当内容多于maxHeight 时必然会超出显示,咱们首先添加超出范围进行隐藏的css样式,以及动画开合过渡时间样式:
<template>
<div class="shrink-view">
<slot></slot>
</div>
</template>
<style scoped>
.shrink-view {
overflow: hidden;
-webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms;
}
<style>
<scr....ipt/>
现在,基本样式定义完成,进行下一步设置不同视图高度操作,此步操作可能涉及逻辑思考了。展开,当希望展开时,一定是希望展开到视图内容完整的高度,而此高度的获取,可通过 dom 的 scrollHeight 获取到,在组件挂载到界面、组件内容更新后,都有必要拿到这个高度值进行保存。因此,在 mounted 和 updated 方法里获取该值是最合适不过的地方:
<temp....late/>
<script>
export default {
props: {
},
updated () {
this.init();
},
mounted () {
this.init();
},
methods: {
// 因为 updated 和 mounted 里都要使用本方法,所以将本方法提取为一个方法方便调用。使用 nextTick 进一步保证视图高度获取精确。
init () {
this.$nextTick(() => {
this.contentHeight = this.$el.scrollHeight;
});
}
},
watch: {
},
data () {
return {
contentHeight: 0, // 使用此字段保存视图内容的实际高度
}
}
}
<script>
<style scoped>...</style>
代码中使用 contentHeight 字段保存了视图内容的实际高度,现在只需要再设定一个字段来保存开合状态即可,不妨使用 mIsOpen 字段来维护这个状态:
<script>
export default {
props: {
value: Boolean // 定义属性 value,这样v-model的值就可以接收到。
},
updated () {
this.init();
},
mounted () {
this.init();
},
methods: {
// 因为 updated 和 mounted 里都要使用本方法,
// 所以将本方法提取为一个方法方便调用。使用 nextTick
// 进一步保证视图高度获取精确。
init () {
this.$nextTick(() => {
this.contentHeight = this.$el.scrollHeight;
});
}
},
watch: {
// 监听 value 的变化,并将变化值赋值给 本组件维护的 mIsOpen 字段中
value (newValue) {
this.mIsOpen = newValue;
},
// 监听 mIsOpen 的变化,一旦变化,将input事件暴露,]
// 这样可实现v-model双向绑定。
mIsOpen (newValue) {
this.$emit('input', newValue);
}
},
data () {
return {
contentHeight: 0, // 使用此字段保存视图内容的实际高度
mIsOpen: this.value // 保存开合状态,默认值使用prop定义的属性(即v-model的值)
}
}
}
<script>
代码中定义了一个data的字段 mIsOpen 和 prop的属性字段 value,前者保存当前组件的开合状态,后者连接了使用者传入的开合状态,并监听属性value,将改变的值赋值给dada的mIsOpen字段,实现组件内的状态变更。同时组件内状态变化后暴露事件input,从而实现双向绑定。
最后,只需要将mIsOpen的不同状态反应到template内,进行视图响应,那么整体任务就算完成:
<template>
<div class="shrink-view" :style="{maxHeight: (mIsOpen?contentHeight:0) + 'px'}">
<slot></slot>
</div>
</template>
<script>...</script>
<style>...</style>
实现也非常简单,只需要根据 mIsOpen 不同的状态,为 maxHeight 赋值不同的高度,即可实现。
现在完整的代码如下:
<!-- ShrinkView.vue -->
<template>
<div class="shrink-view" :style="{maxHeight: (mIsOpen?contentHeight:0) + 'px'}">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
value: Boolean
},
updated () {
this.init();
},
mounted () {
this.init();
},
methods: {
init () {
this.$nextTick(() => {
this.contentHeight = this.$el.scrollHeight;
});
}
},
watch: {
value (newValue) {
this.mIsOpen = newValue;
},
mIsOpen (newValue) {
this.$emit('input', newValue);
}
},
data () {
return {
contentHeight: 0,
mIsOpen: this.value,
}
}
}
</script>
<style scoped>
.shrink-view {
-webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms;
overflow: hidden;
}
</style>
四、使用
现在可以在其他界面上使用这个组件了。下面我在一个最简单的界面使用了这个组件:
<template>
<div>
<h1>Vue实现收缩效果</h1>
<input type="checkbox" v-model="open">
<div style="border: 1px solid gray">
<shrink-view v-model="open">
你看到的边框只是为了演示效果需要,实际组件是不会有任何
内容的,它只提供收缩功能。
<hr>
此处内容可以是任何内容,组件会自动根据内容高度进行展开,
开发者需要关心的只需暴露是否开还是关的状态。开发者也只
需关心此状态,故此可以放心的使用本控件实现页面上部分内
容的收缩展示。
</shrink-view>
</div>
</div>
</template>
<script>
import ShrinkView from '../components/ShrinkView';
export default {
components: {
ShrinkView
},
data () {
return {
open: false
}
}
}
</script>
五、不是VUE怎么办?
放心,如果你不是使用的vue,但你通过阅读本文,你便会知道其实现原理,通过 transition 对 maxHeight 支持从而实现 高度 或 宽度的变化动效,不要忘了添加 overflow: hidden
样式,以免内容超出达不到效果。