How to highlight and get element of html element when mouse hover in vue

This is a simple html for the navigation header which I use vue to show it up with v-html.

this is the demo - https://shuffle.dev/preview?project=12e60d2546392911632c3b5d1576766424f55da0&page=index.html&screen=main

<section class="text-gray-700 font-heading font-medium relative bg-gray-50 bg-opacity-50">
  <nav class="flex justify-between px-6 lg:px-12 py-8">
      .......
      .......
      <div class="mt-auto px-10">
        <button class="py-3 px-5 mt-6 w-full font-body font-bold uppercase tracking-wide text-sm border-2 border-gray-200 hover:border-gray-300 border-opacity-50 rounded-full">
          <span class="block mt-px">New project</span>
        </button>
      </div>
    </nav>
  </div>
</section>

what I want to do is to have the mouse hover on each html element such as <svg>, <button>, <div>, highlight the element and output the html element in the console.

the wishful result will be similar to the image below, a highlight on the 'New Tools' text.

enter image description here

One simple solution is to give all the elements a class and add the mouseover listener, however it raised a issue that for a complex html element with parent and child element, when the mouse is hovering over the child element, both parent and child element will be effected, how to only highlight the child element while avoid highlighting the parent element is what I am trying to figure out.


You can add eventListener on the buttons. With CSS you can style only the span child element. The Event Handler can console what ever you want.

<!-- Use preprocessors via the lang attribute! e.g. <template lang="pug"> -->
<template>
  <div id="app">
    <section class="text-gray-700 font-heading font-medium relative bg-gray-50 bg-opacity-50">
      <nav class="flex justify-between px-6 lg:px-12 py-8">

          <div class="mt-auto px-10">
            <button class="py-3 px-5 mt-6 w-full font-body font-bold uppercase tracking-wide text-sm border-2 border-gray-200 hover:border-gray-300 border-opacity-50 rounded-full">
              <span class="highlight block mt-px">New project</span>
            </button>
            
            <button class="py-3 px-5 mt-6 w-full font-body font-bold uppercase tracking-wide text-sm border-2 border-gray-200 hover:border-gray-300 border-opacity-50 rounded-full">
              <span class="block mt-px">New tools</span>
            </button>            
          </div>
        </nav>
      
    </section>

  </div>
</template>

<script>
export default {
  data() {
    return {      
    };
  },
  methods: {

  },
  mounted() {
    //const btns = document.querySelectorAll('button');
    const btns = document.querySelectorAll('button');
    btns.forEach(b => {
      b.addEventListener('mouseover', (e) => {
        let span = e.target.children[0];        
        console.log('child Element from hovering button', span);
      })  
    })
    
    console.log(btns)
  }
};
</script>

<!-- Use preprocessors via the lang attribute! e.g. <style lang="scss"> -->
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

a,
button {
  color: #4fc08d;
}
button:hover .highlight {
    background: red;
}

button {
  background: none;
  border: solid 1px;
  border-radius: 2em;
  font: inherit;
  padding: 0.75em 2em;
}
</style>

Solution:

Use the mounted hook of Vue component to fetch the dynamic HTML subtree then add 'Click' event handler for eligible node elements of the subtree to trigger element selection.

Additionally use mouseover & mouseout event Handler to toggle interactive styling

Step 1: Setup the project with a vue component using v-html

<div id="app">
  Create designs using Uinel
  <div id="interactivecanvas" v-html="dynamicHTML"></div>
  <div id="selectedElement">Selection: {{selectedElement.tagName}}</div>
</div>

Step 2: Add CSS styles to indicate a Selectable Element

.selectable{ 
  box-shadow: 0 0 0 2px green;
}

Step 3: Add a mounted hook to fetch dynamic HTML Sub-tree

 mounted(){
  
    // List of selectable Elements. Uses css selector syntax
    let selectableElements = ['li', 'span', 'button']
    selectableElements[0] = '#interactivecanvas '+selectableElements[0]

    let canvasContents = document.querySelectorAll(selectableElements.join(',
     #interactivecanvas '))
  .....

Step 4: Iterate through Sub-tree Elements and add Event Handlers for Selection

mounted(){
...
 for(let i=0; i<canvasContents.length; i++){
...
  canvasContents[i].addEventListener('click',e=>{
    e.stopPropagation();
    this.selectElement(e)
  })
...
}

Step 5: Optional! you can add mouseover & mouseout event Handler to toggle interactive styling.

Check the example below. View in full page.

new Vue({
  el: '#app',
  data() {
    return {
      selectedElement: {},
      dynamicHTML: `
        <div class="header">
          <nav class="nav">
            <ul id="nav">
              <li>Home</li>
              <li>Contact Us</li>
              <li>About Us</li>
            </ul>
            <div id="card">
              <div id="usercard">
                <span>Sona</span>
                <span>&#x25BC;</span>
              </div>
              <button>New Project</button>
            </div>
          <nav>
        </div>
      `
    }
  },
  mounted(){
  
    // List of selectable Elements. Uses css selector syntax
    let selectableElements = ['li', 'span', 'button']
    selectableElements[0] = '#interactivecanvas '+selectableElements[0]

    let canvasContents = document.querySelectorAll(selectableElements.join(', #interactivecanvas '))
    for(let i=0; i<canvasContents.length; i++){
    
      // Add mouseover & mouseout event Handler to toggle styles
      canvasContents[i].addEventListener('mouseover',(e)=>{
        e.stopPropagation();
        e.currentTarget.classList.add("selectable")
      })
      canvasContents[i].addEventListener('mouseout',(e)=>{
        e.stopPropagation();
        e.currentTarget.classList.remove("selectable")
      })
      
      // Event to fire Selection action. Use 'hover' or 'click'
      canvasContents[i].addEventListener('click',e=>{
        e.stopPropagation();
        this.selectElement(e)
      })
    }
  },
  methods:{
    selectElement(e){
      console.log('Selected '+e.currentTarget)
      this.selectedElement = e.currentTarget
    }
  }
})
* {
  box-sizing: border-box;
}

body {
  font-family: 'Verdana'
}

ul {
  list-style: none;
}

.header {
  background: rgba(245,246,247);
  padding: 20px;
}

.nav {
  display: flex;
}

.nav > ul > li {
  display: inline;
  margin: 20px;
}

.nav button {
  border: 2px solid gray;
  border-radius: 9999px;
  padding: 12px 20px;
}

.nav #card {
  display: flex;
  align-items: center;
}

#usercard {
  margin: 20px;
  padding: 10px;
}

.selectable{ 
  box-shadow: 0 0 0 2px green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  Create designs using Uinel
  <div id="selectedElement">Selection: {{selectedElement.tagName}}</div>
  <div id="interactivecanvas" v-html="dynamicHTML"></div>
</div>