最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Make tooltip enterableinteractive - Stack Overflow

programmeradmin3浏览0评论

I have a tooltip component that uses Javascript to attach mouseover,mouseout event listeners to an element and on mouseover instantiates a tooltip component. I would like to make my tooltips interactive/enterable, so that if the user moves the mouse toward/into the tooltip itself when showing, the tooltip stays visible and allows mouse interaction.

The trouble is that moving the mouse toward the tooltip triggers the mouseout on the parent element, closing the tooltip. How does one prevent this? Is there a way to expand the element's boundaries invisibly to include the tooltip, so that mouseout is only fired if the mouse is moved away from both parent element and the tooltip?

Here's a simplified version of my code (I' m using Svelte as my framework, but the question should be valid independent of framework):

<!-- component that wants to use a tooltip -->

  <span
      use:attachTooltip={{
        component: EntityCardTooltip      
      }}
  >My visible text</span>
  export const attachTooltip = (node,options) => {
    if (options) {

      let _component

      node.addEventListener('mouseover',addTooltip)
      node.addEventListener('mouseout',removeTooltip)

      function addTooltip(e) {
        const {component, ...otherOpts} = options

        _component = new component({
          target: node,
          props: {
            mouseEvent: e,
            ...otherOpts
          }
        })
      }

      function removeTooltip(e) {
        _component.$destroy()
      }

      return {
        destroy() {
          node.removeEventListener('mouseover',addTooltip)
          node.removeEventListener('mouseout',removeTooltip)
        }
      }
    }
  }

<!-- EntityCardTooltip --->

  <Tooltip {mouseEvent}>
    <div class="tipBody" slot="Content">
      <!--- tooltip content here --->
    </div>
  </Tooltip>

<!-- Tooltip --->

  <div class="tip" use:adjustTooltipOnMouseOver >
    {#if $$slots.Content}
      <slot name="Content" />
    {:else if innerHTML}
      {@html innerHTML}
    {/if}
  </div>

<style>
  @keyframes fadeInFromNone {
    0% {
        display: block;
        opacity: 0;
    }

    1% {
        display: block;
        opacity: 0;
    }

    100% {
        display: block;
        opacity: 1;
    }
  }

  .tip, .tip:before {
    pointer-events: none;
    box-sizing: border-box;
    display: block;
    opacity: 1;
  }

  .tip:before {
    content: "";
    position: absolute;
    width: 2rem;
    height: 2rem;
    z-index: 13;
  }

  .tip {
    animation: fadeInFromNone 0.05s linear 0.05s;
    animation-fill-mode: both;
    position: fixed;
    color: black;
    min-width: 3rem;
    white-space: nowrap;
    display: block;
    text-overflow: ellipsis;
    white-space: pre;
    z-index: 12;
  }
</style>

There is more CSS not shown here, used to conditionally position by transforms using CSS vars set by adjustTooltipOnMouseOver (determines direction, prevents overflow, etc.).

I have a tooltip component that uses Javascript to attach mouseover,mouseout event listeners to an element and on mouseover instantiates a tooltip component. I would like to make my tooltips interactive/enterable, so that if the user moves the mouse toward/into the tooltip itself when showing, the tooltip stays visible and allows mouse interaction.

The trouble is that moving the mouse toward the tooltip triggers the mouseout on the parent element, closing the tooltip. How does one prevent this? Is there a way to expand the element's boundaries invisibly to include the tooltip, so that mouseout is only fired if the mouse is moved away from both parent element and the tooltip?

Here's a simplified version of my code (I' m using Svelte as my framework, but the question should be valid independent of framework):

