angulartypescriptmouseeventangular2-directivesangular2-hostbinding

mouseenter / mouseleave with @HostListener


The flickering is killing me and after reading all the jQuery related threads and mdn I still cannot figure it out.

So I have this @Directive for showing a tooltip and this is how I bind it to the elements:

  @HostListener('mouseenter', ['$event']) onEnter( e: MouseEvent ) {
    this.showTooltip(e);
  }

  @HostListener('mouseleave', ['$event']) onLeave( e: MouseEvent ) {
    this.hideTooltip(e);
  }

  @HostListener('click', ['$event']) onClick( e: MouseEvent ) {
    this.hideTooltip(e);
  }

  constructor(
    private el: ElementRef,
    private renderer: Renderer2,
    private coordsService: CoordsService,
    @Inject(DOCUMENT) doc
  ) {
    this.docBody = doc.body;
  }

  public ngAfterViewInit(): void {
    this.tooltipContainer = this.renderer.createElement('div');
    this.renderer.addClass( this.tooltipContainer, 'tooltip--tip');
    this.renderer.addClass( this.tooltipContainer, 'tooltip--pos_' + this.position);
  }

  public showTooltip( e: MouseEvent ): void {

    if ( this.tooltip !== null ) {

      // text 
      this.selectedTooltip = this.renderer.createText( this.tooltip );

      // append to body
      this.renderer.appendChild( this.tooltipContainer, this.selectedTooltip);
      this.renderer.appendChild( this.docBody, this.tooltipContainer );

      // target element
      const target: HTMLElement = <HTMLElement>e.target;
      const bbox: ClientRect = target.getBoundingClientRect();

      // the array holds the pixel position of the property; should sync with top:left
      let coords: ElementCoords = this.coordsService.setPositionCoords(bbox, this.position, this.tooltipContainer, { left: -6 } );

      // write position
      this.renderer.setStyle( this.tooltipContainer, 'top', coords.top+'px' );
      this.renderer.setStyle( this.tooltipContainer, 'left', coords.left+'px' );
    }

  }

  public hideTooltip( e: MouseEvent ): void {
    if ( this.selectedTooltip ) {
      this.renderer.removeChild ( this.tooltipContainer, this.selectedTooltip);
      this.renderer.removeChild( this.docBody, this.tooltipContainer );
      this.selectedTooltip = null;
    }
  }

Every <span> that has text in it flickers. Every selector that has an SVG in it flickers... Any ideas?

PS. Somehow el: ElementRef is not being used, although I have injected it for some reason. Tried matching the reference to it - still no luck.


Solution

  • Your tooltipContainer is triggering a mouseleave because it's over the element. If you don't mind that you can't click/select anything there. Set in your css the pointer-events: none to the tooltipContainer.

    You enter the element with the directive (mouseenter), the tooltip container goes over this element. Now the tooltip has the cursor over it. Aha! This triggers the (mouseleave) from the element with the directive, so the tooltip goes away. But hey, not the directive has the cursor again... (mouseenter)!... but hey! the tooltip is under the cursor again... (mouseleave)... etc :)

    With pointer-events, you make sure the overlaying element does not receive any evens, and these are triggered to the element below it. You can however also try to make the tooltip not overlap the element, but that's up to you