SVG数据可视化组件基础教程:带刻度的仪表盘2

大家好,我是设计师邱兴,一个学习前端的设计师,今天给大家制作一个用SVG实现的带刻度的仪表盘,SVG相较于Echart来说制作简单,但是效果可以非常丰富。

一、目标

通过SVG和JavaScript实现一个精美的圆形仪表盘进度条,具有以下特点:

  1. 使用渐变色背景和进度块,提升视觉效果。
  2. 动态更新进度,展示从0到80的下载速度值。
  3. 包含刻度数字,显示不同的速度值。
  4. 使用滑块控制进度值。

二、所需工具与准备

  1. 工具

    • 一个文本编辑器(如Notepad++、VS Code等)。
    • 浏览器(用于预览效果)。
  2. 基础准备

    • 确保你对HTML、CSS和JavaScript有一定的了解。
    • 确保你对SVG的基本语法有一定了解。

三、代码分析与操作步骤

1. 创建HTML结构

创建一个HTML文件(如Lesson3.html)并设置基本的HTML结构:

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>SVG 仪表盘进度条</title>
  <style>
    /* 样式部分 */
  </style>
</head>
<body>
  <div class="gauge-container">
    <svg id="gauge" width="350" height="300" viewBox="0 0 350 300">
      <!-- SVG内容 -->
    </svg>
  </div>
  <div class="slider-container">
    <input type="range" id="progressSlider" min="0" max="80" value="0" step="1" style="width:300px;" />
    <div>进度:<span id="progressNum">0</span> / 80</div>
  </div>

  <script>
    // JavaScript部分
  </script>
</body>
</html>

2. 添加CSS样式

<style>标签中,添加以下CSS样式:

css 复制代码
body {
  background: #fff;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: Arial, sans-serif;
}
.gauge-container {
  margin-top: 40px;
  margin-bottom: 20px;
}
.slider-container {
  margin-top: 30px;
  text-align: center;
}
  • 设置页面背景颜色为白色。
  • 使用flexbox布局将SVG仪表盘和滑块居中显示。
  • 设置滑块容器的样式。

3. 定义SVG渐变

<svg>标签中,定义背景渐变和活动渐变:

ini 复制代码
<defs>
  <linearGradient id="bgGradient" x1="0" y1="0" x2="1" y2="0">
    <stop offset="0%" stop-color="#3b82f6" stop-opacity="0.2"/>
    <stop offset="100%" stop-color="#3b82f6" stop-opacity="0.05"/>
  </linearGradient>
  <linearGradient id="activeGradient" x1="0" y1="0" x2="1" y2="0">
    <stop offset="0%" stop-color="#2563eb"/>
    <stop offset="100%" stop-color="#60a5fa"/>
  </linearGradient>
</defs>
  • bgGradient:背景渐变,用于显示未填充区域。
  • activeGradient:活动渐变,用于显示已填充区域。

4. 绘制背景和进度圆环

<svg>标签中,添加背景和进度圆环的组元素:

bash 复制代码
<g id="gauge-bg"></g>
<g id="gauge-active"></g>

5. 添加刻度数字和中心元素

<svg>标签中,添加刻度数字和中心元素:

arduino 复制代码
<g id="gauge-labels"></g>
<circle cx="175" cy="175" r="90" fill="white" opacity="0.95"/>
<text x="175" y="130" text-anchor="middle" font-size="18" fill="#888" font-weight="bold">下载速度</text>
<text id="gauge-value" x="175" y="170" text-anchor="middle" font-size="44" fill="#222" font-weight="bold">0</text>
<text x="175" y="200" text-anchor="middle" font-size="18" fill="#888" font-weight="bold">Mbps</text>
  • gauge-labels:用于显示刻度数字。
  • 中心白色圆和文字:用于显示进度值。

6. 编写JavaScript代码

<script>标签中,添加以下JavaScript代码来实现动态效果:

ini 复制代码
const minValue = 0;
const maxValue = 80;
const numBlocks = 8;
const startAngle = 225;
const endAngle = 45 + 360;
const radiusOuter = 130;
const radiusInner = 110;
const centerX = 175;
const centerY = 175;

// 辅助函数:极坐标转换为笛卡尔坐标
function polarToCartesian(cx, cy, r, angle) {
  const rad = (angle-90) * Math.PI / 180.0;
  return {
    x: cx + (r * Math.cos(rad)),
    y: cy + (r * Math.sin(rad))
  };
}

// 辅助函数:生成圆弧路径
function describeArc(cx, cy, r1, r2, startAngle, endAngle) {
  const p1 = polarToCartesian(cx, cy, r2, endAngle);
  const p2 = polarToCartesian(cx, cy, r2, startAngle);
  const p3 = polarToCartesian(cx, cy, r1, startAngle);
  const p4 = polarToCartesian(cx, cy, r1, endAngle);

  const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

  return [
    "M", p1.x, p1.y,
    "A", r2, r2, 0, largeArcFlag, 0, p2.x, p2.y,
    "L", p3.x, p3.y,
    "A", r1, r1, 0, largeArcFlag, 1, p4.x, p4.y,
    "Z"
  ].join(" ");
}

