Search code examples
javascripthtmlangulartypescriptangular-cdk-drag-drop

place the tooltip on available large space inside a container in angular


I have an editor in which the user can create a banner the user can drag the element in any position he/she wants inside a banner, the element has a tooltip, on hover, it should show the tooltip positioned on the side where the space is larger than the rest (top, left, bottom, right) and the tooltip should never go outside the container no matter what.

HTML

<div id="banner-container" class="banner-container">
    <span
        (cdkDragReleased)="onCircleButtonDragEnd($event)"
        id="point-button"
        class="point-button"
        cdkDragBoundary=".banner-container"
        cdkDrag
        [style.left]="banner.bannerElements.x"
        [style.top]="banner.bannerElements.y"
        [attr.data-id]="banner.bannerElements.buttonId"
        [id]="'button-' + banner.bannerElements.buttonId"
    ></span>
    <span
        id="tooltip"
        [style.left]="banner.bannerElements.x"
        [style.top]="banner.bannerElements.y"
        [attr.data-id]="banner.bannerElements.tooltipId"
        [id]="'button-' + banner.bannerElements.tooltipId"
    >
        Szanujemy Twoją prywatność
    </span>
</div>

TS

  banner = {
        buttonId: 11,
        tooltipId: 2,
        x: 0,
        y: 0
    };

onCircleButtonDragEnd(event) {
        const container = event.currentTarget as HTMLElement;
        const containerWidth = container.clientWidth;
        const containerHeight = container.clientHeight;

        this.banner.y =
            ((event.clientX - container.getBoundingClientRect().left) /
                containerWidth) *
            100;
        this.banner.y =
            ((event.clientY - container.getBoundingClientRect().top) /
                containerHeight) *
            100;
    }``

CSS

.point-button {
  cursor: pointer;
  display: block;
  width: 24px;
  height: 24px;
  border: 2px solid rgb(179, 115, 188);
  background-color: rgb(255, 255, 255);
  background-image: none;
  border-radius: 100%;
  position: relative;
  z-index: 1;
  box-sizing: border-box;
}

.point-button:active {
  box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.banner-container {
  width: 350px;
  height: 200px;
  max-width: 100%;
  border: dotted #ccc 2px;
}
.tooltip {
  width: fit-content;
  height: 50px;
  border: 2px #ccc solid;
  display: none;
}
.point-button:hover + .tooltip {
  display: block;
}`

**

LIVE DEMO

** : DEMO

enter image description here enter image description here enter image description here enter image description here

**

Update:

** Without using any library


Solution

  • TLDR; StackBlitz demo.


    You can create a function that does the math and logic of determining the top and left properties of the tooltip position. Then you can easily apply them using [ngStyle]:

    <div #container class="banner-container">
      <span #element class="point-button" cdkDrag
        [cdkDragBoundary]="container"
        (cdkDragReleased)="updateTooltipStyle()"
      ></span>
      <span #tooltip class="tooltip" [ngStyle]="tooltipStyle">
        This is a tooltip :-)
      </span>
    </div>
    
    export class CdkDragDropHorizontalSortingExample implements AfterViewInit {
    
      @ViewChild('container') container!: ElementRef;
      @ViewChild('element') element!: ElementRef;
      @ViewChild('tooltip') tooltip!: ElementRef;
     
      tooltipStyle = {};
    
      ngAfterViewInit() {
        this.updateTooltipStyle();
      }
      
      updateTooltipStyle() {
        const parent  = this.container.nativeElement.getBoundingClientRect();
        const element = this.element.nativeElement.getBoundingClientRect();
        const tooltip = this.tooltip.nativeElement.getBoundingClientRect();
      
        const spaceLeft   = element.left  - parent.left;
        const spaceRight  = parent.right  - element.right;
        const spaceTop    = element.top   - parent.top;
        const spaceBottom = parent.bottom - element.bottom;
    
        const spaceX = Math.max(spaceLeft, spaceRight) - tooltip.width;
        const spaceY = Math.max(spaceTop, spaceBottom) - tooltip.height;
    
        let tooltipTop, tooltipLeft;
    
        if (spaceX > spaceY) {
          tooltipLeft = spaceLeft > spaceRight
            ? element.left - parent.left - tooltip.width - MARGIN
            : element.right - parent.left + MARGIN;
    
          tooltipTop = Math.min(Math.max(0, (element.top - parent.top) + (element.height - tooltip.height) / 2), parent.height - tooltip.height);
    
        } else {
          tooltipTop = spaceTop > spaceBottom
            ? element.top - parent.top - tooltip.height - MARGIN
            : element.bottom - parent.top + MARGIN;
    
          tooltipLeft = Math.min(Math.max(0, (element.left - parent.left) + (element.width - tooltip.width) / 2), parent.width - tooltip.width);
        }
    
        this.tooltipStyle = {
          left  : `${tooltipLeft}px`,
          top   : `${tooltipTop}px`,
          width : `${tooltip.width}px`
        };
      }
    
    }
    

    Here's a working StackBlitz demo.