DEV Community

A0mineTV
A0mineTV

Posted on

How to Create a Dynamic Popover with CSS and JavaScript 🚀

Introduction

Popovers are a common UI pattern used to display contextual information or provide quick access to actions. They are often used in dashboards, data tables, or forms. In this tutorial, I'll show you how to create a fully dynamic popover using CSS and JavaScript.

This solution is designed to handle the following challenges:

  • Dynamic positioning based on the viewport to prevent overflow.
  • Arrow alignment pointing to the triggering element.
  • Smooth behavior when scrolling or interacting with the page.

By the end of this tutorial, you will have a reusable and responsive popover implementation.


Step 1: HTML Structure

To start, we assume a basic HTML structure where the popover will be triggered by clicking an SVG icon. Here’s an example of a column title group, such as you might find in a data table:

<div class="dt-column-title-group">
  <svg>...</svg>
  <span class="dt-column-title">Example Title</span>
</div>```



The **popover** will be dynamically created and injected into the DOM when the user clicks on the `<svg>` icon.

---

## Step 2: Styling the Popover with CSS

To ensure the popover looks clean and professional, we’ll style it using CSS. The following code includes the base styles for the popover and the arrow, along with dynamic adjustments to the arrow’s position (top, bottom, left, or right).

### Popover Base Styles

The base styles define the shape, background, border, and shadow of the popover:



```css
.popover {
  position: relative; /* Ensures the arrow can position relative to this */
  width: 100%; /* Makes the popover content fit dynamically */
  border-radius: 0.375rem; /* Rounded corners for a softer look */
  border: 1px solid #E2E8F0; /* Subtle border for contrast */
  background-color: #fff; /* White background for clean content display */
  box-shadow: 0 0.5em 1em -0.125em #0a0a0a1a, 0 0 0 1px #0a0a0a05; /* Light shadow for depth */
  z-index: 1001; /* Ensure it's above other elements */
}
Enter fullscreen mode Exit fullscreen mode

 Popover Arrow Styles

The arrow is a small rotated square positioned along the edge of the popover. By default, the arrow points downward.

.popover:after {
  content: "";
  background-color: #fff; /* Matches the popover's background */
  border-color: #E2E8F0; /* Matches the popover's border */
  border-right-width: 1px;
  border-bottom-width: 1px;
  height: 1rem;
  width: 1rem;
  position: absolute;
  bottom: -0.5rem; /* Positioned below the popover */
  left: 50%; /* Centered horizontally */
  transform: translateX(-50%) rotate(45deg); /* Creates the arrow shape */
}
Enter fullscreen mode Exit fullscreen mode

Arrow Position Adjustments

Depending on the popover’s position, the arrow’s location needs to change dynamically. The following classes adjust the arrow’s placement:

Arrow on Top

When the popover is positioned below the triggering element, the arrow points upward.

.popover.arrow-top:after {
  bottom: auto; /* Remove the default bottom positioning */
  top: -0.5rem; /* Position the arrow above the popover */
  border-bottom-width: 0; /* Hide the bottom border */
  border-top-width: 1px; /* Show the top border */
}
Enter fullscreen mode Exit fullscreen mode

Arrow on the Right

When the popover is aligned to the left of the triggering element, the arrow points to the right.

.popover.arrow-right:after {
  top: 50%; /* Vertically centered */
  left: 97%; /* Positioned to the right of the popover */
  bottom: auto; /* Remove the default bottom positioning */
  transform: translateY(-50%) rotate(45deg); /* Keeps the arrow correctly aligned */
  border-right-width: 1px; /* Show the right border */
  border-left-width: 0; /* Hide the left border */
}
Enter fullscreen mode Exit fullscreen mode

Dynamic Arrow Positioning

With these styles, the arrow can dynamically adjust its position based on the popover’s placement. This creates a seamless and responsive experience for users interacting with your UI.


Step 3: Adding JavaScript for Dynamic Behavior

To make the popover interactive and dynamically positioned, we’ll use JavaScript. This script handles the creation, positioning, and cleanup of the popover as users interact with the page.

Initial Setup

The script begins by selecting all SVG icons that will act as popover triggers. We also maintain a state variable, isPopoverOpen, to track whether the popover is currently displayed.

