如何使用 CSS 和 JavaScript 创建动态弹出窗口

介绍

弹出框是一种常见的 UI 模式,用于显示上下文信息或提供快速访问操作的功能。它们通常用于仪表板、数据表或表单。在本教程中,我将向您展示如何使用 **CSS** 和 **JavaScript** 创建 **完全动态的弹出框**。

该解决方案旨在应对以下挑战:

  • 根据视口动态定位,防止溢出。
  • 指向触发元素的箭头对齐。
  • 滚动或与页面交互时的行为流畅。
  • 在本教程结束时,您将拥有一个可重复使用且响应迅速的弹出窗口实现。

    步骤 1:HTML 结构

    首先,我们假设一个基本的 HTML 结构,单击 **SVG** 图标即可触发弹出窗口。以下是列标题组的示例,您可能在数据表中找到此类标题组:

    ... Example Title
    ``` The **popover** will be dynamically created and injected into the DOM when the user clicks on the `` 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 */ }

    弹出箭头样式

    箭头是沿弹出窗口边缘放置的旋转小方块。默认情况下,箭头指向下方。

    .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 */
    }

    箭头位置调整

    根据弹出窗口的位置,箭头的位置需要动态变化。以下类可调整箭头的位置:

    **顶部箭头**

    当弹出窗口位于触发元素下方时,箭头指向上方。

    .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 */
    }

    **右侧箭头**

    当弹出窗口与触发元素左侧对齐时,箭头指向右侧。

    .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 */
    }

    动态箭头定位

    使用这些样式,箭头可以根据**弹出框的位置**动态调整其位置。这为与您的 UI 交互的用户创造了无缝且响应迅速的体验。

    步骤 3:添加 JavaScript 以实现动态行为

    为了使弹出窗口具有交互性和动态定位性,我们将使用 JavaScript。此脚本负责在用户与页面交互时创建、定位和清理弹出窗口。

    初始设置

    脚本首先选择所有将充当弹出窗口触发器的 SVG 图标。我们还维护一个状态变量“isPopoverOpen”,以跟踪弹出窗口当前是否显示。

    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 = '
    Popover Content
    '; popoverElement.classList.add('popover-container'); document.body.appendChild(popoverElement); const popoverHTML = popoverElement.querySelector('.popover');

    弹出框定位逻辑

    `positionPopover` 函数根据触发器的位置(`svgIcon`)计算弹出窗口的理想位置,并确保其适合视口。箭头的位置也会动态调整。

    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

    处理滚动事件

    为了让弹出窗口在用户滚动时保持正确位置,我们添加了一个“scroll”事件监听器:

    const handleScroll = () => {
            if (isPopoverOpen) {
              positionPopover();
            }
          };
    
          window.addEventListener('scroll', handleScroll);

    清理:关闭弹出窗口

    当用户点击弹出窗口外部或触发器不再可见时,应移除弹出窗口。这通过两个事件监听器进行管理:

    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);
         }
       });
    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);

    步骤 4:测试 Popover

    在为弹出窗口实现 **HTML**、**CSS** 和 **JavaScript** 后,测试其在不同场景下的行为非常重要。请按照以下步骤确保您的弹出窗口按预期工作。

    1. 基本功能

  • 触发弹出窗口:单击 SVG 图标。验证弹出窗口是否出现。确认弹出窗口根据视口正确定位且不会溢出。
  • 关闭弹出窗口:单击弹出窗口外部。验证弹出窗口是否已从 DOM 中移除。
  • 2.动态定位

  • 视口边界:将触发元素放置在屏幕边缘附近进行测试:顶部边缘:验证弹出窗口是否出现在元素下方,箭头指向上方。底部边缘:验证弹出窗口是否出现在元素上方,箭头指向下方。左侧边缘:验证弹出窗口是否出现在元素右侧,箭头指向左侧。右侧边缘:验证弹出窗口是否出现在元素左侧,箭头指向右侧。
  • 滚动:打开弹出窗口并滚动页面。确保弹出窗口动态调整其位置并与触发元素保持对齐。
  • 3.响应能力

  • 在不同的屏幕尺寸(台式机、平板电脑和手机)上进行测试。
  • 验证弹出窗口是否仍然可用,并在较小的屏幕上正确调整其位置。
  • 4. 边缘情况

  • 小视口:将浏览器窗口缩小到非常小的尺寸。验证弹出窗口是否能够适应并保持在视口内。
  • 隐藏触发器:滚动直到触发元素(SVG 图标)消失。验证当触发器不再可见时弹出窗口是否自动移除。
  • 多个弹出窗口:在页面上添加多个触发元素。验证单击一个触发器不会干扰其他触发器的行为。
  • 5. 最后的润色

  • 样式一致性:确保弹出窗口设计(背景、边框、箭头)与网站的整体主题相匹配。检查在位置之间转换时是否存在任何视觉故障(例如,从上到下或从左到右)。
  • 性能:在浏览器中打开开发者工具。监控控制台中的任何错误或警告。确保在添加或删除弹出窗口时没有不必要的重新渲染或内存泄漏。
  • 结论

    测试弹出窗口可确保在不同场景中实现无缝的用户体验。通过解决极端情况并确保响应能力,您的弹出窗口实现将非常可靠且可用于生产环境。

    让我知道这对您的项目有何作用,并随时分享您的见解或定制!