如何筛选网页上可见的HTML节点并提取字体信息
在前端开发、页面分析、自动化测试等场景中,我们常常需要筛选出网页上用户可见的HTML节点,并提取这些节点使用的字体相关属性。本文将介绍完整的实现思路与具体代码方案。
一、核心需求分析
要实现可见节点筛选和字体信息提取,需要解决两个核心问题:
如何判断一个HTML节点是否在页面上可见
如何获取节点生效的字体相关样式属性
二、可见性判断规则
一个节点被认为是可见的,需要同时满足以下条件:
节点的宽高均大于0,没有设置 display: none 样式
节点的 visibility 属性不为 hidden
节点的 opacity 大于0
节点没有被其他元素完全遮挡(可选,视场景复杂度决定是否实现)
三、基础实现方案
3.1 判断节点是否可见
我们可以通过 getComputedStyle 方法获取节点的最终计算样式,结合节点的尺寸属性完成可见性判断:
/**
* 判断HTML节点是否可见
* @param {HTMLElement} node 待检测的节点
* @returns {boolean} 是否可见
*/
function isNodeVisible(node) {
if (!node || !(node instanceof HTMLElement)) {
return false;
}
// 获取计算样式
const style = window.getComputedStyle(node);
// 检查display、visibility、opacity
if (style.display === 'none') return false;
if (style.visibility === 'hidden') return false;
if (parseFloat(style.opacity) <= 0) return false;
// 检查节点尺寸
const rect = node.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return false;
return true;
}3.2 提取节点字体信息
字体相关属性同样可以通过 getComputedStyle 获取,常见的字体属性包括 font-family、font-size、font-weight、font-style、line-height 等:
/**
* 提取节点的字体信息
* @param {HTMLElement} node 待提取的节点
* @returns {Object} 字体属性对象
*/
function getNodeFontInfo(node) {
if (!node || !(node instanceof HTMLElement)) {
return null;
}
const style = window.getComputedStyle(node);
return {
fontFamily: style.fontFamily,
fontSize: style.fontSize,
fontWeight: style.fontWeight,
fontStyle: style.fontStyle,
lineHeight: style.lineHeight,
letterSpacing: style.letterSpacing
};
}3.3 筛选可见节点并提取字体信息
我们可以遍历页面上的所有元素节点,先过滤出可见节点,再批量提取字体信息:
/**
* 筛选页面可见节点并提取字体信息
* @returns {Array} 可见节点及其字体信息数组
*/
function getVisibleNodesFontInfo() {
const allNodes = document.querySelectorAll('*');
const result = [];
allNodes.forEach(node => {
if (isNodeVisible(node)) {
const fontInfo = getNodeFontInfo(node);
result.push({
node: node,
tagName: node.tagName.toLowerCase(),
classList: Array.from(node.classList),
fontInfo: fontInfo
});
}
});
return result;
}四、进阶优化
4.1 排除不可见后代节点
上述方案会遍历所有节点,包括可见节点内部不可见的子节点,我们可以添加过滤逻辑,只保留最外层可见节点,或者按需保留所有可见节点:
/**
* 筛选最外层可见节点(排除可见节点内部的其他可见子节点)
* @returns {Array} 最外层可见节点数组
*/
function getTopVisibleNodes() {
const allNodes = Array.from(document.querySelectorAll('*'));
const visibleNodes = allNodes.filter(node => isNodeVisible(node));
// 如果节点的父节点也在可见节点列表中,说明该节点是内部节点,排除
return visibleNodes.filter(node => {
let parent = node.parentElement;
while (parent) {
if (visibleNodes.includes(parent)) {
return false;
}
parent = parent.parentElement;
}
return true;
});
}4.2 处理节点遮挡场景
如果需要精确判断节点是否被完全遮挡,可以通过 document.elementFromPoint 方法检测节点区域内的元素层级:
/**
* 增强可见性判断,检查节点是否被完全遮挡
* @param {HTMLElement} node 待检测的节点
* @returns {boolean} 是否可见
*/
function isNodeVisibleEnhanced(node) {
if (!isNodeVisible(node)) return false;
const rect = node.getBoundingClientRect();
// 取节点中心点和四个角进行检测
const points = [
{ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
{ x: rect.left + 1, y: rect.top + 1 },
{ x: rect.right - 1, y: rect.top + 1 },
{ x: rect.left + 1, y: rect.bottom - 1 },
{ x: rect.right - 1, y: rect.bottom - 1 }
];
for (const point of points) {
const topElement = document.elementFromPoint(point.x, point.y);
// 如果检测到的顶层元素不是当前节点或其后代,说明被遮挡
if (!node.contains(topElement)) {
return false;
}
}
return true;
}五、使用示例
在浏览器控制台中执行以下代码,即可获取页面所有可见节点的字体信息:
// 获取所有可见节点字体信息
const visibleFontList = getVisibleNodesFontInfo();
console.log('可见节点字体信息:', visibleFontList);
// 获取最外层可见节点字体信息
const topVisibleNodes = getTopVisibleNodes();
const topVisibleFontList = topVisibleNodes.map(node => ({
node: node,
tagName: node.tagName.toLowerCase(),
fontInfo: getNodeFontInfo(node)
}));
console.log('最外层可见节点字体信息:', topVisibleFontList);六、注意事项
遍历所有 * 节点在节点数量较多的页面(如超过1万个节点)可能会有性能损耗,可根据场景缩小遍历范围,比如只遍历特定容器下的节点
getComputedStyle返回的属性值都是字符串格式,如需数值计算需要先进行类型转换部分节点(如 <script>、<style>、<meta> 等)不会渲染在页面上,遍历时可以提前排除这些标签类型
如果页面有跨域iframe,无法直接访问iframe内部的节点,需要单独处理同源的iframe内容
七、扩展场景
上述方案还可以结合具体需求扩展,例如:
如果需要导出字体信息到本地,可以将结果转换为JSON格式,通过
Blob和URL.createObjectURL生成下载链接,下载地址示例参考 https://www.ipipp.com如果需要分析特定路径下的字体使用情况,可以修改遍历范围,仅筛选指定容器下的节点,例如
document.querySelector('.content').querySelectorAll('*')如果需要统计不同字体的使用占比,可以对提取到的 fontFamily 进行去重和计数处理