Show popup on mouse location

html, javascript, jquery

Solution

It depends whether your popup (tooltip) is position `absolute` or `fixed` and the parent has any `position` other than `static` set, and if the popup is inside the wrapper or not; or is a direct child of `body`.

Three examples ahead:

- Absolute popup as child of a non-static parent (has Pros and Cons)

- Fixed popup (has Pros and Cons)

- Absolute popup as immediate child of document `<body>` (Best)

1. Move absolute child to X,Y coordinates within the parent

Case: `position: absolute;` popup inside a `position: relative;` parent.

- If the parent has scrollbars (`overflow` value other than `visible`)

X = Event.pageX + Parent.scrollLeft - Parent.offsetLeft
Y = Event.pageY + Parent.scrollTop - Parent.offsetTop

- If the Parent has no scrollbars (Don't use this!):

// !! Bad practice !!
X = Event.pageX - Parent.offsetLeft   
Y = Event.pageY - Parent.offsetTop    

Example:

const el = (sel, par) => (par||document).querySelector(sel);

const elArea  = el("#area");
const elPopup = el("#popup");

const showPopup = (evt) => {
  const elBtn = evt.currentTarget;
  
  Object.assign(elPopup.style, {
    left: `${evt.pageX + elBtn.scrollLeft - elBtn.offsetLeft}px`,
    top:  `${evt.pageY + elBtn.scrollTop - elBtn.offsetTop}px`,
    display: `block`,
  });
};

elArea.addEventListener("click", showPopup);
body {
  height: 300vh; /* Just to force some scrollbars */
}

#area {
  position: relative;  /* this is needed! */  
  height: 150px;
  background: #eee;
  margin: 40px;
}

#popup {
  position: absolute;
  height: 30px;
  background: gold;
  display: none;
}
<div id="area">
  Click here to shop popup, and scroll the window
  <div id="popup">Popup</div>
</div>

Pros:

- Even if the page is scrolled, the popup stays still inside the wrapping element

Cons:

- Additional code is needed to prevent the popup move if it was the actual Event `target` - to allow inner content to be interacted with and the popup not to move.

- The popup needs to be child of a specific parent Element, which leads to poor code reusability. PS: A solution would be using: `SomeClickedElement.append(EL_popup)`.

- The wrapping parent element (`#area`) needs a `position` set (other than `static`), i.e: `position: relative;`

2. Move fixed element to X,Y coordinates

Case: `position: fixed;` popup outside (or inside) of parent, but usually as child of `<body>`.

X = Event.clientX  
Y = Event.clientY

Example:

const el = (sel, par) => (par||document).querySelector(sel);

const elArea  = el("#area");
const elPopup = el("#popup");

const showPopup = (evt) => {
  Object.assign(elPopup.style, {
    left: `${evt.clientX}px`,
    top: `${evt.clientY}px`,
    display: `block`,
  });
};

elArea.addEventListener("click", showPopup);
body {
  height: 300vh; /* Just to force some scrollbars */
}

#area {
  /* position: relative; /* not necessary any more */
  height: 150px;
  background: #eee;
  margin: 40px;
}

#popup {
  position: fixed;
  height: 30px;
  background: gold;
  display: none;
}
<div id="area">
  Click here to shop popup, and scroll the window
</div>

<div id="popup">Popup fixed</div>

Pros:

- If placed outside of the parent (`#area`) - no additional code is necessary to prevent it from moving if the click initiated inside of it.

- Better code reusability. Depending on the clicked element you can reuse the same popup by just changing its content.

- The wrapping parent element (`#area`) does not need `position`.

- `Event.clientX` and `Event.clientY` is all it takes to move it to the new position.

Cons:

- If the page is scrolled, the popup position is fixed in relation to the window. A solution is to make it hide on page scroll, or more complicated - move it by the difference in coordinates.

3. (Best!) Move absolute (in-body) element to X,Y coordinates

Case: `position: absolute;` popup as immediate child of `body`

X = Event.clientX + window.scrollX
Y = Event.clientY + window.scrollY

Example:

const el = (sel, par) => (par||document).querySelector(sel);

const elArea  = el("#area");
const elPopup = el("#popup");

const showPopup = (evt) => {
  Object.assign(elPopup.style, {
    left: `${evt.clientX + window.scrollX}px`,
    top: `${evt.clientY + window.scrollY}px`,
    display: `block`,
  });
};

elArea.addEventListener("click", showPopup);
body {
  height: 300vh; /* Just to force some scrollbars */
}

#area {
  /* position: relative; /* not necessary any more */
  height: 150px;
  background: #eee;
  margin: 40px;
}

