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>
相关推荐
恋猫de小郭11 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅18 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606119 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了19 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅19 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅19 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅20 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment20 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅20 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊20 小时前
jwt介绍
前端