Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

FHoulbreque's avatar

Vue Component modal for Bulma with Velocity.js animation

Hi there,

I finally achieved what I wanted with my vue component but... I had so many trouble to get it work like I intended.

I wanted a different effect for the overlay background and the modal itself and when building my component, it led to many issues as I had two v-if and a third for the main block...

The thing I don't understand is why the v-leave hook isn't triggered if a parent v-if is activated... It could have saved me from a lot of hassle... If you have any clue?

I plan on refactoring both transition components and mixed them into one to set up some predefined effects and accept the choice as a prop, but I didn't do that yet. So a lot of duplicate between my two components...

Kind regards.

Here's my HTML in my Laravel view :

<modal :show="footerModal" @close="footerModal = false">
<template slot="header">Mentions légales</template>
    <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab assumenda consequatur distinctio dolor,
        error esse explicabo harum illo laborum laudantium maxime nihil placeat quam rerum, voluptatum!
        Architecto beatae debitis dignissimos et nisi numquam perspiciatis quia recusandae vero voluptates.
        Aut, exercitationem magni nam non numquam omnis pariatur suscipit tempora vitae. Aperiam.
    </p><br />
    <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab, adipisci asperiores,
        consequuntur cupiditate dignissimos distinctio dolore doloremque ducimus eius enim eos error harum
        id illo incidunt itaque laborum magnam magni modi molestias necessitatibus nesciunt nisi non nostrum
        quas quisquam rem repellat sequi sunt tempora tenetur unde vero voluptate. Illum, repellat!
    </p><br />
    <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Autem cumque, ea fuga magnam neque officia
        optio quia quos rem voluptatibus!
    </p>
</modal>

Here is my Modal.vue :

<template>
    <div class="modal is-active" v-if="modalStatus">
        <velocity-fade :duration="500" v-if="modalEvent" :closing="isClosing" @finished="hasFinished++">
            <div class="modal-background" @click.prevent="close" @finished="destroyModal"></div>
        </velocity-fade>
        <velocity-slide :duration="1000" v-if="modalEvent" :closing="isClosing" @finished="hasFinished++">
            <div class="modal-card">
                <header class="modal-card-head">
                    <p class="modal-card-title">
                        <slot name="title">Mentions légales</slot>
                    </p>
                    <button class="delete" @click.prevent="close"></button>
                </header>
                <section class="modal-card-body">
                    <div class="content">
                        <slot></slot>
                    </div>
                </section>
            </div>
        </velocity-slide>
    </div>
</template>

<script>
    import VelocityFade from '../transitions/VelocityFade.vue'
    import VelocitySlide from '../transitions/VelocitySlide.vue'

    export default {
        props: ['show'],
        components: {
            VelocityFade,
            VelocitySlide
        },
        data() {
            return {
                hasFinished: 0,
                isClosing: false
            }
        },
        computed: {
            modalStatus() {
                return (this.hasFinished === 2 && this.isClosing) ? false : this.show;
            },
            modalEvent() {
                return this.isClosing ? true : this.show;
            }
        },
        watch: {
            hasFinished(newHasFinished) {
                if (newHasFinished === 2) {
                    this.$emit('close', true);
                    this.hasFinished = 0;
                    this.isClosing = false;
                }
            }
        },
        methods: {
            close() {
                this.isClosing = true;
            }
        }
    }
</script>

Here's my VelocitySlide.vue component :

<template>
    <transition name="slide" mode="out-in" @enter="enter">
        <slot></slot>
    </transition>
</template>

