H5实现网络信号检测全解析(附源码)

一、需求背景与方案目标

1.1 核心需求

传统H5网络检测多依赖境外资源测速,国内访问时因跨境链路问题导致结果失真;同时,文字化的"弱/中/强"网络等级提示不够直观。因此,需要一套满足以下要求的方案:

  • 适配国内网络环境,基于国内CDN节点测速,结果贴近真实使用体验;
  • 采用信号格可视化呈现网络强弱,符合移动端用户认知习惯;
  • 支持离线检测与网络状态实时监听;
  • 纯前端实现,无额外后端依赖,可直接集成到各类H5项目。

1.2 方案目标

实现"国内精准测速+信号格可视化+实时状态监听"的一体化网络检测功能,提供清晰的网络等级提示、详细的测速数据及针对性的用户体验建议。

二、核心实现原理

本方案核心围绕"基础网络信息获取+国内节点实际测速+信号格可视化渲染"三大模块展开,各模块协同工作确保检测精准度与视觉体验。

2.1 基础网络信息获取:Network Information API

利用浏览器提供的Network Information API(网络信息API)获取基础网络参数,包括网络类型(Wi-Fi/4G/5G等)、预估下行带宽、往返延迟等。该API为测速提供基础参考,但其兼容性存在差异(支持Chrome、Edge等,Safari暂不支持),需做好降级处理。

2.2 国内节点实际测速:HTTP请求测速法

针对境外资源测速不准的问题,选取国内主流CDN节点(阿里云、腾讯云、字节跳动)的静态资源作为测试对象。通过请求固定大小的资源,计算下载耗时并换算实际网速,同时采用"多节点重试+多次测速取平均"策略,减少网络波动对结果的影响。

2.3 信号格可视化:纯CSS绘制

摒弃传统文字提示,采用4格信号栏(符合移动端常见设计规范)可视化呈现网络等级。通过CSS控制信号栏的点亮数量与颜色:强网(4格全亮,绿色)、中网(3格点亮,黄色)、弱网(1格点亮,红色)、离线(全灰),纯CSS实现无需额外图片资源,加载更快且适配高清屏。

三、关键技术细节拆解

3.1 国内测试资源选型与优化

测试资源的选择直接影响测速精准度,需满足"国内节点覆盖广、资源稳定、大小固定"三大条件。本方案选取3个国内主流CDN节点资源作为备选:

rust 复制代码
const TEST_RESOURCES = [
  // 阿里云图标CDN(约10KB,国内节点覆盖全)
  'https://img.alicdn.com/tps/i3/TB1_oz6GVXXXXaFXVXXJDFnIXXX-64-64.png?random=',
  // 腾讯云CDN(约50KB,资源稳定)
  'https://qzonestyle.gtimg.cn/qzone/v8/img/loading.gif?random=',
  // 字节跳动CDN(约20KB,移动端适配优)
  'https://p3-passport.byteacctimg.com/img/user-avatar/8499999999999999~300x300.image?random='
];

优化点:通过random=参数避免浏览器缓存,同时设置cache: 'no-cache'请求头,确保每次测速都获取最新资源;多节点备选机制可应对单个节点失效问题,提升测速成功率。

3.2 精准测速逻辑实现

采用"多次测速取平均"策略减少网络波动影响,默认测速2次,计算有效测速结果的平均值。核心逻辑如下:

  1. 循环选取备选测试资源,记录请求开始时间;
  2. 通过fetch请求资源,获取响应头中的Content-Length(资源大小);
  3. 计算请求耗时,换算实际网速(单位:Mbps);
  4. 过滤无效测速结果,计算平均速度;若无有效结果,使用Network Information API的预估带宽兜底。
ini 复制代码
async function testRealSpeed() {
  let totalSpeed = 0;
  let validTests = 0;
  const testCount = 2; // 测速2次取平均

  for (let i = 0; i < testCount; i++) {
    const resourceUrl = TEST_RESOURCES[i % TEST_RESOURCES.length] + Math.random();
    const startTime = performance.now();
    
    try {
      const response = await fetch(resourceUrl, {
        method: 'GET',
        cache: 'no-cache',
        mode: 'cors'
      });

      if (!response.ok) throw new Error('资源请求失败');
      
      const contentLength = response.headers.get('Content-Length');
      const endTime = performance.now();
      const duration = (endTime - startTime) / 1000; // 耗时(秒)
      
      if (contentLength && duration > 0) {
        const sizeKB = parseInt(contentLength) / 1024; // 资源大小(KB)
        const speedMbps = (sizeKB * 8) / (1024 * duration); // 换算为Mbps
        totalSpeed += speedMbps;
        validTests++;
      }
    } catch (error) {
      console.warn(`第${i+1}次测速失败(节点切换):`, error);
      continue;
    }
  }

  // 无有效测速时,用预估带宽兜底
  if (validTests === 0) {
    const { downlink } = getNetworkType();
    return { speed: downlink || 0, duration: 0 };
  }

  return {
    speed: totalSpeed / validTests,
    duration: (performance.now() - (performance.now() - totalSpeed)) / 1000
  };
}

