5

Here is my first attempt at keeping track of whether the control key (ctrl) is being pressed while the mouse is over a <section>:

<template>
  <section v-on:mouseover="controlKeyPressed = $event.ctrl">...</section>
</template>

That seems like it'd be extremely bad for performance, because every time the user moves the mouse, this code needs to fire. Ideally, there would be code that runs only when the ctrl key is pressed/released.

tony19
  • 125,647
  • 18
  • 229
  • 307
Adam Zerner
  • 17,797
  • 15
  • 90
  • 156
  • 1
    Are you trying to check the CTRL key only during `mouseover`? Or just generally check whether the CTRL key is down (regardless of `mouseover`)? – tony19 Mar 02 '18 at 23:14
  • @tony19 I only need to know whether the CTRL key is being pressed when the mouse is over the component. – Adam Zerner Mar 02 '18 at 23:44

3 Answers3

4

You can listen for keyboard events specifically. From the example code in the vue.js documentation:

<!-- only call `vm.submit()` when the `keyCode` is 13 -->
<input v-on:keyup.13="submit">

So you would listen for both keyup and keydown events for the ctrl key, and store the current state in your handler function.

tony19
  • 125,647
  • 18
  • 229
  • 307
fennel
  • 371
  • 2
  • 11
  • Seems like a good solution, although, if Vue compiles that to `v-on:keyup="submit" function submit(e) { if (e.key === 13) { // continue to execute }}` internally, then it would be the same solution as the one I initially mentioned in my OP. Do you know if Vue does that internally? – Adam Zerner Mar 02 '18 at 23:43
3

The idiomatic way to do this in Vue is using an event handler with a key modifier:

<section @mouseover.ctrl="handleMouseEvent">

new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!',
  },
  methods: {
    log(msg) {
      console.log(`${+new Date()} scrolling with CTRL pressed`)
    }
  }
})
.dragarea {
  width: 200px;
  height: 200px;
  background: lightblue;
}
<script src="https://unpkg.com/vue@2.5.13"></script>

<div id="app">
  <section @mouseover.ctrl="log('here')" class="dragarea">
    <div>Move mouse here while holding down <kbd>CTRL</kbd></div>
  </section>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
3

That seems like it'd be extremely bad for performance, because every time the user moves the mouse, this code needs to fire.

Before entering the mouseover details, even if that event runs often, you shouldn't have such a performance hit. Code like this runs every little second by the browser for a number of reasons (and libs/plugins/extensions/trackers). Unless you don't do any intense calculation on the handler, there should be no problem.

Ultimately, though, this is the kind of thing you shouldn't worry until you have numbers.

Ideally, there would be code that runs only when the ctrl key is pressed/released.

There are the mouseenter and mouseleave events. But it doesn't seem to be what you are looking for. See how they behave:

new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!',
  },
  methods: {
    imIn() {
      console.log('Mouse enters with CTRL')
    },
    imOut() {
      console.log('Mouse leaves with CTRL')
    }
  }
})
section { background: blue; height: 30px; width: 100px; }
<script src="https://unpkg.com/vue@2.5.13/dist/vue.min.js"></script>

<div id="app">
  <section @mouseenter.ctrl="imIn" @mouseleave.ctrl="imOut"></section>
</div>

Actually, mouseover doesn't run every time

MDN says:

The mouseover event is fired when a pointing device is moved onto the element that has the listener attached or onto one of its children.

So it is not ran every time. Only when the mouse:

  • moves onto the element; or
  • moves onto a child of the element

Have a look at the boxes below.

new Vue({
  el: '#app',
  methods: {
    mouseOverElementWithChildren() {
      console.log('Mouse Over Element with Children', new Date())
    },
    mouseOverElementWithoutChildren() {
      console.log('Mouse Over Element WITHOUT Children', new Date())
    }
  }
})
.nonempty { border: 1px solid red; }
.nonempty * { border: 1px solid blue; width: 200px; }

.empty { border: 1px solid green; height: 40px; }
* { font-size: small; font-family: verdana }
<script src="https://unpkg.com/vue@2.5.13/dist/vue.min.js"></script>

<div id="app">
  Hold CTRL and move the mouse over the elements below.<br><br>
  
  This one has many children. Notice the MOUSEOVER only triggers when you move over a line (that is, enters/leaves the element or one of its children).
  <section @mouseover.ctrl="mouseOverElementWithChildren" class="nonempty">
    <ul>
      <li>a a a</li>
      <li>b b b</li>
    </ul>
  </section>

  <br> This one has no children. Notice the MOUSEOVER only triggers when you ENTER/LEAVE the element.
  <section @mouseover.ctrl="mouseOverElementWithoutChildren" class="empty"></section>
  <br><br><br><br><br><br><br>
</div>

A workaround for elements with many children

So, if you don't want your mouseover code to run every time, either create an empty element (which is probably not what you want), or create an overlay on top of that element and hook the mouseover event to that overlay.

That would require some CSS work, but is doable:

new Vue({
  el: '#app',
  methods: {
    mouseOverElementWithChildren() {
      console.log('Mouse Over BLUE', new Date())
    },
    mouseOverElementWithoutChildren() {
      console.log('Mouse Over GREEN', new Date())
    }
  }
})
.nonempty { border: 1px solid red; height: 70% }
.nonempty * { border: 1px solid blue; }
.empty { border: 1px solid green; width: 80%; height: 80%; }
/* below is from https://stackoverflow.com/a/2941203/1850609 */
#app { width: 100px; height: 100px; position: relative; border: 1px solid orange; }
.nonempty, .empty { position: absolute; top: 0; left: 0; }
.empty { z-index: 10; }
* { font-size: x-small; font-family: verdana }
<script src="https://unpkg.com/vue@2.5.13/dist/vue.min.js"></script>

Hold CTRL and move the mouse over the elements below.<br>Whole #app is orange border. Green is on top of the blue. Notice the blue does not trigger mouseOver events, and green only triggers once.
<div id="app">
  
  
  <section @mouseover.ctrl="mouseOverElementWithChildren" class="nonempty">
    <ul>
      <li>a a a</li>
      <li>b b b</li>
    </ul>
  </section>

  <section @mouseover.ctrl="mouseOverElementWithoutChildren" class="empty"></section>
  <br><br><br><br><br><br><br>
</div>
acdcjunior
  • 132,397
  • 37
  • 331
  • 304
  • This particular handler doesn't do anything intense, but I do have other handlers that are doing intense things. In my case, running simulations. So while the simulations are running, I fear that this code will execute many times per second, thus slowing down the performance of my simulations. – Adam Zerner Mar 03 '18 at 00:14
  • Yeah, the `mouseenter/leave` approach wouldn't work because it wouldn't detect CTRL presses while the mouse remains inside the component. – Adam Zerner Mar 03 '18 at 00:15
  • An "overlayed" `
    ` with high `z-index` would run the code only once. I will try to show it in the answer. Gimme a sec.
    – acdcjunior Mar 03 '18 at 00:20
  • I'm done. Check it out. May give you some insights. Now, though, I'm thinking you cant escape the `mousemove` event, because if the user holds CTRL while inside the element, that's the only way to pick it up. – acdcjunior Mar 03 '18 at 00:52