前端怎么测网速?

1,033 阅读3分钟

如果只是想单纯地测试本机的网络速度,那么可以在线测试www.speedtest.cn/,或者很多桌面工具也都有提供测速的工具。

本文所谓的测网速,是测试你的网站服务器的网速,而不是你本机的网速~

原理解析

测网速的原理其实很简单:

  1. 在服务器上准备一些文件供下载用;
  2. 多次测试下载这些文件用了多长时间;
  3. 然后用时间和下载的文件大小计算一下,即可得出平均网速;

示例如下:

image.png

代码实操

了解了原理,那么我们就可以写一个测网速的组件了。

我们要准备两点:

  • 在服务器上准备一个文件,并记录文件大小size(一般几M大小即可)
  • 实现代码

我准备的文件配置如下

const TEST_FILE = { 
  url:'https://www.xxx.com/20240412/10/34/4/0dedaa5dbc6c.mp4?v=1',
  size: 2680833 // 约等于2.57M大小
}

效果图如下:

image.png

整个组件的代码如下,因为是8k大屏弹框组件,所以文字大小、长宽等都很大,这里不做整理了,仅提供逻辑思路。

注意点:测试下载的文件,一定要加唯一标识的后缀,否则后面可能会从浏览器缓存取,导致测速不准确;

牛马上班抽空写文章、没时间整理了,请见谅。还有冗余的代码,都是从其他同事的组件里复制的

<template>
  <div class="layer" v-if="isShowLockBox == 1">
    <div class="dialog" v-if="isShowLockBox == 1">
      <div class="title">测试网速</div>
      <div class="fl-right" @click="isShowLockBox = 0;averageSpeed = 0">
        <img class="fl-img" src="@/assets/images/home/crossOff.png"/>
        <img class="fl-img-active" src="@/assets/images/home/crossOffActive.png"/>
      </div>

      <div class="speed-test">
        <div class="controls">
          <button @click="startTest" :disabled="isTesting" style="font-size: 70px;">
            {{ isTesting ? '测试中...' : '开始测速' }}
          </button>
          <button @click="cancelTest" v-if="isTesting" style="font-size: 70px;">取消</button>
        </div>

        <div class="progress" v-if="isTesting">
          <div class="progress-bar" :style="{ width: progress + '%' }"></div>
        </div>

        <div class="results">
          <div class="speed-display">
            <span class="value">{{ averageSpeed }}</span>
            <span class="unit">M/s</span>
          </div>
        </div>

        <div class="chart" v-if="speedHistory.length > 0">
          <div
            v-for="(speed, index) in speedHistory"
            :key="index"
            class="chart-bar"
            :style="{ height: Math.min(speed * 5, 100) + '%' }"
          ></div>
        </div>
      </div>
    </div>
  </div>

  <div class="lock-btns" title="测网速">
    <img :src="$loadLocalImg('lock/netSpeed.png')" class="lock-icon" alt="测试网速" @click="handleLockOpen">
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

// 是否显示遮罩层的输入框,默认不显示
const isShowLockBox = ref(0)
/** 解锁,仅显示输入框 */
const handleLockOpen = () => {
  isShowLockBox.value = 1
}

// 测试文件配置
const TEST_FILE = {
  url: 'https://www.xxx.com/group1/default/20240412/10/34/4/0da5dbc6c.mp4?v=1',
  size: 2680833 // 500KB
}

const isTesting = ref(false)
const progress = ref(0)
const speedHistory = ref([])
const controller = ref(null)
const testCount = ref(0)
const totalTests = 10

// 计算平均速度
const averageSpeed = computed(() => {
  if (speedHistory.value.length === 0) return '0.00'
  const sum = speedHistory.value.reduce((a, b) => a + b, 0)
  return (sum / speedHistory.value.length).toFixed(2)
})

// 开始测试
const startTest = async () => {
  resetState()
  isTesting.value = true
  controller.value = new AbortController()

  try {
    for (let i = 0; i < totalTests; i++) {
      await measureSpeed()
      testCount.value = i + 1
      progress.value = ((i + 1) / totalTests) * 100
    }
  } finally {
    isTesting.value = false
  }
}

