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?