Создание модального компонента с помощью Vue.js

В этой статье вы узнаете, как с Vue.js создать переиспользуемый компонент модального окна с использованием анимированных переходов и слотов.

Определение структуры шаблона


Начнем с определения нашего шаблона. Нам понадобится 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 Сделал перевод статьи человечным.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 13

    +5
    Вроде уже 2018-ый на дворе, а переводы в стиле Prompt все еще актуальны.
      0
      А чем пример из доков не устраивает? Еще лучше будет.
        +2
        Там нет таких перлов: «Обратите внимание, как модально открывается внезапно? Мы можем заставить его задушить, используя переход.»
        0
        Все отлично в этих примера, кроме одного, скрол остается глобальным и это напрягает в мобильных браузерах. Где модальное окно должно быть на весь экран. Неужели единственный выход вешать на body класс?
          0

          АФФФФФФФФФТОР где ссылка на Pastebin ?????

            –2
            Всем вечер добрый! Я заранее извиняюсь. Вчера только зарегистрировался здесь и сидел смотрел что и как с публикациями. В общем это статья — тупо перевод. Я и ссылку не вставил на оригинал. В общем, просто хотел проверить, как быстро проходят модерацию новые статьи. Оказалось, что быстро. Я вообще расчитывал, что такая не пройдет, но хотя бы увижу срок проверки. Надеюсь, что меня за это не забанят. Даже если ее удалят — я не огорчусь, потому что завтра я скину свою первую статью. Личную. Чужих больше не будет. Буду писать по тематике JS, Vue.js, Webpack, Node.js и т.п. Есть много о чем рассказать. Поэтому сильно не огорчайтесь на меня — исправлюсь!
              +1

              Это не повод размещать некачественный перевод или вообще любой другой контент. А исправиться сейчас можно только одним способом — сесть, вычитать и поправить всё это вот «моя твоя шатать уууу», что написано в статье.

                0
                Обратите внимание 10^100 переводить переводить лучше чем вам!
                Минус в карму за то, что даже мануалы прочитать не способен.
                  0

                  Осмелюсь предположить что русский для вас далеко не родной язык.

                  0
                  Прошел по всей статье — сделал человечный перевод.
                    +1
                    Обратите внимание, как модальное окно резко открывается?
                    Теперь наш компонент модального окна открывается и закрывается гладко!

                    Эталон перевода. Нет, ну правда.
                    0
                    В итоге это модальное окно постоянно присутствует в DOM.
                    Вот если бы оно динамически создавалось при необходимости и с такой же анимацией – тогда другое дело. А так… Туфта.
                      0
                      Да. Это вообще самый простой способ. Да и распространенный в общем. Сейчас дописываю статью-туториал (свой), тоже про создание компонента модального окна, но с наворотами. Завтра выложу ее здесь.

                    Only users with full accounts can post comments. Log in, please.