Highcharts 实战:基于 Gauge 仪表盘打造实时动态时钟可视化开发全解析

Highcharts实战:基于Gauge仪表盘打造实时动态时钟可视化开发全解析

先上Highcharts 时钟的Code,直接复制用!

javascript 复制代码
/**
 * Get the current time
 */
const getNow = () => {
    const now = new Date();

    return {
        date: now,
        hours: now.getHours() + now.getMinutes() / 60,
        minutes: now.getMinutes() * 12 / 60 + now.getSeconds() * 12 / 3600,
        seconds: now.getSeconds() * 12 / 60
    };
};

let now = getNow();

// Create the chart
Highcharts.chart('container', {

    chart: {
        type: 'gauge',
        plotBackgroundColor: null,
        plotBackgroundImage: null,
        plotBorderWidth: 0,
        plotShadow: false,
        height: '80%'
    },

    credits: {
        enabled: false
    },

    title: {
        text: 'The Highcharts Clock'
    },

    pane: {
        background: [{
            borderWidth: 1,
            outerRadius: '105%'
        }, {
            // reflex for supported browsers
            backgroundColor: {
                radialGradient: {
                    cx: 0.5,
                    cy: -0.4,
                    r: 1.9
                },
                stops: [
                    [0.5, 'rgba(255, 255, 255, 0.2)'],
                    [0.5, 'rgba(200, 200, 200, 0.2)']
                ]
            }
        }],
        startAngle: 0,
        endAngle: 360,
        innerSize: 0
    },

    yAxis: {
        labels: {
            distance: -23,
            style: {
                fontSize: '18px'
            }
        },
        min: 0,
        max: 12,
        lineWidth: 0,
        showFirstLabel: false,
        offset: 0,

        minorTickInterval: 'auto',
        minorTickWidth: 3,
        minorTickLength: 5,
        minorTickPosition: 'inside',
        minorTicksPerMajor: 5,
        minorGridLineWidth: 0,

        tickInterval: 1,
        tickWidth: 4,
        tickPosition: 'inside',
        tickLength: 10,
        title: {
            text: 'Powered by<br/>Highcharts',
            style: {
                color: '#BBB',
                fontWeight: 'normal',
                fontSize: '10px',
                lineHeight: '10px'
            },
            y: 10
        }
    },

    tooltip: {
        format: '{series.chart.tooltipText}'
    },

    series: [{
        data: [{
            id: 'hour',
            y: now.hours,
            dial: {
                radius: '60%',
                baseWidth: 6,
                baseLength: '95%',
                rearLength: 0,
                topWidth: 1,
                borderRadius: 0
            }
        }, {
            id: 'minute',
            y: now.minutes,
            dial: {
                baseLength: '95%',
                baseWidth: 4,
                rearLength: 0,
                topWidth: 1,
                borderRadius: 0
            }
        }, {
            id: 'second',
            y: now.seconds,
            dial: {
                radius: '100%',
                baseWidth: 1,
                topWidth: 1,
                rearLength: '20%'
            }
        }],
        animation: false,
        dataLabels: {
            enabled: false
        }
    }]
},

// Move
function (chart) {
    setInterval(function () {

        now = getNow();

        if (chart.axes) { // not destroyed
            const hour = chart.get('hour'),
                minute = chart.get('minute'),
                second = chart.get('second');

            // Cache the tooltip text
            chart.tooltipText = Highcharts.dateFormat('%H:%M:%S', now.date);

            hour.update(now.hours, true, false);
            minute.update(now.minutes, true, false);

            // Move to 59 sec without animation ...
            if (now.seconds === 0) {
                second.update(-0.2, true, false);
            }
            // ... then bounce to next second
            second.update(now.seconds, true, {
                easing: 'easeOutBounce'
            });
        }

    }, 1000);

});

/**
 * Easing function from https://github.com/danro/easing-js/blob/master/easing.js
 */
Math.easeOutBounce = function (pos) {
    if ((pos) < (1 / 2.75)) {
        return (7.5625 * pos * pos);
    }
    if (pos < (2 / 2.75)) {
        return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
    }
    if (pos < (2.5 / 2.75)) {
        return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
    }
    return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
};

一、前言:可视化大屏里,时钟为何要用专业图表库实现?

