vuejs: @keyup.esc on div element is not working

前端 未结 6 626
借酒劲吻你
借酒劲吻你 2021-02-12 12:57

I\'m expecting that \'close\' event is fired when I\'m clicking ESC button being on \"shadow-modal\" div, but it\'s not happening

vue 2.5.13, any ideas why?

相关标签:
6条回答
  • 2021-02-12 13:27

    If you need to close a modal window, you must observe several conditions.

    1. In the tag of the modal window:
    <div class="modal-content" ref="modal" tabindex="0" @keyup.esc="$emit('close')">
    
    1. In the lifecycle hook mounted():
    mounted() {
       this.$refs.modal.focus()
    }
    
    
    0 讨论(0)
  • 2021-02-12 13:42

    The Key events cannot be generated from div or other elements. In order to get key events from div you need to use <Input type="text"></Input> inside the div element.

    0 讨论(0)
  • 2021-02-12 13:44

    While it's not input element that you're trying to bind the keyborad events, they will not work unless you define a tabindex:

    <div class="shadow-modal" @keyup.esc="$emit('close')" tabindex="0">
    

    Here's a reference: https://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/SCR29.html

    0 讨论(0)
  • 2021-02-12 13:46

    (Apart from the 2 earlier answers.)

    You don't have handle all event with Vue.

    Here another way

    export default {
      created() {
        document.onkeydown = evt => {
          evt = evt || window.event;
          if (evt.keyCode == 27) {
            this.$emit("close");
          }
        };
      }
    };
    
    0 讨论(0)
  • 2021-02-12 13:46

    My Alternative Implementation

    <template>
      <div class="shadow-modal"
         @keyup.esc="$emit('close')">
        <transition name="modal">
          <div class="modal-mask">
            <div class="modal-wrapper">
              <div class="modal-container">
                <div class="modal-header">
                  <slot name="header">
                    default header
                  </slot>
                </div>
                <div class="modal-body">
                  <slot name="body">
                    default body
                  </slot>
                </div>
                <div class="modal-footer">
                  <slot name="footer">
                    <a href="#"
                       class="btn btn--diagonal btn--blue"
                       @click="$emit('close')">Cancel</a>
                  </slot>
                </div>
              </div>
            </div>
          </div>
        </transition>
      </div>
    </template>
    
    <script>
    export default {
      beforeMount() {
        window.addEventListener('keyup', this.onEscapeKeyUp);
      },
      beforeDestroy () {
        window.removeEventListener('keyup', this.onEscapeKeyUp)
      },
      methods: {
        onEscapeKeyUp (event) {
          if (event.which === 27) {
            this.$emit('close');
          }
        },
      },
    };
    </script>
    
    0 讨论(0)
  • 2021-02-12 13:50

    While DmitrySemenov solution works for me it's not the best solution when you have multiple modals on the page. I tried it and I discovered it fires close event for every modal.

    I think the best way to do it is registering a "keyup" event when the modal is shown and deregistering it after hide. It gives you an advantage because the event is registered only when it's needed. To do it you need to add a watcher for "show" property:

      props: {
        show: {
          type: Boolean,
          default: false
        }
      },
      watch: {
        show() {   
          if (this.show === false) {
            window.removeEventListener("keyup", this.onEscapeKeyUp);
          } else {
            window.addEventListener("keyup", this.onEscapeKeyUp);
          }
        }
      },
      methods: {
        onEscapeKeyUp(event) {
          if (event.which === 27) {
            this.$emit("close");
          }
        }
      }
    

    The modal should have v-if="show" that controls modal's visibility:

    <div class="modal-mask" v-if="show" @click="$emit('close');">
    

    Whole solution code (you can see it on codesandbox.io):

    Modal.vue

    <template>
      <transition name="modal">
        <div class="modal-mask" v-if="show" @click="$emit('close');">
          <div class="modal-wrapper" @click.stop>
            <div class="modal-container">
              <div class="modal-header"><slot name="header"></slot></div>
              <div class="modal-body"><slot name="body"></slot></div>
              <div class="modal-footer">
                <slot name="footer">
                  <div class="buttons">
                    <a class="button" href="#" @click.prevent="$emit('close');">
                      OK
                    </a>
                  </div>
                </slot>
              </div>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script>
    export default {
      name: "Modal",
      props: {
        show: {
          type: Boolean,
          default: false
        }
      },
      watch: {
        show() {
          const body = document.querySelector("body");
    
          if (this.show === false && body.style.overflow === "hidden") {
            body.style.overflow = "";
            window.removeEventListener("keyup", this.onEscapeKeyUp);
          } else {
            body.style.overflow = "hidden";
            window.addEventListener("keyup", this.onEscapeKeyUp);
          }
        }
      },
      methods: {
        onEscapeKeyUp(event) {
          if (event.which === 27) {
            console.log("close event");
            this.$emit("close");
          }
        }
      }
    };
    </script>
    
    <style lang="scss" scoped>
    .modal-mask {
      position: fixed;
      z-index: 1100;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.5);
      overflow: auto;
      transition: opacity 0.3s ease;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .modal-wrapper {
      max-width: 980px;
      width: 100%;
    }
    
    .modal-container {
      padding: 1.5em 2em;
      background-color: white;
      border-radius: 2px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
      transition: all 0.3s ease;
    }
    
    .wrapper {
      max-width: 980px;
    }
    
    .modal-body {
      margin: 1em 0;
    }
    
    .modal-enter {
      opacity: 0;
    }
    
    .modal-leave-active {
      opacity: 0;
    }
    
    .modal-enter .modal-container,
    .modal-leave-active .modal-container {
      -webkit-transform: scale(1.1);
      transform: scale(1.1);
    }
    </style>
    

    App.vue

    <template>
      <div id="app">
        <button @click="show1 = true;">Show first modal</button>
        <button @click="show2 = true;">Show second modal</button>
    
        <Modal :show="show1" @close="show1 = false;">
          <div slot="header"><h1>First Modal</h1></div>
          <div slot="body">
            Lorem ipsum dolor sit amet consectetur, adipisicing elit. Rem beatae
            repellat dolores deleniti illum voluptatem facilis neque ut placeat,
            eius iusto tempore! Totam omnis non tempore perferendis expedita numquam
            neque!
          </div>
        </Modal>
    
        <Modal :show="show2" @close="show2 = false;">
          <div slot="header"><h1>Second Modal</h1></div>
          <div slot="body">
            Lorem ipsum dolor sit amet consectetur, adipisicing elit. Rem beatae
            repellat dolores deleniti illum voluptatem facilis neque ut placeat,
            eius iusto tempore! Totam omnis non tempore perferendis expedita numquam
            neque!
          </div>
        </Modal>
      </div>
    </template>
    
    <script>
    import Modal from "./components/Modal";
    
    export default {
      name: "App",
      components: {
        Modal
      },
      data() {
        return {
          show1: false,
          show2: false
        };
      }
    };
    </script>
    
    0 讨论(0)
提交回复
热议问题