Tips for preventing cursor tooltips from going off the screen

I've been working on creating a popup box that displays text positioned away from an element when the cursor hovers over it. Check out a simplified demo here.

Currently, the popup box collapses when it gets close to the bounding box. I'd like it to appear at the bottom-left (if it overflows to the right) or top-left (if it overflows to the bottom and right) of the hovered element's position if it exceeds the boundaries.

The text and position displayed are dynamically generated, meaning I won't know them in advance.

My current solution involves using Javascript:

  • Retrieve the text to be displayed. Calculate the width of the text by multiplying the number of characters by 0.25em.
  • Determine the total width of the displayed text + padding (left and right), referred to as textLength. This sets the popup's width to ensure all text fits on one line.
  • If the cursor's x position + textLength is greater than the box width, shift the position of the popup box to the left by subtracting textLength and some distance from the element.
  • Repeat the same check for the y position. If the cursor position + line height (1em) + bottom padding exceeds the box height, adjust the y position accordingly.

This solution works, but I'm interested in exploring more efficient alternatives that don't involve character counting. Is there a more elegant way to achieve this, perhaps using CSS only without Javascript?

https://i.sstatic.net/81xhx.png

Answer №1

Unfortunately, it seems that accomplishing this task with CSS alone is not possible. However, after some experimentation with your fiddle, I was able to implement the desired functionality.

My approach involved referencing the container and verifying whether the popup's position and size fell within the container's BoundingClientRect.

Here is the revised code for the popupShow function:

const showPopup = (top, left, text, container) => {
    popup.textContent = text;
  
  const containerBCR = container.getBoundingClientRect();
  const popupBCR = popup.getBoundingClientRect();
  const popupWidth = popupBCR.width,
        popupHeight = popupBCR.height;
  
  let popupTop = top + 20,
        popupLeft = left + 20,
      newPopupWidth;
  
  console.log("height: ", popupHeight);
  console.log("top: ", top);
  console.log("bottomPopup: ", top + 20 + popupHeight);
  console.log("bottomBoundary", containerBCR.bottom);
  
  if (left + 20 + popupWidth > containerBCR.right) {
    popupLeft = left - popupWidth;
    
    if (popupLeft < containerBCR.left) {
        popupLeft = containerBCR.left;
      newPopupWidth = left - containerBCR.left;
    }
  }
  
  if (top + 20 + popupHeight > containerBCR.bottom) {
    popupTop = top - popupHeight;
    
    if (popupTop < containerBCR.top) {
        popupTop = containerBCR.top;
    }
  }
  
  popup.style.top = popupTop + "px";
  popup.style.left = popupLeft + "px";
  popup.style.width = newPopupWidth;
  popup.style.visibility = 'visible';
}

Additionally, I updated the popup to utilize "visibility: hidden" instead of "display: none" to ensure we can still access its size when needed.

Feel free to review the revised fiddle and share your thoughts.

I made a minor adjustment to push one circle slightly downwards because the current code does not account for the popup's padding, causing it to overflow slightly (by a few pixels).

Answer №2

This approach relies on dividing the element into quadrants and determining if it exceeds 50% width and/or height. It then switches the style to utilize the right or bottom position accordingly. The method is indifferent to the popup's content, eliminating the need for precise measurements.

const popup = document.getElementById("pop-up")

const parsePx = (px) => parseFloat(px.slice(0, -2))

const showPopup = (text, position) => {
  popup.textContent = text;
  popup.style.top = position.top;
  popup.style.left = position.left;
  popup.style.right = position.right;
  popup.style.bottom = position.bottom;
  popup.style.display = 'inline-block';
}

const hidePopup = () => {
  popup.style.display = 'none';
}

const circles = document.querySelectorAll(".red-circle")
circles.forEach(el => el.addEventListener('mouseover', (e) => {

  const hoveredEl = e.target;

  const textContent = hoveredEl.getAttribute('data-content');
  
  //get absolute position of elements
  let elBounds = hoveredEl.getBoundingClientRect();
    
  //get absolute position of parent;
  let ctBounds = popup.parentElement.getBoundingClientRect();
    
  //calculate relative positions
  let left =  elBounds.left - ctBounds.left + (elBounds.width / 2), 
    top = elBounds.top - ctBounds.top + (elBounds.height / 2),
    width = ctBounds.width,
    height = ctBounds.height

  //prepare position settings
  let position = { left: "auto", top: "auto", bottom: "auto", right: "auto" }
  
  //calculate if we're over 50% of box size
  if(top>ctBounds.height/2) position.bottom = ctBounds.height - top + 20 + 'px'; else position.top = top + 20 + 'px';
  if(left>ctBounds.width/2) position.right = ctBounds.width - left + 20 + 'px'; else position.left = left + 20 + 'px';
  
  showPopup(textContent, position);
  
}))

circles.forEach(el => el.addEventListener('mouseout', (e) => { hidePopup() }))
.container {  width: 200px;  height: 200px; border: 1px solid black;  position: relative;}
.red-circle { border-radius: 50%;  background: red;  width: 20px;  height: 20px;  position: absolute;}
#pop-up {  background-color: #EFEFEF;  padding: 0.25em;  position: absolute;}
<div class="container">
  <div style="top:20px;left:20px;" class="red-circle" data-content="This is a red circle"></div>
  <div style="top:10px;left:150px;" class="red-circle" data-content="This is the top-right red circle"></div>
  <div style="top:140px;left:150px;" class="red-circle" data-content="This is the bottom-right red circle"></div>
  <div style="top:140px;left:15px;" class="red-circle" data-content="This is the bottom-left red circle"></div>
  <span style="display:hidden" id="pop-up"></span>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Could there be a coding issue stopping <div class="rating"> from aligning more centrally along the horizontal axis on the screen?