很多前端开发从业者会有固有认知:时钟仅属于简单UI组件,通过CSS+定时器就能快速实现,没必要引入重型图表库。

但在政企大屏、工业控制、数字孪生、金融交易中心这类企业级可视化项目中,时间展示模块从来不是单纯"看时间",而是一套高实时性动态数据可视化载体。 结合落地项目,典型应用场景包含:

  1. 智慧城市指挥大屏:多时区全球同步时钟面板
  2. 产线工控操作台:设备累计运行时长监控表盘
  3. 航空航天任务管控系统:任务计时、发射倒计时表盘
  4. 证券交易大厅:开盘/收盘倒计时、市场实时计时
  5. 园区数字孪生驾驶舱:全域统一标准时间同步展示
  6. 集团运营监控中心:多分支机构时间统一展示

这类业务场景对时间组件有硬性技术要求:高精度帧级刷新、多指针联动平滑动画、全自定义表盘主题、支持与折线/柱状/热力图等图表数据联动、兼容大屏4K自适应渲染。

Highcharts内置的Gauge仪表盘系列,底层基于SVG矢量渲染,原生支持多指针、自定义圆弧、分段刻度、弹性动画,无需手动绘制坐标系,是实现专业级动态时钟的最优方案之一。本文基于Highcharts官方时钟Demo改造落地,拆解底层实现逻辑、核心API、动画原理,同时补充工业大屏落地优化方案。

二、成品效果预览:三指针机械风格实时时钟

案例完整复刻传统机械钟表视觉逻辑,包含独立时针、分针、秒针三组动态指针,每秒自动刷新角度:

  1. 时针:粗短,随分钟缓慢偏移,不生硬整点跳转
  2. 分针:中等粗细,随秒数微小偏移
  3. 秒针:纤细最长,走动带回弹缓冲动画,还原机械表阻尼手感
  4. 完整360°圆形表盘,12小时主刻度+分钟细分刻度
  5. 悬浮Tooltip展示完整年月日时分秒
  6. 透明表盘底色,可无缝嵌入深色可视化大屏

简易布局示意:

markdown 复制代码
        12
    11      1
  10          2
  9     ●圆心  3
    8          4
      7      5
        6

秒针逐秒刷新动态流转,时分针连续平滑过渡,无卡顿跳帧。

三、整体技术架构链路

整套时钟组件遵循数据驱动视图的标准Highcharts开发范式,完整执行链路清晰分层,便于后续扩展改造:

javascript 复制代码
原生Date时间对象
        ↓
时间数值映射转换模型(24小时→0~12表盘刻度换算)
        ↓
Highcharts Gauge仪表盘容器
        ↓
多Point独立指针(时针/分针/秒针Dial样式分离配置)
        ↓
requestAnimationFrame驱动帧动画,定时更新数值

核心设计思路:不直接操作DOM绘制指针,而是将时分秒时间,换算为仪表盘0~12区间的可视化数值,依靠Gauge原生指针渲染能力自动生成视图,所有动画、缩放、自适应逻辑由图表库接管。

四、核心步骤1:时间数据映射换算逻辑

基础数据区间差异

原生Date对象获取的时间数值区间,和Gauge仪表盘0~12的刻度体系不匹配,需要统一换算映射:

  • 小时:0~23(24小时制)
  • 分钟:0~59
  • 秒:0~59
  • Gauge表盘统一区间:0~12

封装统一时间获取函数,完成数值换算:

javascript 复制代码
const getNowTime = () => {
  const now = new Date();
  // 时针:小时 + 分钟偏移量,实现分针带动时针缓慢移动
  const hours = now.getHours() % 12 + now.getMinutes() / 60;
  // 分针:每分钟对应表盘0.2刻度,叠加秒数微小偏移
  const minutes = now.getMinutes() * 0.2 + now.getSeconds() * (0.2 / 60);
  // 秒针:每秒固定0.2刻度
  const seconds = now.getSeconds() * 0.2;
  return { date: now, hours, minutes, seconds };
};

换算逻辑拆解

  1. 时针换算 例:10点30分,不能直接赋值10,半小时需要偏移0.5刻度,最终数值10.5,保证时针随分针缓慢转动,贴合真实钟表视觉。 10 + 30/60 = 10.5

  2. 分针换算 表盘一圈12刻度,60分钟走完一圈,单分钟占比 12/60=0.2刻度;叠加秒数偏移,分针随秒针轻微移动。

  3. 秒针换算 同分针换算逻辑,每秒移动0.2刻度,60秒完整走完表盘一圈。

