JavaScript 对话框式弹出提示框 PopoverTip 实现详解

在网页开发中,我们经常需要使用弹出框来展示额外的信息或操作选项。不同于模态对话框,弹出提示框(Popover)通常依附于触发元素,以更轻量的方式呈现内容。本文将详细解析一个简单的 JavaScript 对话框式弹出提示框的实现过程,并附带代码分析和最终效果展示。

HTML 结构

<div class="popover-wrapper">
    <button class="popover-trigger" data-placement="auto">自动弹出</button>
    <div class="popover-tip">
        <div style="width: 100px; height: auto;">这是弹出框的内容,可以根据需要调整大小。</div>
    </div>
</div>
  • .popover-wrapper:作为外部容器包裹触发元素和弹出框,利用相对定位方便弹出框定位。
  • .popover-trigger:触发弹出框的按钮,可以是任何元素。data-placement 属性控制弹出方向,支持 topbottomleftright 和 auto(自动判断)。
  • .popover-tip:弹出框容器,初始状态下隐藏,内部的 div 元素用于设置弹出框的尺寸和内容。

CSS 样式

.popover-tip {
    display: none; /* 默认隐藏 */
    position: absolute; /* 绝对定位 */
    visibility: hidden; /* 隐藏但不影响布局,用于获取尺寸 */
    background-color: #fff;
    border: 1px solid #ccc;
    padding: 10px;
    z-index: 10; /* 确保弹出框在上方 */
}

.popover-tip.show {
    display: block;
    visibility: visible;
}

.popover-tip::before { /* 使用伪元素创建三角形箭头 */
    content: '';
    position: absolute;
    border: 6px solid transparent;
    z-index: -1; /* 确保箭头在弹出框下方 */
}

.popover-tip.top::before {
    border-top-color: inherit; /* 箭头颜色继承弹出框边框颜色 */
    left: 50%;
    top: 100%;
    transform: translateX(-50%); /* 水平居中 */
}
/* 其他方向箭头样式类似,调整边框颜色和位置 */
  • .popover-tip:默认隐藏,使用绝对定位。设置背景色、边框、内边距等样式。visibility: hidden 确保在计算尺寸时不影响布局。
  • .popover-tip.show:显示弹出框,同时设置可见性。
  • .popover-tip::before:利用伪元素创建三角形箭头,根据不同弹出方向设置边框颜色和位置,实现箭头指向效果。

JavaScript 实现

const triggers = document.querySelectorAll('.popover-trigger');

triggers.forEach(trigger => {
    trigger.addEventListener('click', function(event) {
        const popover = this.nextElementSibling || this.parentElement.querySelector('.popover-tip');
        let placement = this.dataset.placement || 'top';

        // 计算弹出框位置
        const triggerRect = this.getBoundingClientRect();
        const windowHeight = window.innerHeight;
        const windowWidth = window.innerWidth;

        // 先将弹出框显示出来,以便获取其尺寸
        popover.style.display = 'block';
        const popoverRect = popover.getBoundingClientRect();

        // 计算初始位置(相对于按钮)
        let top = triggerRect.top + window.scrollY;
        let left = triggerRect.left + window.scrollX;

        // 根据位置和弹出框尺寸调整
        if (placement === 'auto') {
            // ... 自动判断最佳弹出方向的逻辑 ...
        } else {
            switch (placement) {
                case 'top':
                    top -= popoverRect.height + 8;
                    left += (triggerRect.width - popoverRect.width) / 2;
                    break;
                // ... 其他方向的定位逻辑 ...
            }
        }

        // 应用位置和显示弹出框
        popover.style.top = `${top}px`;
        popover.style.left = `${left}px`;
        popover.classList.add('show', placement);

        // 点击页面其他地方关闭弹出框
        document.addEventListener('click', function closePopover(event) {
            if (!trigger.contains(event.target) && !popover.contains(event.target)) {
                popover.classList.remove('show', 'top', 'bottom', 'left', 'right');
                document.removeEventListener('click', closePopover);
            }
        });

        event.stopPropagation(); // 阻止事件冒泡
    });
});
  1. 获取所有触发元素和绑定事件:使用 querySelectorAll 获取所有 class 为 .popover-trigger 的元素,并为每个元素绑定点击事件监听器。
  2. 获取弹出框元素和弹出方向:获取与触发元素关联的 .popover-tip元素,以及 data-placement 属性指定的弹出方向。
  3. 计算弹出框位置
    • 先将弹出框临时设置为 display: block,获取其尺寸信息。
    • 根据弹出方向和触发元素的位置计算弹出框的 top 和 left 值,确保弹出框相对于触发元素正确显示。
    • 自动弹出方向 (auto) 需要判断上下左右的可用空间,选择最佳的弹出方向。
  4. 应用位置和显示弹出框:设置弹出框的 top 和 left 样式,并添加 .show 类,使其显示出来。
  5. 点击空白处关闭弹出框:为 document 绑定点击事件,判断点击位置是否在触发元素或弹出框内部,如果不是则关闭弹出框。
  6. 阻止事件冒泡:使用 event.stopPropagation() 阻止事件冒泡,避免点击弹出框区域时触发 document 的点击事件,导致弹出框立即关闭。

效果展示

