Pull to refresh

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

Reading time 6 min
Views 31K
В этой статье вы узнаете, как с 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 Сделал перевод статьи человечным.
Tags:
Hubs:
+4
Comments 13
Comments Comments 13

Articles