// 绘制背景和进度块
function drawGaugeBlocks(value) {
  const bgGroup = document.getElementById('gauge-bg');
  const activeGroup = document.getElementById('gauge-active');
  bgGroup.innerHTML = '';
  activeGroup.innerHTML = '';
  const angleStep = (270) / numBlocks;
  const valuePerBlock = (maxValue - minValue) / numBlocks;
  const activeBlocks = Math.round((value - minValue) / valuePerBlock);

  for (let i = 0; i < numBlocks; i++) {
    const sa = startAngle + i * angleStep;
    const ea = sa + angleStep * 0.92;

    // 背景块
    const bgPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
    bgPath.setAttribute("d", describeArc(centerX, centerY, radiusInner, radiusOuter, sa, ea));
    bgPath.setAttribute("fill", "url(#bgGradient)");
    bgGroup.appendChild(bgPath);

    // 进度块
    if (i < activeBlocks) {
      const activePath = document.createElementNS("http://www.w3.org/2000/svg", "path");
      activePath.setAttribute("d", describeArc(centerX, centerY, radiusInner, radiusOuter, sa, ea));
      activePath.setAttribute("fill", "url(#activeGradient)");
      activeGroup.appendChild(activePath);
    }
  }
}

// 绘制刻度数字
function drawGaugeLabels() {
  const labelsGroup = document.getElementById('gauge-labels');
  labelsGroup.innerHTML = '';
  const angleStep = (270) / numBlocks;
  const valuePerBlock = (maxValue - minValue) / numBlocks;
  for (let i = 0; i <= numBlocks; i++) {
    const angle = startAngle + i * angleStep;
    const pos = polarToCartesian(centerX, centerY, radiusOuter + 18, angle);
    const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
    label.setAttribute("x", pos.x);
    label.setAttribute("y", pos.y + 6);
    label.setAttribute("text-anchor", "middle");
    label.setAttribute("font-size", "16");
    label.setAttribute("fill", "#7ca0d9");
    label.setAttribute("font-weight", "bold");
    label.textContent = Math.round(minValue + i * valuePerBlock);
    labelsGroup.appendChild(label);
  }
}

// 控制进度
function setProgress(val) {
  drawGaugeBlocks(val);
  document.getElementById('gauge-value').textContent = val;
  document.getElementById('progressNum').textContent = val;
}

// 初始化
drawGaugeLabels();
setProgress(0);

// 监听滑块
document.getElementById('progressSlider').addEventListener('input', function() {
  setProgress(Number(this.value));
});

7. 完整代码

将上述代码整合到一个HTML文件中,完整的代码如下:

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>SVG 仪表盘进度条</title>
  <style>
    body {
      background: #fff;
      display: flex;
      flex-direction: column;
      align-items: center;
      font-family: Arial, sans-serif;
    }
    .gauge-container {
      margin-top: 40px;
      margin-bottom: 20px;
    }
    .slider-container {
      margin-top: 30px;
      text-align: center;
    }
  </style>
