Модальное окно на Vue js — пишем компонент

В статье мы поговорим как можно реализовать компонент — модальное окно на Vue js, который можно переиспользовать в любом месте вашего проекта и динамически подставлять данные в компонент используя слоты.

Структура модального окна

Для начала определим структуру. Внутри модального окна будет четыре главных блока — заголовок (.modal-title), контент (.modal-content), футер (.modal-footer) и кнопка закрытия. Плюс нам понадобится обертка с затемнением на весь экран чтобы при нажатии на нее окно закрывалось. В основном компоненте модального окна мы будем использовать именованные слоты. Они позволят нам передавать блоки в основной компонент. Подробнее о слотах во Vue js можно почитать здесь.

Назовем основной компонент modal-window.vue и выглядеть он будет так:

<template>
    <div v-if="show" class="modal-shadow" @click.self="closeModal">
        <div class="modal">
            <div class="modal-close" @click="closeModal">&#10006;</div>
            <slot name="title">
                <h3 class="modal-title">Заголовок</h3>
            </slot>
            <slot name="body">
                <div class="modal-content">
                    Дефолтный контент модального окна
                </div>
            </slot>
            <slot name="footer">
                <div class="modal-footer">
                    <button class="modal-footer__button" @click="closeModal">
                        Ок
                    </button>
                </div>
            </slot>
        </div>
    </div>
</template>

<script>
    export default {
        name: "ModalWindow",
        data: function () {
            return {
                show: false
            }
        },
        methods: {
            closeModal: function () {
                this.show = false
            }
        }
    }
</script>

<style scoped
       lang="scss">
    .modal-shadow {
        position: absolute;
        top: 0;
        left: 0;
        min-height: 100%;
        width: 100%;
        background: rgba(0, 0, 0, 0.39);
    }

    .modal {
        background: #fff;
        border-radius: 8px;
        padding: 15px;
        min-width: 420px;
        max-width: 480px;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);

        &-close {
            border-radius: 50%;
            color: #fff;
            background: #2a4cc7;
            display: flex;
            align-items: center;
            justify-content: center;
            position: absolute;
            top: 7px;
            right: 7px;
            width: 30px;
            height: 30px;
            cursor: pointer;
        }

        &-title {
            color: #0971c7;
        }

        &-content {
            margin-bottom: 20px
        }

        &-footer {
            &__button {
                background-color: #0971c7;
                color: #fff;
                border: none;
                text-align: center;
                padding: 8px;
                font-size: 17px;
                font-weight: 500;
                border-radius: 8px;
                min-width: 150px;
            }
        }
    }
</style>

Мы используем директиву v-show чтобы показывать и скрывать модальное окно в зависимости от значения параметра show. Подробнее о директивах тут. Так же у нас есть функция closeModal — функция меняет значение show на false и окно закрывается. Срабатывает функция при клике на кнопку закрытия модального окна и на клик по серой подложке. При добавлении обработчика события клика на элемент подложки мы использовали модификатор события self, который говорит о том, что клик будет срабатывать только на этом элементе и не будет происходить погружение события. В тэги <slot></slot> мы поместили контент, который будет отображаться по умолчанию если мы ничего не передадим в слот.

Использование модального окна с данными по умолчанию

Теперь создадим компонент app.vue и вызовем модальное окно, которое у нас получилось.

<template>
    <div class="page">

        <button сlass="show-modal-button" @click="showModal">Показать модальное окно</button>

        <modal-window ref="modal"></modal-window>
        
    </div>

</template>

<script>
    import ModalWindow from '../modal-window/modal-window.vue'

    export default {
        name: 'App',
        components: {
            ModalWindow
        },
        methods: {
            showModal: function () {
                this.$refs.modal.show = true
            }
        },
    }
</script>

<style lang="scss">
    body {
        font-family: 'Source Sans Pro', sans-serif;
        margin: 0;
        padding: 0;
        height: 100vh;
    }

    .page {
        position: relative;
        width: 100%;
        min-height: 100%;
    }