设计核心思想

所有可视化组件开发的底层逻辑,都是业务原始数据 → 视觉坐标系映射。仪表盘、折线图、热力图、树形图均遵循这套转换逻辑,掌握映射思路可快速迁移到任意可视化场景。

五、核心步骤2:Gauge表盘基础容器配置

1. 图表类型指定

javascript 复制代码
chart: {
  type: 'gauge',
  backgroundColor: 'transparent', // 透明背景适配大屏
  reflow: true // 窗口自动自适应
}

Gauge是Highcharts专属仪表盘系列,原生支持圆弧、指针、分段刻度,区别于普通线性图表。

2. 关闭冗余图表边框样式

时钟无坐标轴、绘图边框,默认图表边框、阴影全部关闭,保证表盘干净简洁:

javascript 复制代码
plotBorderWidth: 0,
plotShadow: false,
credits: { enabled: false } // 隐藏Highcharts水印

3. Pane面板:从半圆改为完整圆形表盘

Gauge默认是180°半圆仪表盘,通过startAngle、endAngle调整为完整360°圆形,实现钟表表盘基础轮廓:

javascript 复制代码
pane: {
  startAngle: 0,
  endAngle: 360,
  background: [{ fill: 'rgba(30,30,40,0.6)' }] // 深色表盘底色适配大屏
}

4. 刻度系统配置,还原机械钟表刻度分层

  • tickInterval: 1 :每1个刻度单位绘制主刻度(对应1/2/3...12小时数字)
  • minorTickInterval: 'auto' :自动生成细分分钟小刻度
javascript 复制代码
yAxis: {
  min: 0,
  max: 12,
  tickInterval: 1,
  minorTickInterval: 'auto',
  labels: { distance: 20 } // 数字刻度向外偏移,避免遮挡指针
}

六、核心步骤3:多指针分离配置(时分秒视觉分层)

Highcharts Gauge支持单series内配置多个Point,每个Point独立定制Dial指针样式,以此区分时针、分针、秒针粗细、长度。

javascript 复制代码
series: [{
  data: [
    { id: 'hour', dial: { radius: '60%', baseWidth: 6 } },
    { id: 'minute', dial: { radius: '80%', baseWidth: 4 } },
    { id: 'second', dial: { radius: '100%', baseWidth: 1 } }
  ]
}]

样式分层逻辑完全复刻实体钟表:

  1. 时针:最短、最粗,radius限制60%表盘半径,baseWidth宽度6px
  2. 分针:长度中等,宽度4px,视觉层级弱于时针
  3. 秒针:最长、最纤细,宽度仅1px,视觉上最突出

七、核心步骤4:实时刷新与弹性动画实现

1. 定时更新机制

采用1000ms定时器每秒获取最新时间,也可替换requestAnimationFrame实现帧级刷新:

javascript 复制代码
const timer = setInterval(() => {
  const time = getNowTime();
  // 获取三个指针实例,更新数值并开启动画
  chart.get('hour').update(time.hours, true);
  chart.get('minute').update(time.minutes, true);
  chart.get('second').update(time.seconds, true, {
    easing: 'easeOutBounce' // 秒针专属回弹缓动函数
  });
}, 1000);

2. update API 动画原理

调用point.update更新数值时,图表不会销毁重建指针DOM,复用原有SVG元素,仅修改旋转角度属性,浏览器渲染开销极低,4K大屏多实例同时渲染也不会卡顿。

3. 秒针回弹动画细节

为秒针单独配置easeOutBounce缓动曲线,数值到达目标刻度后轻微回弹,模拟机械钟表齿轮阻尼效果,视觉真实度大幅提升;时分针使用默认线性缓动,保证平缓连续转动。

八、Highcharts Gauge时钟方案核心技术优势

1. 原生数据驱动动画,无需手动操作渲染层

传统CSS时钟需要自行计算transform旋转角度、管理帧动画;Gauge完全通过数值驱动视图,仅需维护时间业务数据,旋转、过渡、缓冲全部由图表库封装,减少大量底层DOM操作代码。

2. SVG矢量渲染,大屏无损缩放

