Position Bootstrap Popover based on SVG/Mouse Position

Viewed 382

I want to reposition the popover - which is currently to the right of the Circle SVG - based on either the user's mouse position within the CircleSVG (on click) or move it to the exact the center of the SVG (moving both the popover content and arrow). Is it possible to change it dynamically?

const popover = new bootstrap.Popover(document.getElementById("test"), {
                        html: true,
                        sanitize: false,
                        trigger: 'manual',
                        content: 'Nice'
                    });
                    
function togglePopover() {
  popover.toggle();
}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>

<svg onclick="togglePopover()"  height="100" width="100">
  <circle id="test" cx="50" cy="50" r="40" stroke="black" stroke width="3" fill="red" />
</svg>

2 Answers

Yes, it's possible to change the popover position according to the mouse position. You can do that by appending a <style> element to the HTML document with dynamic CSS every time the SVG is clicked and the popover is showed.

const popover = new bootstrap.Popover(document.getElementById("test"), {
  html: true,
  sanitize: false,
  trigger: 'manual',
  content: 'Nice'
});

function addStyle(css) {
  const existingStyleElement = document.querySelector('#popoverCustomStyle');

  if (existingStyleElement) {
    existingStyleElement.remove();
  }

  document.head.insertAdjacentHTML('beforeend', `
    <style id="popoverCustomStyle">${css}</style>
  `);
}

document.querySelector('#mySVG').addEventListener('click', function togglePopover(e) {
  popover.toggle();

  if (popover._hoverState) {
    const popoverArrowWidth = popover.tip.querySelector('.popover-arrow').getBoundingClientRect().width;

    const popoverPlacementSettings = {
      left: {
        x: `calc(${e.pageX - popoverArrowWidth}px - 100%)`,
        y: `calc(${e.pageY}px - 50%)`
      },
      right: {
        x: `${e.pageX + popoverArrowWidth}px`,
        y: `calc(${e.pageY}px - 50%)`
      },
      top: {
        x: `calc(${e.pageX}px - 50%)`,
        y: `calc(${e.pageY - popoverArrowWidth / 2}px - 100%)`
      },
      bottom: {
        x: `calc(${e.pageX}px - 50%)`,
        y: `${e.pageY + popoverArrowWidth / 2}px`
      }
    }

    setTimeout(() => {
      addStyle(`
      .my-popover {
        inset: 0 auto auto 0 !important;
        transform: translate(${popoverPlacementSettings[popover._popper.state.placement].x}, ${popoverPlacementSettings[popover._popper.state.placement].y}) !important;
      }
    `);
      popover.tip.classList.add('my-popover');
    }, 0)
  }
})
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>

<svg id="mySVG" height="100" width="100">
  <circle id="test" cx="50" cy="50" r="40" stroke="black" stroke width="3" fill="red" />
</svg>

So you asked a couple of things, both I believe can be accomplished by generating the popover at the time of the click event.

Putting the popover so that it points to the in the middle of the element can be accomplished by calculating and supplying an "offset" option. See the red circle & the togglePopover_toCenter function in the example

Pointing the popover to where the mouse is when the click occurs can be accomplished by attaching an event listener that uses the mouse location and adjusts the popover position when it is generated. See the blue circle & the togglePopover_toMouse function in the example

function togglePopover_toCenter(event) {
  const targetElement = event.target;
  
  // if a popover already exisits for this element. Dispose of it.
  const oldPopover = bootstrap.Popover.getInstance(targetElement);
  if (oldPopover){
    oldPopover.dispose();
  }
  
  const generatePopover = (element) => {
    // get it's width
    const boundingRect = element.getBoundingClientRect();
    const elementWidth = boundingRect.width;

    // calculate the "distance" value needed by the offset option
    // half of element width, converted to a negative, adding back the default 8 for the triangle
    const offsetDistance = (elementWidth/2 * -1) + 8;
  
    // set up the popover using the offset attribute
    const popover = new bootstrap.Popover(
      element, 
      {
        html: true,
        sanitize: false,
        trigger: 'manual',
        content: 'Nice',
        offset: [0, offsetDistance]
      }
    );
    return popover;
} 
  const newPopover = generatePopover(targetElement);
  newPopover.toggle();
}


                    
function togglePopover_toMouse(event) {
  
  // if a popover already exisits for this element. Dispose of it.
  const oldPopover = bootstrap.Popover.getInstance(event.target);
  if (oldPopover){
      oldPopover.dispose();
  }
  
  const generatePopover_toMouse = (event) =>{
    // get the clicked element
    const targetElement = event.target;

    // set up the popover 
    const popover = new bootstrap.Popover(
      targetElement, 
      {
        html: true,
        sanitize: false,
        trigger: 'manual',
        content: 'Nice'
      }
    );
  
    
    // get the clicked elements boundRect
    const boundingRect = targetElement.getBoundingClientRect();
    const x = event.clientX - boundingRect.left; //x position within the element.
    const y = event.clientY - boundingRect.top;  //y position within the element.
  
    // set up an event listerner to move the popover after it is shown
    targetElement.addEventListener('shown.bs.popover',() => {
      if (popover.tip){
        popover.tip.style.left = `${x - boundingRect.width}px`;
        popover.tip.style.top = `${y - (boundingRect.height/2)}px`;
      }
    });

    popover.toggle();
  } 
  
  // create a new popover by passing in the click event
  generatePopover_toMouse(event);
}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"/>


<svg onclick="togglePopover_toCenter(event)"  height="100" width="100">
  <circle id="test" cx="50" cy="50" r="40" stroke="black" stroke width="3" fill="red" />
</svg>

<br />


<svg onclick="togglePopover_toMouse(event)"  height="100" width="100">
  <circle id="test" cx="50" cy="50" r="40" stroke="black" stroke width="3" fill="blue" />
</svg>

Related