// 核心测速方法
const measureSpeed = async () => {
  let loadedBytes = 0
  const startTime = performance.now()
  const randomParam = Math.random()
  const urlWithRandom = `${TEST_FILE.url}&r=${randomParam}`

  try {
    const response = await fetch(urlWithRandom, {
      signal: controller.value.signal
    })

    const reader = response.body.getReader()
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      loadedBytes += value?.length || 0
    }

    const duration = (performance.now() - startTime) / 1000 // 秒
    const speedMbps = (loadedBytes * 8) / duration / 1e6 // 转换为Mbps
    speedHistory.value.push(speedMbps)
  } catch (error) {
    if (error.name !== 'AbortError') {
      console.error('测速失败:', error)
    }
  }
}

// 取消测试
const cancelTest = () => {
  controller.value?.abort()
  isTesting.value = false
}

// 重置状态
const resetState = () => {
  progress.value = 0
  speedHistory.value = []
  testCount.value = 0
}
</script>

<style lang="scss" scoped>
.speed-test {
  width: 1200px;
  height: 640px;
  margin: 2rem auto;
  padding: 20px;
  background: rgba(11, 20, 39, 0.8);
  border-radius: 12px;
}

.controls {
  margin-bottom: 1rem;
}

button {
  padding: 8px 16px;
  margin-right: 10px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:disabled {
  background: #6c757d;
}

.progress {
  height: 20px;
  background: #ddd;
  border-radius: 5px;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  background: #28a745;
  transition: width 0.3s ease;
}

.speed-display {
  text-align: center;
  margin: 2rem 0;
}

.value {
  font-size: 8rem;
  font-weight: bold;
  color: #fff;
}

.unit {
  font-size: 4.2rem;
  color: #fff;
}

.chart {
  display: flex;
  height: 150px;
  align-items: flex-end;
  gap: 2px;
  margin-top: 1rem;
}

.chart-bar {
  flex: 1;
  background: #007bff;
  transition: height 0.5s ease;
}



.layer {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  z-index: 9999;
  background-color: rgba(0, 0, 0, 0.05);
  display: flex;
  justify-content: center;
  align-items: center;
  pointer-events: auto;
}
.dialog {
  position: relative;
  width: 1372px;
  border-radius: 20px;
  text-align: center;
  color: #ffffff;
  background-color: #020A1A;
  .title {
    font-size: 80px;
    background: #134392;
    height: 150px;
    line-height: 150px;
  }
  .input-box {
    width: 1202px;
    height: 117px;
    margin: 123px auto 0;
    :deep(.el-input__wrapper) {
      padding: 0;
      border-radius: 20px;
      overflow: hidden;
    }
    :deep(.el-input__inner) {
      height: 118px;
      line-height: 118px;
      font-size: 64px;
      border: 4px solid #ffffff;
      border-radius: 20px;
      padding-left: 40px;
      background-color: #000000;
      color: #ffffff;
      & > ::placeholder {
        color: rgba(255, 255, 255, 0.5)
      }
    }
  }
  .btns {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    width: 1202px;
    margin: 200px auto 85px;
    .lock-btn {
      width: 518px;
      height: 113px;
      line-height: 113px;
      border-radius: 20px;
      text-align: center;
      background: rgba(255,255,255,0.2);
      font-size: 44px;
      color: #ffffff;
      cursor: pointer;
    }
    & > div:last-child {
      background-color: #0067E6;
    }
  }
}

.lock-btns {
  position: fixed;
  bottom: 25px;
  right: 1400px;
  z-index: 10000;
}
.lock-icon {
  width: 130px;
  cursor:pointer;
  pointer-events: auto;
}

.fl-right {
  pointer-events: auto;
  cursor: pointer;
  position: absolute;
  top: 40px;
  right: 40px;

  &:hover {
    img {
      &.fl-img {
        display: none;
      }

      &.fl-img-active {
        display: initial;
      }
    }
  }

  img {
    width: 57px;
    height: 57px;

    &.fl-img-active {
      display: none;
    }
  }
}
</style>