</style>

Для этого мы импортировали компонент ModalWindow в компонент App и задали ему ref="modal", чтобы в последующем мы могли обратиться к модальному окну. При нажатии на кнопку .show-modal-button выполняется функция showModal. В этой функции мы обращаемся к компоненту модального окна <modal-window></modal-window> через this.$refs.modal и меняем значение свойства show на true. У нас получился такой результат.

Передаем данные в слоты

Теперь посмотрим как мы можем переиспользовать модальное окно с  другими данными. Для этого создадим еще одну кнопку и еще одно модальное окно. После изменений компонент App будет выглядеть следующим образом.

<template>
    <div class="page">

        <button class="show-modal-button" @click="showModal">Показать модальное окно</button>
        <button class="show-modal-button" @click="showModalTwo">Показать еще одно модальное окно</button>

        <modal-window ref="modal"></modal-window>

        <modal-window ref="modalTwo">
            <template v-slot:title>
                <h3 class="modal-title">Добавить отзыв</h3>
            </template>
            <template v-slot:body>
                <textarea class="modal-textarea"                   
                          placeholder="Добавьте отзыв">
                </textarea>
            </template>
            <template v-slot:footer>
                <button class="modal-footer__button" @click="sendDataFunction">Отправить</button>
            </template>
        </modal-window>

    </div>

</template>

<script>
    import ModalWindow from '../modal-window/modal-window.vue'

    export default {
        name: 'App',
        components: {
            ModalWindow
        },
        methods: {
            showModal: function () {
                this.$refs.modal.show = true
            },
            showModalTwo: function () {
                this.$refs.modalTwo.show = true
            },
            sendDataFunction: function () {
                // обработчик отправки данных
            }
        },
    }
</script>

Во втором модальном окне мы передаем в слоты с помощью тега <template> и атрибута v-slot:slotname другие данные. Таким образом блок <h3 class="modal-title">Добавить отзыв</h3> заменит блок по умолчанию, который указан в компоненте модального окна. В слот footer мы передали кнопку, при клике на которую мы можем, например, отправить данные из textarea, которую мы передали в слот body.

Добавляем анимацию

Нашему компоненту не хватает немного изящности. Для этого добавим анимацию модального окна. Вернемся к исходному компоненту и добавим специальный компонент-обертку <transiton></transition>. Дадим ему атрибут name="modal". Компонент transition отвечает за анимацию переходов добавляя классы. Эти классы мы и будем использовать для стилизации анимации. Подробнее об этих классах можно почитать по ссылке.

<template>
    <transition name="modal">
        <div v-if="show" class="modal-shadow" @click.self="closeModal">
            <div class="modal">
                <div class="modal-close" @click="closeModal">&#10006;</div>
                <slot name="title">
                    <h3 class="modal-title">Заголовок</h3>
                </slot>
                <slot name="body">
                    <div class="modal-content">
                        Дефолтный контент модального окна
                    </div>
                </slot>
                <slot name="footer">
                    <div class="modal-footer">
                        <button class="modal-footer__button" @click="closeModal">
                            Ок
                        </button>
                    </div>
                </slot>
            </div>
        </div>
    </transition>
</template>

<script>
    export default {
        name: "ModalWindow",
        data: function () {
            return {
                show: false
            }
        },
        methods: {
            closeModal: function () {
                this.show = false
            }
        }
    }
</script>

<style lang="scss">

    ...

    .modal-enter-active, .modal-leave-active {
        transition: opacity 2s
    }

    .modal-enter, .modal-leave-to {
        opacity: 0
    }

    ...

</style>

В итоге у нас получилась такая анимация появления модального окна:

Из статьи мы узнали как написать переиспользуемый компонент — модальное окно на Vue js с использованием слотов и стандартного компонента анимации.

Cody Maverick: