【Vue】使用Vue实现div收缩效果

by img Microanswer 创建时间:Sep 10, 2019 9:30:28 AM 

标签: vue 手风琴 收缩 过度动画



重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

一、效果展示

你看到的边框只是为了演示效果需要,实际组件是不会有任何内容的,它只提供收缩功能。此处内容可以是任何内容,组件会自动根据内容高度进行展开,需要用户关心的是否开还是关的状态。开发者也只需关心此状态,故此可以放心的使用本控件,实现页面上部分内容的收缩展示。

图1

二、实现要点

基于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 样式,以免内容超出达不到效果。

全文完, 转载请注明出处。 对你有帮助?不如赞一个吧:
发表评论(发表评论需要登录,你现在还没有登录。)
你需要先登录才可以评论。

评论列表 (0条)