Codder
Codder

Reputation: 37

How can I make a tooltip overflow and display correctly outside a parent container with overflow: hidden?

I have a nested structure of div elements where the innermost child contains a tooltip. The parent container (parent class) has the CSS property overflow: hidden. However, when hovering over the tooltip, it gets clipped and doesn't overflow outside the boundaries of the parent. Here's a simplified version of the structure:

document.querySelectorAll(".tooltip_container").forEach((tooltip) => {
  const tooltipText = tooltip.getAttribute("data-tooltip");
  const tooltipContent = document.createElement("div");
  tooltipContent.className = "tooltip_content";
  tooltipContent.innerText = tooltipText;

  tooltip.appendChild(tooltipContent);

});
.parent_class {
  position: relative;
  width: 300px;
  height: 100px;
  margin: 50px;
  overflow: auto;
  border: 2px solid #ccc;
  padding: 20px;
  background-color: #f9f9f9;
}

.child_level_four {
  position: relative;
  margin-top: 0px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}


.tooltip_container {
  position: relative;
  display: inline-block;
}

.tooltip_content {
  position: absolute;
  bottom: 100%;
  left: -15%;
  transform: translateX(-50%);
  background-color: #333;
  color: #fff;
  padding: 8px;
  border-radius: 4px;
  opacity: 0;
  visibility: hidden;
  white-space: nowrap;
  transition: all 0.3s ease-in-out;
  z-index: 1000;
}

.tooltip_content::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border-width: 5px;
  border-style: solid;
  border-color: #333 transparent transparent transparent;
}

.tooltip_container:hover .tooltip_content {
  opacity: 1;
  visibility: visible;
  transform: translateY(-18%);
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Pure JS Tooltip</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="parent_class">
    <div class="child_level_one">
      <div class="child_level_two">
        <div class="child_level_three">
          <div class="child_level_four">
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script src="script.js"></script>
</body>
</html>

Upvotes: 1

Views: 93

Answers (2)

Muideen Adewale
Muideen Adewale

Reputation: 1

This happen because the overflow: hidden attribute on .parent_class truncates any information that exceeds its borders, including your tooltip. Here is the simple way to make the tooltip visible:

  1. Change the tooltip's position to fixed. This positions it relative to the viewport instead of the parent container, bypassing the overflow: hidden issue, replace below with your .tooltip_content class.

Solution:

.tooltip_content {
    /* position: absolute;
    bottom: 100%;
    left: -50%;
    transform: translateX(-50%);*/
    position: fixed; /* Position relative to viewport */
    bottom: auto;    /* Adjust position for viewport */
    left: auto;
    transform: none; /* Remove or adjust transform */
    background-color: #333;
    color: #fff;
    padding: 8px;
    border-radius: 4px;
    opacity: 0;
    visibility: hidden;
    white-space: nowrap;
    transition: all 0.3s ease-in-out;
    z-index: 1000;
  }
  1. Positioning Relative to the Button: The position of the tooltip can be dynamically calculated based on the button's position, getBoundingClientRect(), and then moves it with position: fixed. This will position the tooltip based on the viewport, not constrained within the boundaries of its parent container. Also no overflow clipping, because we are using position: fixed, the tooltip isn't affected by overflow: hidden on the parent container, and it can pop out of it.

Solution:

document.querySelectorAll(".tooltip_container").forEach((tooltip) => {
        const tooltipText = tooltip.getAttribute("data-tooltip");
      
        // Create the tooltip element
        const tooltipContent = document.createElement("div");
        tooltipContent.className = "tooltip_content";
        tooltipContent.innerText = tooltipText;
        tooltip.appendChild(tooltipContent);
      
        // When hovering over the button, position the tooltip
        tooltip.addEventListener("mouseenter", () => {
          const rect = tooltip.getBoundingClientRect();  // Get position of the button
          const tooltipRect = tooltipContent.getBoundingClientRect(); // Get size of the tooltip
      
          // Calculate the position of the tooltip based on the button's position
          tooltipContent.style.position = "fixed";
          tooltipContent.style.top = `${rect.top - tooltipRect.height - 0}px`; // Position above the button
          tooltipContent.style.left = `${rect.left + rect.width / 2 - tooltipRect.width / 2}px`; // Center it above the button
      
          // Show the tooltip
          tooltipContent.style.opacity = 1;
          tooltipContent.style.visibility = "visible";
        });
      
        // Hide tooltip when mouse leaves
        tooltip.addEventListener("mouseleave", () => {
          tooltipContent.style.opacity = 0;
          tooltipContent.style.visibility = "hidden";
        });
      });
.parent_class {
    position: relative;
    width: 300px;
    height: 100px;
    margin: 50px;
    overflow: hidden; /* Prevents overflow of the parent */
    border: 2px solid #ccc;
    padding: 20px;
    background-color: #f9f9f9;
  }
  
  .tooltip_container {
    position: relative; /* Tooltip stays aligned to the button */
    display: inline-block;
  }
  
  .tooltip_content {
    position: absolute;
    bottom: 100%;
    left: 50%;
    top: 37px;
    transform: translateX(-50%);
    background-color: #333;
    color: #fff;
    padding: 8px;
    padding-bottom: 27px;
    border-radius: 4px;
    opacity: 0;
    visibility: hidden;
    white-space: nowrap;
    transition: all 0.3s ease-in-out;
    z-index: 1000;
  }
  
  .tooltip_content::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border-width: 5px;
    border-style: solid;
    border-color: #333 transparent transparent transparent;
  }
  
  .tooltip_container:hover .tooltip_content {
    opacity: 1;
    visibility: visible;
    transform: translateY(-18%);
  }
  
 <div class="parent_class">
    <div class="child_level_one">
      <div class="child_level_two">
        <div class="child_level_three">
          <div class="child_level_four">
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

