JS实现:计算被遮挡矩形DOM元素的可见百分比
计算元素未遮挡区域百分比的高效解决方案
问题分析
你需要计算id为a的元素未被其他元素遮挡的区域占比,当前逐像素扫描的方式效率太低,而且遇到pointer-events: none时document.elementFromPoint还会失效,确实得换更高效的几何计算思路——毕竟题目已经明确所有元素都是矩形,这正好可以发挥几何运算的优势。
核心思路:基于矩形交集的几何计算
我们可以通过计算目标元素与其他元素的交集区域,再用目标元素总面积减去所有交集区域的并集面积(避免重复计算重叠遮挡的部分),最后除以总面积得到未遮挡百分比。这种方法完全不需要逐像素扫描,效率提升非常显著。
步骤1:获取元素边界数据
首先用getBoundingClientRect()获取目标元素和页面其他元素的精确位置与尺寸,这是所有计算的基础:
const target = document.getElementById('a'); const targetRect = target.getBoundingClientRect(); const targetTotalArea = targetRect.width * targetRect.height; // 筛选出所有可能遮挡目标的元素(排除目标自身) const allElements = Array.from(document.querySelectorAll('body *')).filter(el => el !== target); const elementRects = allElements.map(el => el.getBoundingClientRect());
步骤2:计算单元素与目标的交集面积
编写工具函数,判断两个矩形是否相交,并计算交集区域的面积:
function getIntersectionArea(rectA, rectB) { const overlapLeft = Math.max(rectA.left, rectB.left); const overlapTop = Math.max(rectA.top, rectB.top); const overlapRight = Math.min(rectA.right, rectB.right); const overlapBottom = Math.min(rectA.bottom, rectB.bottom); const overlapWidth = overlapRight - overlapLeft; const overlapHeight = overlapBottom - overlapTop; // 宽度或高度为负则无交集,返回0 return overlapWidth > 0 && overlapHeight > 0 ? overlapWidth * overlapHeight : 0; }
步骤3:处理多元素遮挡的去重(关键)
如果多个遮挡元素互相重叠,直接累加交集面积会重复计算重叠区域,这里用扫描线算法计算所有交集区域的并集面积,确保每个遮挡区域只被计算一次:
function calculateUnionArea(rects) { if (rects.length === 0) return 0; // 收集所有矩形的垂直边界(x坐标、边界类型、对应的y区间) const edges = []; rects.forEach(rect => { edges.push({ x: rect.left, type: 'start', y1: rect.top, y2: rect.bottom }); edges.push({ x: rect.right, type: 'end', y1: rect.top, y2: rect.bottom }); }); // 按x坐标排序,左边界优先处理 edges.sort((a, b) => a.x - b.x || (a.type === 'start' ? -1 : 1)); let totalUnionArea = 0; let prevX = edges[0].x; const activeYIntervals = []; for (const edge of edges) { const currentX = edge.x; const intervalWidth = currentX - prevX; if (intervalWidth > 0 && activeYIntervals.length > 0) { // 计算当前x区间内,被遮挡的y轴总高度 const totalOccludedHeight = calculateTotalHeight(activeYIntervals); totalUnionArea += intervalWidth * totalOccludedHeight; } // 更新活跃的y区间(添加或移除) if (edge.type === 'start') { activeYIntervals.push([edge.y1, edge.y2]); // 排序后合并重叠/相邻区间,简化后续计算 activeYIntervals.sort((a, b) => a[0] - b[0]); Object.assign(activeYIntervals, mergeIntervals(activeYIntervals)); } else { // 移除对应结束的y区间 const removeIndex = activeYIntervals.findIndex( interval => interval[0] === edge.y1 && interval[1] === edge.y2 ); if (removeIndex !== -1) activeYIntervals.splice(removeIndex, 1); } prevX = currentX; } return totalUnionArea; } // 计算合并后y区间的总高度 function calculateTotalHeight(intervals) { let totalHeight = 0; let prevEnd = -Infinity; for (const [start, end] of intervals) { if (start > prevEnd) { totalHeight += end - start; prevEnd = end; } else if (end > prevEnd) { totalHeight += end - prevEnd; prevEnd = end; } } return totalHeight; } // 合并重叠或相邻的y区间 function mergeIntervals(intervals) { const merged = []; for (const interval of intervals) { if (!merged.length || merged[merged.length - 1][1] < interval[0]) { merged.push(interval); } else { merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], interval[1]); } } return merged; }
步骤4:计算最终未遮挡百分比
收集所有与目标元素有交集的矩形,计算它们的并集面积,最后推导未遮挡占比:
// 筛选出所有与目标有交集的矩形,并裁剪到目标元素范围内 const intersectingRects = elementRects .map(rect => ({ left: Math.max(targetRect.left, rect.left), top: Math.max(targetRect.top, rect.top), right: Math.min(targetRect.right, rect.right), bottom: Math.min(targetRect.bottom, rect.bottom) })) .filter(rect => rect.right > rect.left && rect.bottom > rect.top); const totalOccludedArea = calculateUnionArea(intersectingRects); const unoccludedPercentage = ((targetTotalArea - totalOccludedArea) / targetTotalArea) * 100; console.log(`未遮挡百分比:${unoccludedPercentage.toFixed(2)}%`);
处理pointer-events: none的情况
这个方案完全不依赖document.elementFromPointAPI!不管元素的pointer-events属性设置是什么,只要能通过getBoundingClientRect()获取到它的边界数据,就能正常计算交集,完美解决了pointer-events: none导致的检测失效问题。
内容的提问来源于stack exchange,提问作者Shlomi Schwartz
相关产品推荐
相关产品推荐

