Listening to "esc" key event on div component in Vue.js

JavascriptEvent HandlingVuejs2

Javascript Problem Overview


I wish to listen to an 'esc' key event in order to call a method in a Vue component. The docs shows this example:

<input v-on:keyup.enter="submit">

but i'm using a <div></div>, and need to catch the event from outside. However, I wish NOT to overload global handlers or anything like that.

Any suggestions?

Javascript Solutions


Solution 1 - Javascript

For anyone who wanders here from google, in Vue 2...

<div @keydown.esc="something_in_your_methods"></div>

Solution 2 - Javascript

The secret for making keydown events work on divs and other non-focusable elements is to add a tabindex attribute:

<div tabindex="0"
    @keydown.left="previousImage"
    @keydown.right="nextImage" />

Now the div has become a focusable element and the key events will be triggered.

Here is more info on focusable elements and tabindex

Solution 3 - Javascript

What I did was go for a mixin.

The mixin in a file called close.js

export default {
    created() {
        let that = this;

        document.addEventListener('keyup', function (evt) {
            if (evt.keyCode === 27) {
                that.close();
            }
        });
    },
};

Import and use it in the desired component

import closeMixin from './../../mixins/close.js';

export default {
    mixins: [closeMixin],
    props: [],
    computed: {}
    methods: {
        close(){
            // closing logic
        }
    }
}

Solution 4 - Javascript

3 things to make sure of on the main element:

  • It has a tabindex
  • It or a descendant is focused
  • It is listening for the event

Here is how I usually manage my modals:

<div ref="modal" @keyup.esc="close" tabindex="-1">
   <!-- Modal content -->
</div>
mounted() {
    this.$refs.modal.focus();
}

Solution 5 - Javascript

You can't. Key events are dispatched from the body tag and Vue can't be mounted to the <body> tag.

Sourced from ]

You'll have to set up your own event listener.

(image source & more info at When VueJS Can't Help You)

Solution 6 - Javascript

In my case, I created a directive, v-esc.ts. (※ This is Vue3 directive writing way)

import { Directive } from 'vue'
const directive: Directive = {
  beforeMount(el, binding) {
    el._keydownCallback = (event) => {
        if (event.key === 'Escape') {
            binding.value()
      }
    }
    document.addEventListener('keydown', el._keydownCallback)
  },
  unmounted(el, binding) {
    document.removeEventListener('keydown', el._keydownCallback)
    delete el._keydownCallback
  }
}
export const esc = { esc: directive }

Then I can use it in any component like this. (NOTE: you must pass a function param to v-esc, because the param executed as binding.value() in the directive)

<template>
  <img
    @click.prevent="close"
    v-esc="close"
    src="@/assets/icons/close.svg"
  />
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { esc } from '@/common/directives/v-esc'
export default defineComponent({
  name: 'nitCloseButton',
  ...
  methods: {
    close() {
      this.$emit('close')
    }
  },
  directives: {
    ...esc
  }
})
</script>

P.S One month after, I also need arrow left and arrow right keys. So, I've made this directive more general like this.

import { Directive } from 'vue'
const directive: Directive = {
  beforeMount(el, binding) {
    el._keydownCallback = event => {
      console.log('keydown', event.key)
      if (event.key === binding.arg) {
        binding.value()
      }
    }
    document.addEventListener('keydown', el._keydownCallback)
  },
  unmounted(el, binding) {
    document.removeEventListener('keydown', el._keydownCallback)
    delete el._keydownCallback
  }
}
export const keydown = { keydown: directive }

You can detect any key's keydown by passing keyname as binding.args (v-keydown:{keyName} like below)

<button
  v-keydown:ArrowLeft="moveToPreviousPage"
  class="controller-button lo-center"
  @click="moveToPreviousPage"
>
  <arrow-icon :rotation="180" />
</button>
<button
  v-keydown:ArrowRight="moveToNextPage"
  class="controller-button lo-center"
  @click="moveToNextPage"

export default defineComponent({
  name: 'componentName',
  directives: {
    ...keydown
  }
...
})

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChenView Question on Stackoverflow
Solution 1 - JavascriptlaaksomView Answer on Stackoverflow
Solution 2 - JavascriptBassMHLView Answer on Stackoverflow
Solution 3 - JavascriptFrisbetarianView Answer on Stackoverflow
Solution 4 - JavascriptPierre de LESPINAYView Answer on Stackoverflow
Solution 5 - JavascriptLaundroMatView Answer on Stackoverflow
Solution 6 - JavascriptkazuwombatView Answer on Stackoverflow