#popup {
  position: absolute;
  height: 30px;
  background: gold;
  display: none;
}
<div id="area">
  Click here to shop popup, and scroll the window
</div>

<div id="popup">Popup fixed</div>

Pros:

- Even If the page is scrolled, the popup position is "anchored" in the same position relative to the document, not to the scrolling window.

- (Unless the `Event.currentTarget` is the entire `"body"`) the popup will not change position if its contents are clicked.

Suggestions for all cases:

- For an improved UX (User Experience) a "floating" popup needs additional calculation, and that is — determining the remaining available space constraints to prevent the popup overflow the window right/bottom edges

- Additional code is necessary to close a Popup if the user clicks outside of it. This can be achieved by simply checking if on click — the `Event.target.closest("#popup") !== EL_popup` (Close the popup if `true`). The same sometimes applies for Context-menus, but rarely for window-centered Modals — which have a dedicated close button.

Best practice example:

// DOM utility functions:

const el = (sel, par) => (par||document).querySelector(sel);


// Popup:

let elPopup; // To remember the currently active popup

const handlePopup = (evt) => {
  // Get clicked target
  const elTarget = evt.target;

  // Clicked a popup, do nothing (Comment this line if not needed)
  if (elTarget.closest(".popup")) return;

  // Close currently open popup (if any):
  if (elPopup) elPopup.classList.remove("is-active");

  // Get initiator button
  const elBtn = elTarget.closest("[data-popup]");

  // Not a valid button
  if (!elBtn) return;

  // Get the popup
  elPopup = el(elBtn.dataset.popup);  

  // No matching popup in this page, do nothing
  if (!elPopup) return; 
  
  // Get its parent (BODY) so that we can calculate the min max
  // available space
  const elParent = elPopup.parentElement;
  
  // Position:
  const absX = evt.clientX + window.scrollX;
  const absY = evt.clientY + window.scrollY;
  
  const bcrParent = elParent.getBoundingClientRect();
  const bcrPopup = elPopup.getBoundingClientRect();
  
  const maxX = bcrParent.width - bcrPopup.width;
  const maxY = bcrParent.height - bcrPopup.height;
  
  const x = Math.max(0, Math.min(absX, maxX));
  const y = Math.max(0, Math.min(absY, maxY));
  
  // Show popup
  Object.assign(elPopup.style, {
    left: `${x}px`,
    top: `${y}px`,
  });
  elPopup.classList.add("is-active");

};

el("body").addEventListener("click", handlePopup);
/*QuickReset*/ * { margin: 0; box-sizing: border-box; }

body {
  min-height: 300vh; /* just to force some demo scrollbars */
}

#area {
  background: #eee;
  padding: 10px;
}

.popup {
  position: absolute;
  background: gold;
  visibility: hidden;
  pointer-events: none;
  min-width: 100px;
  padding: 1rem;
}

.popup.is-active {
  visibility: visible;
  pointer-events: auto;
}
<div id="area" data-popup="#popup-one">Show "popup one"</div>

<button data-popup="#popup-two" type="button">Show "popup two"</button>

<br>Click anywhere to close an open popup  
<br>Click inside a popup. It will not close ()

<div class="popup" id="popup-one">Popup one!</div>
<div class="popup" id="popup-two">Popup TWO!</div>

Problem

I am having troubles with getting a popup to come up at the exact location of my mouse click. I want to be able to click a DIV (that is inside a table cell) and for a popup to show up whenever this div is clicked. Right now I have the popup working, however no matter what I try the popup DIV is half way down the page. HTML ``` <ul class="customdropdown" style="display:none;" id="xxx<?php echo $fetch['unit_no']; ?>" role="menu" aria-labelledby="menuitems"> <li role="presentation"><a role="menuitem" tabindex="-1" href="#"</i>Link 1</a></li> <li role="presentation"><a role="menuitem" tabindex="-1" href="#"</i>Link 2</a></li> </ul> ``` JQUERY ``` $(document).ready(function () { $('#myDiv<?php echo $fetch['unit_no']; ?>').click(function (e) { var myDiv = document.getElementById('myDiv<?php echo $fetch['unit_no']; ?>'); var leftx = e.pageX-myDiv.offsetLeft; var topy = e.pageY-myDiv.offsetTop; $("#xxx<?php echo $fetch['unit_no']; ?>") .css({ position: 'absolute', left: leftx, top: topy, display: 'block' }) }); }); ``` Screenshot of what is happening below: Is this to do with the positioning of the DIV?

Original source