底层基于SVG而非Canvas,适配2K/4K、异形大屏拉伸场景,指针、刻度不会模糊,是数字孪生、指挥中心大屏的硬性需求。

3. 多组件联动拓展能力强

时钟本身属于仪表盘组件,可和Highcharts其他系列图表联动:比如设备运行时钟搭配实时功率曲线、交易时钟搭配行情K线,共用一套图表实例数据体系,数据同步更便捷。

4. 统一主题体系,批量切换样式

支持全局预设主题一键切换深色/浅色、工业风/科技风表盘,项目中几十块时钟面板可统一配置,对比纯CSS时钟大幅降低样式维护成本。

九、落地拓展业务场景

场景1:多时区全球时钟面板

创建多个独立Gauge图表实例,分别绑定北京、纽约、伦敦、东京时区时间,统一布局为多表盘面板,用于海关、跨境业务监控大屏。

场景2:工业产线运行计时表盘

修改刻度区间,不再是012小时,改为0720小时(30天),表盘展示设备连续运行时长,搭配红色警戒刻度标识维护周期,替代传统文本计时。

场景3:金融交易倒计时表盘

限定开盘、收盘刻度区间,通过分段色标区分可交易时段/休市时段,秒针作为倒计时指针,交易大厅大屏实时展示剩余交易时间。

场景4:航天任务计时表盘

双Gauge组合,主表盘展示任务总时长,副表盘展示分段倒计时,搭配警示色阶标识关键操作节点。

十、方案横向对比:CSS原生时钟 VS Highcharts Gauge时钟

对比维度 CSS纯手写时钟 Highcharts Gauge实现时钟
圆形刻度绘制 手动计算坐标,代码冗余 yAxis刻度原生支持,一行配置
多指针联动动画 手动管理transform动画 内置point.update动画API
4K大屏自适应 缩放易模糊,需适配多分辨率 SVG矢量无损拉伸,自带reflow自适应
多实例批量主题 需批量修改CSS变量 全局统一主题配置,一键切换
与其他图表联动 数据隔离,联动开发成本高 共用图表库数据总线,联动简单
企业级可视化集成 仅UI组件,无扩展能力 可无缝嵌入Dashboards大屏套件

十一、项目落地开发总结

看似简单的时钟组件,实则完整覆盖Highcharts可视化核心开发思想:数据映射模型、仪表盘容器配置、多元素独立样式管控、实时帧动画渲染。 Gauge系列也并非只能做时钟,工业压力表盘、设备转速仪表盘、能耗指标仪表盘、进度环形表盘都可以复用这套开发逻辑。

很多开发者将Highcharts局限于柱状图、折线图等统计图表,忽略了Gauge、Solidgauge、Treegraph等拓展系列的可视化潜力。在数字孪生、工控大屏、指挥中心这类ToB可视化项目中,各类仪表盘组件是高频需求,掌握数据驱动视图的开发范式,可快速交付各类自定义监控表盘,大幅降低可视化页面开发周期。

整套方案兼容Vue/React框架,支持按需引入模块减小打包体积,适配内网离线部署场景,满足政企项目国产化、本地化部署要求。

相关推荐
Patrick在香港1 小时前
Python爬虫框架实战:优雅抓取香港政府公开API数据的通用方案
java·开发语言·爬虫·python·ai编程·数据可视化·可用性测试
嘟嘟07171 小时前
用单例模式管理弹窗:从一段原生 JS 理解 Singlet
前端·javascript·代码规范
广东程序猿小展1 小时前
《007初露锋芒》风灵月影13项修改器下载 007初露锋芒修改器使用方法
开发语言·javascript·ecmascript
用户6919026813391 小时前
Harness工程的概念,以及简单的代码示例
javascript·架构
AI_paid_community2 小时前
如何使用 Claude 在 AI 时代快速入局新的行业?(经验贴)
前端·javascript·后端
sunly_2 小时前
React Suspense 用法详解
前端·javascript·react.js
渣波2 小时前
TS 必考题深度解析:type 与 interface 的终极对决
前端·javascript
必须会一定会3 小时前
用纯 HTML/JS 做一个 AI 需求澄清器:把模糊想法转换成可执行任务书
开发语言·前端·javascript·人工智能·html·ai编程
小帅不太帅4 小时前
1.5M 参数的 OCR 模型,我把它跑进了浏览器
前端·javascript·ai编程