完整html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对话框样式的弹出提示框 PopoverTip </title>
    <style>
        .popover-wrapper {
            position: relative;
            display: inline-block;
        }
        .popover-tip {
            display: none;
            position: absolute;
            visibility: hidden; /* 隐藏,但会渲染 */
            background-color: #fff;
            border: 1px solid #ccc;
			   border-radius: 5px;
            padding: 10px;
            z-index: 10;
        }
        .popover-tip.show {
            display: block;
            visibility: visible;
        }
        .popover-tip::before {
            content: '';
            position: absolute;
            border: 6px solid transparent;
            z-index: -1;
        }
        /* 箭头样式 */
        .popover-tip.top::before {
            border-top-color: inherit;
            left: 50%;
            top: 100%;
            transform: translateX(-50%);
        }
        .popover-tip.bottom::before {
            border-bottom-color: inherit;
            left: 50%;
            bottom: 100%;
            transform: translateX(-50%);
        }
        .popover-tip.left::before {
            border-left-color: inherit;
            top: 50%;
            left: 100%;
            transform: translateY(-50%);
        }
        .popover-tip.right::before {
            border-right-color: inherit;
            top: 50%;
            right: 100%;
            transform: translateY(-50%);
        }
    </style>
</head>
<body style="height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;">
    <div class="popover-wrapper">
        <button class="popover-trigger" data-placement="auto">自动弹出</button>
        <div class="popover-tip">
            <div style="width: 100px; height: auto;">这是弹出框的内容,可以根据需要调整大小。</div>
        </div>
    </div>
    <div class="popover-wrapper">
        <button class="popover-trigger" data-placement="right">另一个弹出</button>
        <div class="popover-tip">
            <div style="width: 100px; height: auto;">这是弹出框的内容,可以根据需要调整大小。</div>
        </div>
    </div>
    <script>
        const triggers = document.querySelectorAll('.popover-trigger');
        triggers.forEach(trigger => {
            trigger.addEventListener('click', function(event) {
                const popover = this.nextElementSibling || this.parentElement.querySelector('.popover-tip');
                let placement = this.dataset.placement || 'top';
                // 计算弹出框位置
                const triggerRect = this.getBoundingClientRect();
                const windowHeight = window.innerHeight;
                const windowWidth = window.innerWidth;
                // 先将弹出框显示出来,以便获取其尺寸
                popover.style.display = 'block';
                const popoverRect = popover.getBoundingClientRect();
                let top = 0;//triggerRect.top;
                let left = 0;//triggerRect.left;
                // 自动调整位置
                if (placement === 'auto') {
                    const spaceAbove = triggerRect.top;
                    const spaceBelow = windowHeight - triggerRect.bottom;
                    const spaceLeft = triggerRect.left;
                    const spaceRight = windowWidth - triggerRect.right;
                    if (spaceAbove >= popoverRect.height && spaceAbove >= spaceBelow) {
                        placement = 'top';
                        top -= popoverRect.height + 8; // 8px 箭头偏移
                        // 水平居中
                        left += (triggerRect.width - popoverRect.width) / 2;
                    } else if (spaceBelow >= popoverRect.height) {
                        placement = 'bottom';
                        top += triggerRect.height + 8;
                        // 水平居中
                        left += (triggerRect.width - popoverRect.width) / 2;
                    } else if (spaceLeft >= popoverRect.width && spaceLeft >= spaceRight) {
                        placement = 'left';
                        left -= popoverRect.width + 8;
                        // 垂直居中
                        top += (triggerRect.height - popoverRect.height) / 2;
                    } else if (spaceRight >= popoverRect.width) {
                        placement = 'right';
                        left += triggerRect.width + 8;
                        // 垂直居中
                        top += (triggerRect.height - popoverRect.height) / 2;
                    } else {
                        // 如果空间不足,默认顶部弹出
                        placement = 'top';
                        top -= popoverRect.height + 8; // 8px 箭头偏移
                        // 水平居中
                        left += (triggerRect.width - popoverRect.width) / 2;
                    }
                } else {
                    // 非自动模式下,根据选择的位置调整
                    switch (placement) {
                        case 'top':
                            top -= popoverRect.height + 8;
                            // 水平居中
                            left += (triggerRect.width - popoverRect.width) / 2;
                            break;
                        case 'bottom':
                            top += triggerRect.height + 8;
                            // 水平居中
                            left += (triggerRect.width - popoverRect.width) / 2;
                            break;
                        case 'left':
                            left -= popoverRect.width + 8;
                            // 垂直居中
                            top += (triggerRect.height - popoverRect.height) / 2;
                            break;
                        case 'right':
                            left += triggerRect.width + 8;
                            // 垂直居中
                            top += (triggerRect.height - popoverRect.height) / 2;
                            break;
                    }
                }
                // 应用位置和显示弹出框
                popover.style.top = `${top}px`;
                popover.style.left = `${left}px`;
                popover.classList.add('show', placement);
                // 点击页面其他地方关闭弹出框
                document.addEventListener('click', function closePopover(event) {
                    if (!trigger.contains(event.target) && !popover.contains(event.target)) {
                        popover.classList.remove('show', 'top', 'bottom', 'left', 'right');
                        document.removeEventListener('click', closePopover);
                    }
                });
                event.stopPropagation();
            });
        });
    </script>
</body>
</html>

最终效果是一个功能完善的弹出提示框组件,可以根据触发元素的位置自动调整弹出方向,并支持点击空白处关闭。

总结

本文深入解析了如何使用 HTML、CSS 和 JavaScript 实现一个灵活且易于使用的弹出提示框组件。该组件结构清晰,代码易懂,并具备自动调整弹出方向、点击空白处关闭等实用功能,可以作为学习 JavaScript 交互效果开发的良好案例。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值