Gesset
Gesset

Reputation: 63

Show popup on mouse location

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:

enter image description here

Is this to do with the positioning of the DIV?

Upvotes: 2

Views: 13166

Answers (1)

Roko C. Buljan
Roko C. Buljan

Reputation: 206008

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:

  1. Absolute popup as child of a non-static parent (has Pros and Cons)
  2. Fixed popup (has Pros and Cons)
  3. 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>

Upvotes: 10

Related Questions