<!-- component that wants to use a tooltip -->

  <span
      use:attachTooltip={{
        component: EntityCardTooltip      
      }}
  >My visible text</span>
  export const attachTooltip = (node,options) => {
    if (options) {

      let _component

      node.addEventListener('mouseover',addTooltip)
      node.addEventListener('mouseout',removeTooltip)

      function addTooltip(e) {
        const {component, ...otherOpts} = options

        _component = new component({
          target: node,
          props: {
            mouseEvent: e,
            ...otherOpts
          }
        })
      }

      function removeTooltip(e) {
        _component.$destroy()
      }

      return {
        destroy() {
          node.removeEventListener('mouseover',addTooltip)
          node.removeEventListener('mouseout',removeTooltip)
        }
      }
    }
  }

<!-- EntityCardTooltip --->

  <Tooltip {mouseEvent}>
    <div class="tipBody" slot="Content">
      <!--- tooltip content here --->
    </div>
  </Tooltip>

<!-- Tooltip --->

  <div class="tip" use:adjustTooltipOnMouseOver >
    {#if $$slots.Content}
      <slot name="Content" />
    {:else if innerHTML}
      {@html innerHTML}
    {/if}
  </div>

<style>
  @keyframes fadeInFromNone {
    0% {
        display: block;
        opacity: 0;
    }

    1% {
        display: block;
        opacity: 0;
    }

    100% {
        display: block;
        opacity: 1;
    }
  }

  .tip, .tip:before {
    pointer-events: none;
    box-sizing: border-box;
    display: block;
    opacity: 1;
  }

  .tip:before {
    content: "";
    position: absolute;
    width: 2rem;
    height: 2rem;
    z-index: 13;
  }

  .tip {
    animation: fadeInFromNone 0.05s linear 0.05s;
    animation-fill-mode: both;
    position: fixed;
    color: black;
    min-width: 3rem;
    white-space: nowrap;
    display: block;
    text-overflow: ellipsis;
    white-space: pre;
    z-index: 12;
  }
</style>

There is more CSS not shown here, used to conditionally position by transforms using CSS vars set by adjustTooltipOnMouseOver (determines direction, prevents overflow, etc.).

Share Improve this question asked Mar 11 at 12:01 Paul WPaul W 12.2k2 gold badges7 silver badges24 bronze badges 2
  • 1 Suggestion: Change the removeTooltip so it only actives after a timeout (giving user enough time to "move" to the tooltip), in removeTooltip check if user is currently over the tooltip, add a mouseout on the tooltip itself. – fdomn-m Commented Mar 11 at 12:04
  • @fdomn-m, that's a good idea, I'll give that a shot – Paul W Commented Mar 11 at 12:17
Add a comment  | 

2 Answers 2

Reset to default 1

The two common approaches are:

  • Moving the mouse away from the trigger schedules the closing of the tooltip via a timeout.
    Entering the tooltip then cancels this timeout again.
  • Adding invisible elements that bridge the gap; this is a lot more involved.
    E.g. one can add an invisible wedge that connects the trigger with the edges of the tooltip. Depending on the object hierarchy and given layout this can be a bit complicated so I would lean towards just adding a small timeout.

While it is possible to set a delay to removing the tooltip and set additional listeners on the tooltip node to intercept its removal and retrigger once the mouse is moved back out of the tooltip, I found this to be rather unwieldly and rather bug-prone.

It turns out that the behavior I was looking for could be achieved simply by changing mouseout to mouseleave. With mouseout, the moment I moved toward the tooltip it fired and destroyed my tooltip. But with mouseleave it doesn't fire until I move out of the parent node-and-tooltip complex, since the tooltip is a child of the parent node so moving into it doesn't leave the parent.

So the solution I found to work is to simply set a property enterable: true which when true changes the event listened for from mouseout to mouseleave. No need to play with timeouts and migrating event handlers!

export const attachTooltip = (node,options) => {
  if (options) {

    const mouseOutEvent=options.enterable ? "mouseleave" : "mouseout"

    node.addEventListener('mouseenter',addTooltip)
    node.addEventListener(mouseOutEvent,removeTooltip) -- set proper event
   . . .
发布评论

评论列表(0)

  1. 暂无评论