document.querySelectorAll('.dt-column-title-group svg').forEach((svgIcon) => {
  let isPopoverOpen = false;

  svgIcon.addEventListener('click', (event) => {
    event.stopPropagation(); // Prevent event propagation to avoid unwanted behavior

    let popoverContainer = document.querySelector('.popover-container');
    if (isPopoverOpen) {
      // If the popover is open, remove it
      popoverContainer?.remove();
      isPopoverOpen = false;
      return;
    }

    if (!popoverContainer) {
      const popoverElement = document.createElement('div');
      popoverElement.innerHTML = '<div class="popover">Popover Content</div>';
      popoverElement.classList.add('popover-container');
      document.body.appendChild(popoverElement);

      const popoverHTML = popoverElement.querySelector('.popover');
Enter fullscreen mode Exit fullscreen mode

Popover Positioning Logic

The positionPopover function calculates the popover’s ideal position based on the trigger's location (svgIcon) and ensures it fits within the viewport. The arrow's placement is also adjusted dynamically.

      const positionPopover = () => {
        const targetRect = svgIcon.getBoundingClientRect();
        let topPosition = targetRect.top - popoverElement.offsetHeight - 20; // Default: above the element
        let leftPosition = targetRect.left + targetRect.width / 2 - popoverElement.offsetWidth / 2;

        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;

        // Reset arrow classes
        popoverHTML?.classList.remove('arrow-top', 'arrow-bottom', 'arrow-left', 'arrow-right');

        // Adjust if the popover exceeds the top boundary
        if (topPosition < 0) {
          topPosition = targetRect.bottom + 10; // Place below the element
          popoverHTML?.classList.add('arrow-top'); // Arrow on top
        } else {
          popoverHTML?.classList.add('arrow-bottom'); // Arrow on bottom
        }

        // Adjust if the popover exceeds the bottom boundary
        if (topPosition + popoverElement.offsetHeight > viewportHeight) {
          topPosition = targetRect.top - popoverElement.offsetHeight - 10; // Place above the element
          popoverHTML?.classList.remove('arrow-bottom');
          popoverHTML?.classList.add('arrow-top'); // Arrow on top
        }

        // Adjust if the popover exceeds the right boundary
        if (leftPosition + popoverElement.offsetWidth > viewportWidth) {
          leftPosition = targetRect.left - popoverElement.offsetWidth - 20; // Place to the left
          topPosition = targetRect.top; // Align with the top of the trigger
          popoverHTML?.classList.remove('arrow-bottom', 'arrow-top');
          popoverHTML?.classList.add('arrow-right'); // Arrow on the right
        }

        // Adjust if the popover exceeds the left boundary
        if (leftPosition < 0) {
          leftPosition = targetRect.right + 20; // Place to the right
          topPosition = targetRect.top; // Align with the top of the trigger
          popoverHTML?.classList.remove('arrow-bottom', 'arrow-top');
          popoverHTML?.classList.add('arrow-left'); // Arrow on the left
        }

        popoverElement.style.position = 'fixed';
        popoverElement.style.top = `${topPosition}px`;
        popoverElement.style.left = `${leftPosition}px`;
      };

      positionPopover(); // Initial positioning
Enter fullscreen mode Exit fullscreen mode

Handling Scroll Events

To keep the popover correctly positioned when the user scrolls, we add a scroll event listener:

      const handleScroll = () => {
        if (isPopoverOpen) {
          positionPopover();
        }
      };

      window.addEventListener('scroll', handleScroll);
Enter fullscreen mode Exit fullscreen mode

Cleanup: Closing the Popover

The popover should be removed when the user clicks outside of it or when the trigger is no longer visible. This is managed with two event listeners:

   document.addEventListener('click', function handleClickOutside(event) {
     if (!popoverElement.contains(event.target) && !svgIcon.contains(event.target)) {
       popoverElement.remove();
       isPopoverOpen = false;
       document.removeEventListener('click', handleClickOutside);
       window.removeEventListener('scroll', handleScroll);
     }
   });
Enter fullscreen mode Exit fullscreen mode
   const observer = new IntersectionObserver((entries) => {
     entries.forEach((entry) => {
       if (!entry.isIntersecting) {
         popoverElement.remove();
         isPopoverOpen = false;
         observer.disconnect();
         window.removeEventListener('scroll', handleScroll);
       }
     });
   }, { threshold: 0.1 });

   observer.observe(svgIcon);
Enter fullscreen mode Exit fullscreen mode

Step 4: Testing the Popover

After implementing the HTML, CSS, and JavaScript for the popover, it's important to test its behavior in different scenarios. Follow these steps to ensure your popover works as expected.


1. Basic Functionality

  • Triggering the Popover:

    • Click the SVG icon.
    • Verify that the popover appears.
    • Confirm the popover is positioned correctly based on the viewport and does not overflow.
  • Closing the Popover:

    • Click outside the popover.
    • Verify that the popover is removed from the DOM.

2. Dynamic Positioning

  • Viewport Boundaries:

    • Test with the triggering element placed near the edges of the screen:
    • Top Edge: Verify the popover appears below the element with the arrow pointing upward.
    • Bottom Edge: Verify the popover appears above the element with the arrow pointing downward.
    • Left Edge: Verify the popover appears to the right of the element with the arrow pointing to the left.
    • Right Edge: Verify the popover appears to the left of the element with the arrow pointing to the right.
  • Scrolling:

    • Open the popover and scroll the page.
    • Ensure the popover adjusts its position dynamically and remains aligned with the triggering element.

3. Responsiveness

  • Test on different screen sizes (desktop, tablet, and mobile).
  • Verify that the popover remains functional and adjusts its position properly on smaller screens.

4. Edge Cases

  • Small Viewports:

    • Shrink the browser window to a very small size.
    • Verify that the popover adapts to remain within the viewport.
  • Hidden Trigger:

    • Scroll until the triggering element (SVG icon) goes out of view.
    • Verify that the popover is automatically removed when the trigger is no longer visible.
  • Multiple Popovers:

    • Add multiple triggering elements on the page.
    • Verify that clicking on one trigger does not interfere with the behavior of others.

5. Final Touches

  • Styling Consistency:

    • Ensure the popover design (background, border, arrow) matches the overall theme of your website.
    • Check for any visual glitches when transitioning between positions (e.g., top to bottom or left to right).
  • Performance:

    • Open the developer tools in your browser.
    • Monitor for any errors or warnings in the console.
    • Ensure there are no unnecessary re-renders or memory leaks when adding or removing the popover.

Conclusion

Testing the popover ensures a seamless user experience across different scenarios. By addressing edge cases and ensuring responsiveness, your popover implementation will be robust and production-ready.

Let me know how this worked for your project, and feel free to share your insights or customizations!

Top comments (0)