Определение структуры шаблона
Начнем с определения нашего шаблона. Нам понадобится div для заднего плана (тени), div для самого модального окна и некоторые элементы, для определения его структуры:
<template>
<div class="modal-backdrop">
<div class="modal">
<slot name="header">
</slot>
<slot name="body">
</slot>
<slot name="footer">
</slot>
</div>
</div>
</template>
Обратите внимание на использование слотов? Мы могли бы использовать входные параметры (props) для создания заголовка (header), тела (body) и футера (footer), но использование слотов даст нам большую гибкость.
Слоты позволяют нам легко использовать одно и то же модальное окно с различными типами содержимого тела нашего компонента. Мы можем использовать модальное окно, чтобы показать простой текст, но мы можем захотеть повторно использовать то же модальное окно для вывода формы, чтобы отправить запрос. Хотя входящих параметров (props) обычно достаточно для создания компонента, предоставление HTML через входящий параметр потребует от нас использовать его через директиву v-html для рендеринга — что может привести к XSS-атакам.
Здесь мы используем именованные слоты, это дает возможность использовать более одного слота в одном компоненте.
Когда мы определяем именованный слот, все, что мы идентифицируем с этим именем, будет отображаться вместо исходного слота — назовем этот исходный слот значением по умолчанию, как placeholder в input.
Подобно placeholder, слот может также иметь контент по умолчанию, который будет отображаться в случае, если мы его не предоставим.
Поскольку предоставленный контент полностью заменяет ‹slot› тег, чтобы гарантировать, что наши секции header, body и footer имеют требуемые классы, нам нужно обернуть каждый слот в соответствующий элемент с нужными классами.
Давайте установим некоторые значения по умолчанию для слотов, их элементов-оберток и начального CSS, чтобы сделать все это похожим на базовое модальное окно.
<script>
export default {
name: 'modal',
methods: {
close() {
this.$emit('close');
},
},
};
</script>
<template>
<div class="modal-backdrop">
<div class="modal">
<header class="modal-header">
<slot name="header">
This is the default tile!
<button
type="button"
class="btn-close"
@click="close"
>
x
</button>
</slot>
</header>
<section class="modal-body">
<slot name="body">
I'm the default body!
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
I'm the default footer!
<button
type="button"
class="btn-green"
@click="close"
>
Close me!
</button>
</slot>
</footer>
</div>
</div>
</template>
<style>
.modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #FFFFFF;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4AAE9B;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #eeeeee;
justify-content: flex-end;
}
.modal-body {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4AAE9B;
background: transparent;
}
.btn-green {
color: white;
background: #4AAE9B;
border: 1px solid #4AAE9B;
border-radius: 2px;
}
</style>
И мы сделали очень простую версию компонента модального окна!
Добавление анимированных переходов
Обратите внимание, как модальное окно резко открывается? Мы можем сделать более плавным ввод / вывод окна, используя анимированный переход.
Vue предоставляет компонент-оболочку ‹transition›, который позволяет нам добавлять анимированные переходы для появления и исчезновения любого элемента HTML или компонента Vue, и позволяет использовать как CSS классы, так и JavaScript хуки.
Каждый раз, когда компонент или элемент, завернутый в елемент ‹transition›, вставляется или удаляется, Vue проверяет, имеет ли данный элемент CSS-переходы и будет добавлять или удалять их в нужное время. То же самое верно и для JavaScript-хуков, но для нашего случая мы будем использовать только CSS.
Когда элемент добавляется или удаляется, для перехода ввода / вывода применяются шесть классов. Каждый из них будет иметь префикс имени перехода. В этом руководстве вы найдете подробное объяснение того, как работают переходы.
Сначала добавим елемент ‹transition› к нашему модальному окну:
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div class="modal">
...
</div>
</div>
</transition>
</template>
Следом добавим CSS-классы для изменения прозрачности — для плавного появления / исчезновения нашего окна:
<style>
.modal-fade-enter,
.modal-fade-leave-active {
opacity: 0;
}
.modal-fade-enter-active,
.modal-fade-leave-active {
transition: opacity .5s ease
}
</style>
Теперь наш компонент модального окна открывается и закрывается гладко!
Делаем модальное окно более доступным
Единственное, чего не хватает — превратить наш компонент в более доступный для людей с ограниченными возможностями.
Мы можем достичь этого, используя aria-атрибуты.
Добавление role=«dialog» поможет вспомогательному программному обеспечению идентифицировать наш компонент как диалоговое (модальное) окно приложения, которое отделено от остальной части пользовательского интерфейса. Хотя добавление роли диалога полезно, этого недостаточно, чтобы сделать его доступным, мы должны соответствующим образом пометить его. Мы можем достичь этого через aria-labelledby и aria-describedby атрибуты. И не забываем также отметить наши кнопки закрытия!
Окончательная версия нашего модального компонента теперь должна выглядеть так:
<script>
export default {
name: 'modal',
methods: {
close() {
this.$emit('close');
},
},
};
</script>
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header
class="modal-header"
id="modalTitle"
>
<slot name="header">
This is the default tile!
<button
type="button"
class="btn-close"
@click="close"
aria-label="Close modal"
>
x
</button>
</slot>
</header>
<section
class="modal-body"
id="modalDescription"
>
<slot name="body">
I'm the default body!
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
I'm the default footer!
<button
type="button"
class="btn-green"
@click="close"
aria-label="Close modal"
>
Close me!
</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
<style>
.modal-backdrop {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.modal {
background: #FFFFFF;
box-shadow: 2px 2px 20px 1px;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.modal-header,
.modal-footer {
padding: 15px;
display: flex;
}
.modal-header {
border-bottom: 1px solid #eeeeee;
color: #4AAE9B;
justify-content: space-between;
}
.modal-footer {
border-top: 1px solid #eeeeee;
justify-content: flex-end;
}
.modal-body {
position: relative;
padding: 20px 10px;
}
.btn-close {
border: none;
font-size: 20px;
padding: 20px;
cursor: pointer;
font-weight: bold;
color: #4AAE9B;
background: transparent;
}
.btn-green {
color: white;
background: #4AAE9B;
border: 1px solid #4AAE9B;
border-radius: 2px;
}
</style>
Использование компонента модального окна в нашем приложении
Теперь мы можем использовать наш компонент, включив его в наше приложение. Вы так же можете попробовать компонент в действии здесь — codepen.
<script>
import modal from './components/modal.vue';
export default {
name: 'app',
components: {
modal,
},
data () {
return {
isModalVisible: false,
};
},
methods: {
showModal() {
this.isModalVisible = true;
},
closeModal() {
this.isModalVisible = false;
}
},
};
</script>
<template>
<div id="app">
<button
type="button"
class="btn"
@click="showModal"
>
Open Modal!
</button>
<modal
v-show="isModalVisible"
@close="closeModal"
/>
</div>
</template>
P.S. Эта статья — перевод этой забугорной. В комментарии ниже я объяснил, как так получилось.
P.S.S Сделал перевод статьи человечным.
Комментарии (13)
interprise
16.02.2018 18:08Все отлично в этих примера, кроме одного, скрол остается глобальным и это напрягает в мобильных браузерах. Где модальное окно должно быть на весь экран. Неужели единственный выход вешать на body класс?
vuNemesis Автор
16.02.2018 21:37Всем вечер добрый! Я заранее извиняюсь. Вчера только зарегистрировался здесь и сидел смотрел что и как с публикациями. В общем это статья — тупо перевод. Я и ссылку не вставил на оригинал. В общем, просто хотел проверить, как быстро проходят модерацию новые статьи. Оказалось, что быстро. Я вообще расчитывал, что такая не пройдет, но хотя бы увижу срок проверки. Надеюсь, что меня за это не забанят. Даже если ее удалят — я не огорчусь, потому что завтра я скину свою первую статью. Личную. Чужих больше не будет. Буду писать по тематике JS, Vue.js, Webpack, Node.js и т.п. Есть много о чем рассказать. Поэтому сильно не огорчайтесь на меня — исправлюсь!
Mingun
17.02.2018 09:45Это не повод размещать некачественный перевод или вообще любой другой контент. А исправиться сейчас можно только одним способом — сесть, вычитать и поправить всё это вот «моя твоя шатать уууу», что написано в статье.
Electrohedgehog
17.02.2018 12:58Обратите внимание 10^100 переводить переводить лучше чем вам!
Минус в карму за то, что даже мануалы прочитать не способен.
vuNemesis Автор
17.02.2018 16:27+1Прошел по всей статье — сделал человечный перевод.
Electrohedgehog
19.02.2018 10:04Обратите внимание, как модальное окно резко открывается?
Теперь наш компонент модального окна открывается и закрывается гладко!
Эталон перевода. Нет, ну правда.
alek0585
18.02.2018 18:50В итоге это модальное окно постоянно присутствует в DOM.
Вот если бы оно динамически создавалось при необходимости и с такой же анимацией – тогда другое дело. А так… Туфта.vuNemesis Автор
18.02.2018 21:07Да. Это вообще самый простой способ. Да и распространенный в общем. Сейчас дописываю статью-туториал (свой), тоже про создание компонента модального окна, но с наворотами. Завтра выложу ее здесь.
Desprit
Вроде уже 2018-ый на дворе, а переводы в стиле Prompt все еще актуальны.