3.3 信号格可视化CSS实现

通过flex布局实现信号格的纵向排列,利用不同的CSS类控制信号格的点亮状态与颜色。核心CSS代码如下:

css 复制代码
/* 信号格容器 */
.signal-bars {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 30px;
}
/* 单个信号格基础样式 */
.signal-bar {
  width: 6px;
  background: #e0e0e0;
  border-radius: 2px;
  transition: background 0.3s ease;
}
/* 4格信号高度梯度 */
.signal-bar:nth-child(1) { height: 8px; }
.signal-bar:nth-child(2) { height: 15px; }
.signal-bar:nth-child(3) { height: 22px; }
.signal-bar:nth-child(4) { height: 30px; }

/* 不同网络等级的信号格样式 */
.signal-strong .signal-bar { background: #2e7d32; } /* 强网:4格全绿 */
.signal-medium .signal-bar:nth-child(1),
.signal-medium .signal-bar:nth-child(2),
.signal-medium .signal-bar:nth-child(3) { background: #ff8f00; } /* 中网:前3格黄 */
.signal-weak .signal-bar:nth-child(1) { background: #c62828; } /* 弱网:第1格红 */
.signal-offline .signal-bar { background: #e0e0e0; } /* 离线:全灰 */

通过动态切换容器的CSS类(signal-strong/signal-medium/signal-weak/signal-offline),即可实现信号格的状态切换,配合文字提示形成双重反馈。

3.4 网络状态实时监听

利用window.onlinewindow.offline事件监听网络连接状态变化,当网络从离线恢复或变为离线时,及时更新信号格样式与提示文字:

ini 复制代码
// 网络恢复
window.addEventListener('online', () => {
  statusEl.className = 'network-status';
  statusTextEl.textContent = '网络已恢复,点击检测查看信号';
});

// 网络离线
window.addEventListener('offline', () => {
  statusEl.className = 'network-status signal-offline';
  statusTextEl.textContent = '已离线';
  speedInfoEl.innerHTML = '';
});

四、完整代码解析与集成指南

4.1 完整代码结构

完整代码分为HTML结构、CSS样式、JavaScript逻辑三部分:

  • HTML:搭建页面框架,包含检测按钮、信号格容器、状态文字、详细信息展示区;
  • CSS:实现信号格可视化样式、页面布局与交互效果;
  • JavaScript:封装基础网络信息获取、测速逻辑、网络等级判断、状态更新等核心功能。

4.2 核心配置与定制化

开发者可根据项目需求调整以下核心配置:

  1. 网络等级阈值:可根据业务场景调整弱/中/强网的速度阈值(默认:<1Mbps为弱网,1-5Mbps为中网,≥5Mbps为强网);
  2. 测试资源:优先替换为自身项目的国内CDN静态资源(如100KB空白文件),进一步提升测速精准度;
  3. 信号格样式:可修改信号格的数量、大小、颜色,适配项目UI风格。

4.3 集成步骤

  1. 复制完整代码到H5项目的HTML文件中;
  2. 替换TEST_RESOURCES中的测试资源为自身项目的国内CDN资源;
  3. 根据项目需求调整网络等级阈值、信号格样式等配置;
  4. 在需要检测网络的页面引入该HTML,或集成到现有页面的指定区域。

五、兼容性与优化拓展

5.1 兼容性处理

针对不同浏览器的兼容性差异,需做好以下降级处理:

  • Network Information API不支持(如Safari):直接使用HTTP测速结果,不依赖预估带宽;
  • fetch请求失败:多节点重试,若所有节点失败,提示"检测失败,请重试";
  • 移动端适配:通过viewport设置确保页面在不同设备上正常显示。

5.2 进阶优化建议

  1. 动态呼吸效果:为点亮的信号格添加呼吸动画,提升视觉体验;
  2. 深色模式适配:根据系统主题(prefers-color-scheme)自动调整信号格颜色;
  3. 流量保护:检测到蜂窝网络(4G/5G)时,提示用户"当前使用移动流量,是否继续测速";
  4. 定时检测:添加定时检测功能,实时更新网络状态(建议间隔10-30秒,避免频繁测速消耗资源)。

源码如下:

css 复制代码
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>H5网络信号检测(国内优化+信号格版)</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: Arial, sans-serif; } .container { max-width: 600px; margin: 50px auto; padding: 20px; border: 1px solid #eee; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } button { padding: 10px 20px; background: #2196f3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; margin-bottom: 20px; } button:hover { background: #1976d2; } button:disabled { background: #ccc; cursor: not-allowed; } /* 信号格容器 */ .network-status { display: flex; align-items: center; gap: 15px; margin: 10px 0; } /* 信号格样式 */ .signal-bars { display: flex; align-items: flex-end; gap: 3px; height: 30px; } .signal-bar { width: 6px; background: #e0e0e0; border-radius: 2px; transition: background 0.3s ease; } /* 4格信号高度 */ .signal-bar:nth-child(1) { height: 8px; } .signal-bar:nth-child(2) { height: 15px; } .signal-bar:nth-child(3) { height: 22px; } .signal-bar:nth-child(4) { height: 30px; } /* 信号等级样式 */ .signal-strong .signal-bar { background: #2e7d32; } .signal-strong .signal-bar:nth-child(1), .signal-strong .signal-bar:nth-child(2), .signal-strong .signal-bar:nth-child(3), .signal-strong .signal-bar:nth-child(4) { background: #2e7d32; } .signal-medium .signal-bar:nth-child(1), .signal-medium .signal-bar:nth-child(2), .signal-medium .signal-bar:nth-child(3) { background: #ff8f00; } .signal-medium .signal-bar:nth-child(4) { background: #e0e0e0; } .signal-weak .signal-bar:nth-child(1) { background: #c62828; } .signal-weak .signal-bar:nth-child(2), .signal-weak .signal-bar:nth-child(3), .signal-weak .signal-bar:nth-child(4) { background: #e0e0e0; } .signal-offline .signal-bar { background: #e0e0e0; } /* 状态文字 */ .status-text { font-size: 18px; } .signal-strong .status-text { color: #2e7d32; } .signal-medium .status-text { color: #ff8f00; } .signal-weak .status-text { color: #c62828; } .signal-offline .status-text { color: #616161; } .speed-info { margin-top: 20px; font-size: 14px; color: #666; line-height: 1.6; } .tips { margin-top: 10px; font-size: 12px; color: #999; } </style> </head> <body> <div class="container"> <h2>网络信号检测(国内优化版)</h2> <button id="checkBtn">立即检测</button> <!-- 信号格+状态文字容器 --> <div class="network-status" id="status"> <div class="signal-bars"> <div class="signal-bar"></div> <div class="signal-bar"></div> <div class="signal-bar"></div> <div class="signal-bar"></div> </div> <div class="status-text">检测中...</div> </div> <div class="speed-info" id="speedInfo"></div> <div class="tips">提示:基于国内CDN节点测速,结果更贴近实际使用体验</div> </div> <script> // 元素获取 const checkBtn = document.getElementById('checkBtn'); const statusEl = document.getElementById('status'); const statusTextEl = statusEl.querySelector('.status-text'); const speedInfoEl = document.getElementById('speedInfo'); // 网络等级定义(单位:Mbps) const NETWORK_LEVEL = { STRONG: { min: 5, text: '信号强', class: 'signal-strong' }, MEDIUM: { min: 1, max: 5, text: '信号中', class: 'signal-medium' }, WEAK: { max: 1, text: '信号弱', class: 'signal-weak' }, OFFLINE: { text: '已离线', class: 'signal-offline' } }; // 国内测试资源列表(优先用自己服务器的固定大小文件) const TEST_RESOURCES = [ // 阿里云图标CDN(约10KB,国内节点) 'https://img.alicdn.com/tps/i3/TB1_oz6GVXXXXaFXVXXJDFnIXXX-64-64.png?random=', // 腾讯云CDN(约50KB) 'https://qzonestyle.gtimg.cn/qzone/v8/img/loading.gif?random=', // 字节跳动CDN(约20KB) 'https://p3-passport.byteacctimg.com/img/user-avatar/8499999999999999~300x300.image?random=' ]; // 1. 基础网络信息获取 function getNetworkType() { if (!navigator.connection) return { type: '未知', downlink: 0, rtt: '未知' }; const { effectiveType, downlink, rtt } = navigator.connection; // 适配国内网络类型显示 const typeMap = { '4g': '4G', '5g': '5G', '3g': '3G', '2g': '2G', 'wifi': 'Wi-Fi', 'ethernet': '有线网络' }; return { type: typeMap[effectiveType] || effectiveType || '未知', downlink: downlink, rtt: rtt }; } // 2. 国内节点实际测速(多节点重试+多次测速取平均) async function testRealSpeed() { let totalSpeed = 0; let validTests = 0; const testCount = 2; // 测速2次取平均 for (let i = 0; i < testCount; i++) { const resourceUrl = TEST_RESOURCES[i % TEST_RESOURCES.length] + Math.random(); const startTime = performance.now(); try { const response = await fetch(resourceUrl, { method: 'GET', cache: 'no-cache', mode: 'cors' }); if (!response.ok) throw new Error('资源请求失败'); const contentLength = response.headers.get('Content-Length'); const endTime = performance.now(); const duration = (endTime - startTime) / 1000; if (contentLength && duration > 0) { const sizeKB = parseInt(contentLength) / 1024; const speedMbps = (sizeKB * 8) / (1024 * duration); totalSpeed += speedMbps; validTests++; } } catch (error) { console.warn(`第${i+1}次测速失败(节点切换):`, error); continue; } } if (validTests === 0) { const { downlink } = getNetworkType(); return { speed: downlink || 0, duration: 0 }; } return { speed: totalSpeed / validTests, duration: (performance.now() - (performance.now() - totalSpeed)) / 1000 }; } // 3. 判断网络等级 function judgeNetworkLevel(speed) { if (navigator.onLine === false) return NETWORK_LEVEL.OFFLINE; if (speed >= NETWORK_LEVEL.STRONG.min) return NETWORK_LEVEL.STRONG; if (speed >= NETWORK_LEVEL.MEDIUM.min && speed < NETWORK_LEVEL.MEDIUM.max) return NETWORK_LEVEL.MEDIUM; return NETWORK_LEVEL.WEAK; } // 4. 国内网络体验建议 function getExperienceTips(level, speed) { switch (level) { case '信号强': return '可流畅播放4K视频、下载大文件、直播无卡顿'; case '信号中': return '可播放1080P视频、日常刷短视频/网页无压力'; case '信号弱': return '建议仅浏览文字内容,避免播放视频(当前速度:' + speed.toFixed(2) + 'Mbps)'; case '已离线': return '无网络连接,请检查Wi-Fi/移动数据'; default: return '建议再次检测确认网络状态'; } } // 5. 主检测函数(防抖) let isChecking = false; async function checkNetwork() { if (isChecking) return; isChecking = true; checkBtn.disabled = true; checkBtn.textContent = '检测中...'; // 重置信号格样式 statusEl.className = 'network-status'; statusTextEl.textContent = '检测中...'; speedInfoEl.innerHTML = ''; try { // 基础网络信息 const networkInfo = getNetworkType(); // 国内节点实际测速 const { speed, duration } = await testRealSpeed(); // 判断网络等级 const level = judgeNetworkLevel(speed); // 更新信号格样式和文字 statusEl.className = `network-status ${level.class}`; statusTextEl.textContent = level.text; // 显示详细信息 speedInfoEl.innerHTML = ` <p>网络类型:${networkInfo.type}</p> <p>实际测速:${speed.toFixed(2)} Mbps(国内节点)</p> <p>往返延迟:${networkInfo.rtt} ms</p> <p>测速耗时:${duration.toFixed(2)} 秒</p> <p>体验建议:${getExperienceTips(level.text, speed)}</p> `; } catch (error) { statusEl.className = 'network-status signal-offline'; statusTextEl.textContent = '检测失败'; console.error('检测失败:', error); } finally { checkBtn.disabled = false; checkBtn.textContent = '立即检测'; isChecking = false; } } // 6. 监听网络状态变化 window.addEventListener('online', () => { statusEl.className = 'network-status'; statusTextEl.textContent = '网络已恢复,点击检测查看信号'; }); window.addEventListener('offline', () => { statusEl.className = 'network-status signal-offline'; statusTextEl.textContent = '已离线'; speedInfoEl.innerHTML = ''; }); // 绑定按钮事件 checkBtn.addEventListener('click', checkNetwork); // 初始化检测 checkNetwork(); </script> </body> </html>
相关推荐
Wect2 小时前
LeetCode 26.删除有序数组中的重复项:快慢指针的顺势应用
前端·typescript
不想秃头的程序员2 小时前
Vue 与 React 数据体系深度对比
前端·vue.js
前端流一2 小时前
[疑难杂症] 浏览器集成 browser-use 踩坑记录
前端·node.js
谷哥的小弟2 小时前
HTML5新手练习项目—ToDo清单(附源码)
前端·源码·html5·项目
pusheng20252 小时前
地下车库一氧化碳监测的技术挑战与解决方案
前端·安全
成为大佬先秃头3 小时前
渐进式JavaScript框架:Vue — API
开发语言·javascript·vue.js
先做个垃圾出来………3 小时前
搜索树完整
开发语言·javascript·ecmascript
ResponseState2003 小时前
安卓原生写uniapp插件手把手教学调试、打包、发布。
前端·uni-app
阿赵3D3 小时前
JavaScript学习笔记——11、正则表达式
javascript·笔记·学习·正则表达式