配了三年性能优化,今天才发现网络质量从来不是靠猜的——Network Information API 把这件事从设备层彻底暴露了

性能优化的人,最怕的不是代码写得烂,是用户告诉你「页面好卡」,你却复现不了。你本地千兆光纤,测试机全速 4G,结果线上用户说卡——你怎么办?

大多数人的答案是:靠经验猜。视频切到低画质,图片切成 WebP,大文件加个懒加载。靠猜没问题,直到你发现有些用户开着 Data Saver 跑你的「高清首图」,有些人明明在地铁里 3G,你却给他推了 4K 视频。

浏览器其实知道用户网络什么样。只是你从来没问过它。

一行代码,把网络底牌翻出来

navigator.connectionNetwork Information API,Chrome 38+、Edge 79+ 就支持了。Safari 从来没实现,Firefox 2019 年加过又因为隐私原因移掉了,但 Chrome 系和 Electron 应用里基本都能用,全球覆盖率大概 75% 左右。

const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;

console.log(conn.effectiveType);  // "4g" / "3g" / "2g" / "slow-2g"
console.log(conn.downlink);       // 数字,单位 Mbps
console.log(conn.rtt);            // 数字,单位毫秒
console.log(conn.saveData);       // true / false,用户是否开了省流模式
console.log(conn.type);           // "wifi" / "cellular" / "bluetooth" / ...

这五个属性,就是你做自适应加载的全部数据源。

三个最实用的场景

场景一:按网络类型切图片分辨率

这是 Addy Osmani 早在 2017 年就演示过的模式,至今依然是最直接的价值:

function getImageSrc() {
  const conn = navigator.connection;
  if (!conn) return "high.jpg";

  // 开了省流模式,或者在 2G/3G 下,只给缩略图
  if (conn.saveData || conn.effectiveType === "slow-2g" || conn.effectiveType === "2g") {
    return "low.jpg";
  }

  // 4G 以上给高清
  return "high.jpg";
}

<img src={getImageSrc()}> 三行代码,比写 Media Query 精确多了——Media Query 只能猜设备,effectiveType 才是真正的网络状态。

场景二:视频延迟加载 + 质量自适应

视频是流量大户,3G 环境下直接加载 1080p 是灾难:

conn.addEventListener("change", () => {
  const quality = conn.effectiveType;

  if (quality === "4g") {
    videoPlayer.src = "1080p.mp4";
    videoPlayer.play();
  } else if (quality === "3g") {
    videoPlayer.src = "480p.mp4";
    videoPlayer.play();
  } else {
    // 2G / slow-2g:只显示封面,不自动播放
    videoPlayer.poster = "poster.jpg";
  }
});

监听 change 事件可以捕捉网络切换——用户从 WiFi 切到流量,API 会主动通知,你不需要轮询。

场景三:Data Saver 模式下的降级策略

saveData 属性对应 Chrome 的「节省流量」设置,很多用户主动开启,尤其在移动流量贵的地区:

if (navigator.connection?.saveData) {
  // 隐藏视频自动播放
  document.querySelectorAll("video").forEach(v => {
    v.removeAttribute("autoplay");
    v.poster = v.dataset.posterFallback;
  });

  // 图集只加载第一张
  lazyImageContainers.forEach((c, i) => {
    if (i > 0) c.dataset.src = c.dataset.posterFallback;
  });
}

这个场景和 Battery Status API 恰好互补——一个管电量,一个管网速,两个维度叠加才构成完整的「设备感知」。

踩过的三个坑

坑一:effectiveType 不是实时网速,是分类

effectiveType 的值是 "slow-2g""2g""3g""4g",是根据 RTT 和 downlink 估算出来的分类,不是精确测速。用户在 4G 信号边缘来回波动时,可能看到 effectiveType 在 3g 和 4g 之间跳,这是正常行为,别当成 bug。

坑二:Safari 和 Firefox 没有这个 API

写代码前一定要做特性检测:

const conn = navigator.connection;
if (!conn) {
  // 浏览器不支持,给默认值走通用逻辑
  return "high.jpg";
}

渐进增强原则:先写不支持时的降级逻辑,再写支持时的优化逻辑。

坑三:downlink 值不是你的下载速度

navigator.connection.downlink 返回的是「有效带宽估算」,是 25kb/s 为粒度取整的值,反映的是当前网络环境能承载的带宽,不是你当前页面的实际下载速度。别拿这个值去做精确的进度条百分比计算。

怎么接入

第一步,先在控制台跑一遍,看你当前网络环境的原始数据:

// 复制到浏览器控制台直接看
setInterval(() => {
  const c = navigator.connection;
  console.log(`类型:${c.effectiveType} | 带宽:${c.downlink}Mbps | RTT:${c.rtt}ms | 省流:${c.saveData}`);
}, 2000);

第二步,选一个场景落地。推荐从图片自适应开始,ROI 最高——三行代码,换一个 CDN 降费的思路。

第三步,如果你的项目已经接了 Service Worker,在 fetch 事件里加一层网络感知拦截,对不同 effectiveType 返回不同压缩质量的资源,这是最干净的架构做法。


网络质量和电量一样,以前只能靠猜,现在浏览器直接告诉你。配了三年性能优化,下次用户说卡,你可以先问一句:「你网络怎么样?」——然后让代码自己决定怎么处理。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 161 阅读