Upvotes: 0

mplungjan
mplungjan

Reputation: 178350

That is not trivial. We need to append to body and make a sort of mouse follow:

document.querySelectorAll(".tooltip_container").forEach((tooltip) => {
  const tooltipText = tooltip.getAttribute("data-tooltip");

  const tooltipContent = document.createElement("div");
  tooltipContent.className = "tooltip_content";
  tooltipContent.innerText = tooltipText;

  // Append tooltip to the body
  document.body.appendChild(tooltipContent);

  tooltip.addEventListener("mouseenter", (event) => {
    tooltipContent.style.visibility = "visible";
    tooltipContent.style.opacity = "1";

    // Position tooltip initially
    updateTooltipPosition(event);
  });

  tooltip.addEventListener("mouseleave", () => {
    tooltipContent.style.visibility = "hidden";
    tooltipContent.style.opacity = "0";
  });

  tooltip.addEventListener("mousemove", (event) => {
    updateTooltipPosition(event);
  });

  function updateTooltipPosition(event) {
    const offset = 10; // Offset to avoid flickering - since mousing over the tooltip triggers mouseleave
    const tooltipWidth = tooltipContent.offsetWidth;
    const tooltipHeight = tooltipContent.offsetHeight;

    let top = event.clientY - tooltipHeight - offset;
    let left = event.clientX - tooltipWidth / 2;

    // Prevent tooltip from going out of the viewport
    if (top < 0) top = event.clientY + offset; // Position below if it goes above
    if (left < 0) left = offset; // Align to the left edge
    if (left + tooltipWidth > window.innerWidth)
      left = window.innerWidth - tooltipWidth - offset; // Align to the right edge

    tooltipContent.style.top = `${top}px`;
    tooltipContent.style.left = `${left}px`;
  }
});
.parent_class {
  position: relative;
  width: 300px;
  height: 100px;
  margin: 50px;
  overflow: auto;
  border: 2px solid #ccc;
  padding: 20px;
  background-color: #f9f9f9;
}

.child_level_four {
  position: relative;
  margin-top: 0px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}


.tooltip_container {
  position: relative;
  display: inline-block;
  z-index: 1000;
}

.tooltip_content {
  position: absolute;
  background-color: #333;
  color: #fff;
  padding: 8px;
  border-radius: 4px;
  opacity: 0;
  visibility: hidden;
  white-space: nowrap;
  transition: opacity 0.3s ease-in-out;
  z-index: 9999;
}

.tooltip_content::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border-width: 5px;
  border-style: solid;
  border-color: #333 transparent transparent transparent;
}


.tooltip_container:hover .tooltip_content {
  opacity: 1;
  visibility: visible;
  transform: translateY(-18%);
}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Pure JS Tooltip</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div class="parent_class">
    <div class="child_level_one">
      <div class="child_level_two">
        <div class="child_level_three">
          <div class="child_level_four">
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
            <div class="tooltip_container" data-tooltip="Overflow Tooltip Text">
              <button>Hover Me</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <script src="script.js"></script>
</body>
</html>

Upvotes: 0

Related Questions