</head>
<body>
  <div class="gauge-container">
    <svg id="gauge" width="350" height="300" viewBox="0 0 350 300">
      <!-- 背景渐变 -->
      <defs>
        <linearGradient id="bgGradient" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#3b82f6" stop-opacity="0.2"/>
          <stop offset="100%" stop-color="#3b82f6" stop-opacity="0.05"/>
        </linearGradient>
        <linearGradient id="activeGradient" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#2563eb"/>
          <stop offset="100%" stop-color="#60a5fa"/>
        </linearGradient>
      </defs>
      <!-- 背景圆环 -->
      <g id="gauge-bg"></g>
      <!-- 进度圆环 -->
      <g id="gauge-active"></g>
      <!-- 刻度数字 -->
      <g id="gauge-labels"></g>
      <!-- 中心白色圆 -->
      <circle cx="175" cy="175" r="90" fill="white" opacity="0.95"/>
      <!-- 中心文字 -->
      <text x="175" y="130" text-anchor="middle" font-size="18" fill="#888" font-weight="bold">下载速度</text>
      <text id="gauge-value" x="175" y="170" text-anchor="middle" font-size="44" fill="#222" font-weight="bold">0</text>
      <text x="175" y="200" text-anchor="middle" font-size="18" fill="#888" font-weight="bold">Mbps</text>
    </svg>
  </div>
  <div class="slider-container">
    <input type="range" id="progressSlider" min="0" max="80" value="0" step="1" style="width:300px;" />
    <div>进度:<span id="progressNum">0</span> / 80</div>
  </div>

  <script>
    const minValue = 0;
    const maxValue = 80;
    const numBlocks = 8;
    const startAngle = 225;
    const endAngle = 45 + 360;
    const radiusOuter = 130;
    const radiusInner = 110;
    const centerX = 175;
    const centerY = 175;

    // 辅助函数:极坐标转换为笛卡尔坐标
    function polarToCartesian(cx, cy, r, angle) {
      const rad = (angle-90) * Math.PI / 180.0;
      return {
        x: cx + (r * Math.cos(rad)),
        y: cy + (r * Math.sin(rad))
      };
    }

    // 辅助函数:生成圆弧路径
    function describeArc(cx, cy, r1, r2, startAngle, endAngle) {
      const p1 = polarToCartesian(cx, cy, r2, endAngle);
      const p2 = polarToCartesian(cx, cy, r2, startAngle);
      const p3 = polarToCartesian(cx, cy, r1, startAngle);
      const p4 = polarToCartesian(cx, cy, r1, endAngle);

      const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

      return [
        "M", p1.x, p1.y,
        "A", r2, r2, 0, largeArcFlag, 0, p2.x, p2.y,
        "L", p3.x, p3.y,
        "A", r1, r1, 0, largeArcFlag, 1, p4.x, p4.y,
        "Z"
      ].join(" ");
    }

    // 绘制背景和进度块
    function drawGaugeBlocks(value) {
      const bgGroup = document.getElementById('gauge-bg');
      const activeGroup = document.getElementById('gauge-active');
      bgGroup.innerHTML = '';
      activeGroup.innerHTML = '';
      const angleStep = (270) / numBlocks;
      const valuePerBlock = (maxValue - minValue) / numBlocks;
      const activeBlocks = Math.round((value - minValue) / valuePerBlock);

      for (let i = 0; i < numBlocks; i++) {
        const sa = startAngle + i * angleStep;
        const ea = sa + angleStep * 0.92;

        // 背景块
        const bgPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
        bgPath.setAttribute("d", describeArc(centerX, centerY, radiusInner, radiusOuter, sa, ea));
        bgPath.setAttribute("fill", "url(#bgGradient)");
        bgGroup.appendChild(bgPath);

        // 进度块
        if (i < activeBlocks) {
          const activePath = document.createElementNS("http://www.w3.org/2000/svg", "path");
          activePath.setAttribute("d", describeArc(centerX, centerY, radiusInner, radiusOuter, sa, ea));
          activePath.setAttribute("fill", "url(#activeGradient)");
          activeGroup.appendChild(activePath);
        }
      }
    }

    // 绘制刻度数字
    function drawGaugeLabels() {
      const labelsGroup = document.getElementById('gauge-labels');
      labelsGroup.innerHTML = '';
      const angleStep = (270) / numBlocks;
      const valuePerBlock = (maxValue - minValue) / numBlocks;
      for (let i = 0; i <= numBlocks; i++) {
        const angle = startAngle + i * angleStep;
        const pos = polarToCartesian(centerX, centerY, radiusOuter + 18, angle);
        const label = document.createElementNS("http://www.w3.org/2000/svg", "text");
        label.setAttribute("x", pos.x);
        label.setAttribute("y", pos.y + 6);
        label.setAttribute("text-anchor", "middle");
        label.setAttribute("font-size", "16");
        label.setAttribute("fill", "#7ca0d9");
        label.setAttribute("font-weight", "bold");
        label.textContent = Math.round(minValue + i * valuePerBlock);
        labelsGroup.appendChild(label);
      }
    }

    // 控制进度
    function setProgress(val) {
      drawGaugeBlocks(val);
      document.getElementById('gauge-value').textContent = val;
      document.getElementById('progressNum').textContent = val;
    }

    // 初始化
    drawGaugeLabels();
    setProgress(0);

    // 监听滑块
    document.getElementById('progressSlider').addEventListener('input', function() {
      setProgress(Number(this.value));
    });
  </script>
</body>
</html>

四、总结

通过以上步骤,你可以创建一个精美的SVG圆形仪表盘进度条。这个仪表盘具有渐变色背景、动态进度块、刻度数字和滑块控制功能。你可以通过调整代码中的参数来改变仪表盘的外观和行为。希望这个教程对你有所帮助! 以上制作的是一个最简单的一个带刻度的仪表盘,我还录制了一个更加美观的带刻度的仪表盘的视频教程,有兴趣的小伙伴可以点击查看。

相关推荐
东坡白菜2 分钟前
最快实现的前端灰度方案
前端
curdcv_po5 分钟前
🔴 你老说拿下 react,真的 拿下 了吗
前端
魔都吴所谓6 分钟前
[前端]HTML模拟实现一个基于摄像头的手势识别交互页面
前端·html·交互
来自星星的猫教授8 分钟前
Vue 3.6前瞻:响应式性能革命与Vapor模式展望
前端·javascript·vue.js
涵信12 分钟前
第九节 高频代码题-实现Sleep函数(异步控制)
前端·javascript·typescript
Kusunoki_D27 分钟前
Python 实现 Web 静态服务器(HTTP 协议)
服务器·前端·python
爱学习的茄子36 分钟前
【前端实战】三分钟掌握原生JS电影搜索应用,从此告别框架依赖
前端·javascript·深度学习
林太白38 分钟前
Next.js超简洁完整篇
前端·后端·react.js
前端付豪38 分钟前
汇丰登录风控体系拆解:一次 FaceID 被模拟攻击的调查纪实
前端·后端·架构
天生我材必有用_吴用1 小时前
Three.js开发必备:模型对象和材质详解
前端