<script>
    export default {
        props: {
            duration: {
                type: Number,
                default: 500
            },
            closing: Boolean
        },
        methods: {
            beforeEnter(el) {
                el.style.opacity = 1;

            },
            enter(el, done) {
                Velocity(
                    el,
                    {
                        opacity: [1, 0],
                        translateY: ['0%', '-100%']
                    },
                    {
                        duration: this.duration,
                        easing: [0.39, 0.67, 0.04, 0.98],
                        complete() {
                            done()
                        }
                    }
                )
            },
            leave(el) {
                return Velocity.animate(
                    el,
                    {
                        opacity: [0, 1],
                        translateY: ['100%', '0%']
                    },
                    {
                        duration: this.duration,
                        easing: [0.39, 0.67, 0.04, 0.98],
                    }
                );
            },
        },
        watch: {
            closing(newClosing) {
                if (newClosing) {
                    this.leave(this.$el)
                        .then(() => {
                            this.$emit('finished', true);
                        });
                }
            }

        }

    }
</script>

and finally my VelocityFade.vue

<template>
    <transition name="fade" mode="out-in" @enter="enter">
        <slot></slot>
    </transition>
</template>

<script>
    export default {
        props: {
            duration: {
                type: Number,
                default: 500
            },
            closing: Boolean
        },
        methods: {
            enter(el, done) {
                Velocity(
                    el,
                    {opacity: [1, 0]},
                    {
                        duration: this.duration,
                        easing: [0.39, 0.67, 0.04, 0.98],
                        complete() {
                            done()
                        }
                    }
                )
            },
            leave(el) {
                return Velocity(
                    el,
                    {opacity: [0, 1]},
                    {
                        duration: this.duration,
                        easing: [0.39, 0.67, 0.04, 0.98],
                    }
                )
            }
        },
        watch: {
            closing(newClosing) {
                if (newClosing) {
                    this.leave(this.$el)
                        .then(() => {
                            this.$emit('finished', true);
                        });
                }
            }

        }
    }
</script>
0 likes
1 reply
FHoulbreque's avatar

Hello again,

I decided to symplify my approach totally...

So I changed the Modal.vue model to this following example.

Tell me if you know a way to simplify Velocity animation because I tried the sequences from the velocity.ui add-on, bug it's not working as intended... It do not wait until the end of the animation to hide... So I think there was an issue there in my programming.

Anyway, I need advices on my coding so feel free to give me any feedback :)

Kind regard.

<template>
    <transition :css="false" @enter="enter" @leave="leave">
        <div class="modal is-active" v-if="modalStatus">
            <div class="modal-background" @click.prevent="close"></div>
            <div class="modal-card" ref="mod">
                <header class="modal-card-head">
                    <p class="modal-card-title">
                        <slot name="title">Mentions légales</slot>
                    </p>
                    <button class="delete" @click.prevent="close"></button>
                </header>
                <section class="modal-card-body">
                    <div class="content">
                        <slot></slot>
                    </div>
                </section>
            </div>
        </div>
    </transition>
</template>

<script>
    export default {
        props: ['show'],
        data() {
            return {
                animParams: {
                    duration: 1000,
                    easing: [0.39, 0.67, 0.04, 0.98]
                }
            }
        },
        computed: {
            modalStatus() {
               return this.show;
            }
        },
        methods: {
            close() {
                this.$emit('close', true);
            },
            enter(el, done) {
                Velocity(
                    el,
                    {
                        opacity: [1,0]
                    },
                    {
                        duration: 1000,
                        easing: [0.39, 0.67, 0.04, 0.98]
                    }

                );
                Velocity(
                    this.$refs.mod,
                    {
                        translateY: ['0%', '-100%']
                    },
                    {
                        duration: 1000,
                        easing: [0.39, 0.67, 0.04, 0.98],
                        complete() { done ()}
                    }
                )

            },
            leave(el, done) {
                Velocity(
                    el,
                    {
                        opacity: [0,1]
                    },
                    {
                        duration: 500,
                        easing: [0.39, 0.67, 0.04, 0.98]
                    }

                );
                Velocity(
                    this.$refs.mod,
                    {
                        translateY: ['100%', '0%']
                    },
                    {
                        duration: 1000,
                        easing: [0.39, 0.67, 0.04, 0.98],
                        complete() { done ()}
                    }
                )
            }
        },

    }
</script>

Please or to participate in this conversation.