Search code examples
htmlcsssvg

Using visibility and :hover to toggle nav menu ends up clicking nav item on touch device


I have an SVG group where upon hovering it displays a new group with the same path but with different text (i.e. displaying a "dropdown" nav menu). On desktop with a mouse it's fine but when using a touch device any tap will click the link (that should be visibility: hidden) straight away. Here's an example I've pulled from the SVG (so ignore the seemingly weird placement):

<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 306.12 409.2" preserveAspectRatio="none">
  <defs>
    <style type="text/css">
      @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500;600');

      .font-md {
        font-family: 'Poppins';
        font-size: 10px;
        font-weight: 500;
      }

      :root {
        --yellow: #f6b034;
      }

      .training {
        fill: var(--yellow);
      }

      .sub-menu {
        opacity: 0;
        visibility: hidden;
        height: 0;
      }

      .sub-menu > text {
        opacity: 0;        
        transition: opacity 0.5s ease-in-out;
      }

      .has-dropdown:hover + .sub-menu,
      .has-dropdown:hover + .sub-menu > text {
        opacity: 1;
        visibility: visible;
        height: auto;
        pointer-events: auto;
        transform: unset;
      }

      .has-dropdown:hover {
        opacity: 0;
        visibility: hidden;
        height: 0;
      }

      .sub-menu:hover {
        opacity: 1;
        visibility: visible;
        height: auto;
        pointer-events: auto;
      }

      .sub-menu:hover > text {
        opacity: 1;
      }
    </style>
  </defs>
  
  <g id="training" class="has-dropdown">
    <path class="training" d="m0,256c0-27.61,22.39-50,50-50,27.61,0,50,22.38,50,49.99,0-.21.02-.42.02-.63v29.39c0,11.06,8.45,20.15,19.25,21.15.02,0,.04.09,0,.09-.04,0-62.38,0-69.27,0,0,0,0,0,0,0s0,0,0,0h-.62c.1,0,.21,0,.31,0C22.22,305.82,0,283.51,0,256Z"/>
    <text style="fill: var(--brown)" class="font-md" transform="translate(31 285)">Training</text>
  </g>
  <g id="training-sub-menu" class="sub-menu">
    <path class="training" d="m0,256c0-27.61,22.39-50,50-50,27.61,0,50,22.38,50,49.99,0-.21.02-.42.02-.63v29.39c0,11.06,8.45,20.15,19.25,21.15.02,0,.04.09,0,.09-.04,0-62.38,0-69.27,0,0,0,0,0,0,0s0,0,0,0h-.62c.1,0,.21,0,.31,0C22.22,305.82,0,283.51,0,256Z"/>
    <text style="fill: var(--brown)" class="font-md sub-menu-item" transform="translate(31 285)">
      <a href="https://google.com" target="_parent">Google</a>  
    </text>
    <text style="fill: var(--brown)" class="font-md sub-menu-item" transform="translate(31 265)">
      <a href="https://google.com" target="_parent">Google</a>  
    </text>
    <text style="fill: var(--brown)" class="font-md sub-menu-item" transform="translate(31 245)">
      <a href="https://google.com" target="_parent">Google</a>  
    </text>
  </g>
</svg>

I thought using visibility: hidden would have made the link unclickable, but I suspect the click event behaviour from :hover is still firing even after the tap? I've tried other CSS element selectors without luck.


Solution

  • As I'm doing all of this inside an SVG, I get access to SVG-only pointer-events: https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events

    The one that solved my problem was visiblePainted, which seems to constrain pointer events to when it's only visible, so this was the fix:

    .has-dropdown:hover + .sub-menu,
    .has-dropdown:hover + .sub-menu > text {
      opacity: 1;
      visibility: visible;
      height: auto;
      pointer-events: visiblePainted;
      transform: unset;
    }