Search code examples
vue.jshovertouchvuejs3touch-event

How to detect hover state on touchscreen device - Vue 3


In my case in desktop mode user can hover over the element to reveal the menu. User can also click the element that makes a request to the server.

In touchscreen mode, I would like the click to be revealing the menu instead of making a request.

It seems like impossible task to achieve unless I start going outside of vue and changing css directly from DOM level.

<script setup>
import { ref } from "vue";

const text = ref("whatever");
const isEditing = ref(false);
function sendToServer() {
  console.log("Sending request of ", text.value, " to server.")
  isEditing.value = false;
}
</script>

<template>
  <div class="wrapper">
    <span v-show="!isEditing" @click="sendToServer" @touchstart.prevent> {{ text }}</span>
    <input v-show="isEditing" v-model="text">
    <span @click="sendToServer" class="icon">➡️</span>
    <span @click="isEditing = !isEditing" class="icon">✏️</span>
  </div>
</template>

<style>
  .icon {
    opacity: 0;
  }
  
  .wrapper:hover .icon {
    opacity: 1;
  }
  
  .wrapper > span {
    cursor: pointer;
  }
</style>

Vue SFC playground link


Solution

  • "It seems like impossible task to achieve unless I start going outside of vue and changing css directly from DOM level."

    I would say it is impossible to achieve with CSS-only, but Vue (IMHO) seems cable and well suited to handle that.

    Instead of using css :hover you will need to manage the visibility using state (isSelected in example)

    then use @touchstart to toggle visibility - this will only be available to touch devices and mouse events @mouseover to show and @mouseout to hide.

    there may be some more finessing to handle some edge cases, but this is how I'd implement it. For example, you may need a global touch event listener to hide when user clicks outside of the text.

    <script setup>
      import { ref } from "vue";
    
      const text = ref("whatever");
      const isEditing = ref(false);
      const isSelected = ref(false);
      function toggleIsSelected(){
            isSelected.value = !isSelected.value
      }
      function unselect(){
        isSelected.value = false;
      }
      function sendToServer() {
        console.log("Sending request of ", text.value, " to server.")
        isEditing.value = false;
      }
    </script>
    
    <template>
      <div class="wrapper">
        <span v-show="!isEditing"
              @click="sendToServer"
              @touchstart=toggleIsSelected
              @mouseover=toggleIsSelected
              @mouseout=unselect
        > {{ text }}</span>
        <input v-show="isEditing" v-model="text">
        <span v-show=isSelected @click="sendToServer" class="icon">➡️</span>
        <span v-show=isSelected @click="isEditing = !isEditing" class="icon">✏️</span>
      </div>
    </template>
    
    <style>
      .wrapper > span {
        cursor: pointer;
      }
    </style>