Just wondering, could there be something in my code that's preventing <div class="rating"> from moving closer to the center? And is there a way to make it move closer horizontally? (On a side note, CSS layout and positioning still puzz ...

The navigation bar and footer are not utilizing the entire width of the browser

Something seems off on the Safari browser, as there's still a gap in the right side of the navbar and footer. I've been using Bootstrap and steering clear of Javascript. Any ideas on how to fix this? Appreciate any help. Thanks! Here's the ...

Acquire the value of ant-design Select dropdown upon hover

How can we detect the selected option in an ant-design dropdown when it is hovered? <Select defaultValue="lucy" style={{ width: 120 }} onChange={handleChange}> <Option value="jack">Jack</Option> <Option value=& ...

Element that hovers and floats

Just last week, I was working on coding a custom menu bar for my blog located at . However, when it came to adding social buttons, things went awry. It seemed like a float element issue! Despite my attempts to resolve it by adjusting widths and properties, ...

Ways to ensure that v-model does not become "true" or "false" in an input checkbox using Vue

I am currently working on a filter popup that includes a list of checkboxes. By default, some items should be selected and others not selected. I have connected these checkboxes to an object array using v-model. My issue is that when I deselect and select ...

What method is the most effective for retrieving the prior slug name in NextJS?

Looking for Help with Retrieving postID? Greetings! I am currently working on a basic post detail page using NextJS. My URL structure is: [postID]/[title].tsx On the post detail page, I need to fetch the post's data based on the postID, which is hig ...

Creating a mapping in Node.js to write to a serial port and then retrieve the

Currently, I am utilizing the node-serialport module for serial port communication. My goal is to send the command ATEC and receive a response of ECHO. However, the process of sending and receiving data is asynchronous (after sending the data, the timing ...

Show angular variable data in a table based on their index

I have an array of angular variables with values like [ 170, 1, 130, 3, 134, 4.... and so on]. I would like to present these values in a table format where the even positioned values are shown in one column and the odd positioned values in another column. ...

The text exceeds the width of the paragraph

Something very odd is happening, a rare occurrence in my over 20 years of web development experience. The text within the paragraph seems to be overflowing its boundaries: https://i.sstatic.net/bmB1zg7U.jpg My client specifically requested the Duffy Scri ...

Retrieving Data Attribute From Form Using Python Flask

Is there a way to retrieve data from a POST request in Flask that is not the standard text field value? I am looking for a solution that does not involve using JavaScript, but rather only Python. Let's consider a simplified example: I need to extrac ...

The fascinating CSS Circle interaction on mouse click or hover

I am encountering a challenge with my circular element: Here is the HTML code for it: <div id="quadrant-sizer"></div> And here is the CSS styling: #quadrant-sizer { width: 40px; height: 40px; border-radius: 999px; } The issue I ...

Troubleshooting issues with parsing JSON responses in AngularJS

Being new to using AngularJS, I am encountering an issue with parsing a JSON response. I have user login credentials such as Username and password, and I am attempting to parse them when the user clicks on the login button(). If the username and password m ...

Searching through a JSON object on a Laravel web page using JavaScript or AJAX for live filtering purposes

After diving into AJAX and JavaScript, I find myself needing to replace some outdated Angular functionality on our Laravel site. The specific task at hand is creating a live search filter for a page with a static header search bar. The main requirement is ...

Why can't we use percentages to change the max-height property in JavaScript?

I am currently working on creating a responsive menu featuring a hamburger icon. My goal is to have the menu list slide in and out without using jQuery, but instead relying purely on JavaScript. HTML : <div id="animation"> </div> <button ...

Personalized Firefox Scrollbar - Rounded Corners

I have successfully customized the default CSS of browser scrollbars for Chrome and Edge, but I am facing issues with Firefox. Is there a way to sync the scrollbar styling in Firefox with Chrome and Edge? Currently, I am unable to apply border radius to th ...

Tips for creating a responsive fixed div/nav that adjusts its size based on the container it is placed within

Just starting out in web development and struggling with this issue. Any assistance would be much appreciated! When resizing the window, the fixed div moves outside of its container instead of resizing. The navigation bar on the website I'm working o ...

How to perfectly align a full-width div with a div of specific width

Consider this scenario: .d1 { width: 100px; float: left; } .d2 { width: auto; float: left; } <div class="d1">Fixed Content</div> <div class="d2">This content should expand to fill the rest of the page vertically.</div> This setu ...

Can HTML Elements be used within a Contenteditable section?

I am facing an issue with a contenteditable tag on my website. Users are unable to type code into the tag and have it display as an actual element instead of just text. Is there a way for users to input full, working HTML elements in a contenteditable box ...

The function stringByEvaluatingJavaScriptFromString is failing to execute

I have tackled similar problems that have been posted in this forum, however my issue remains unresolved. I am attempting to utilize stringByEvaluatingJavaScriptFromString in swift, but it is not functioning as expected. Any assistance would be greatly app ...

What could be causing React onclick events to not trigger when wrapped within a Vue application? (No additional libraries)

As I dive into the world of combining React and Vue components, I encountered an interesting challenge... const RootTemplate = () => { return ( <div id="vue-app"> ... <IconButton color="inherit" onClick={thi ...