目录
前言
在全球化与数字化交织发展的当代社会,时间作为最基础的信息维度,其精准管理与多维展示已成为支撑现代复杂系统运转的关键要素。在之前的博客中,介绍了一款能生成静态效果的时钟组件,原文地址:用HTML5 Canvas打造高颜值动态时钟的创意实现。然而,传统的时间显示工具多局限于单一时钟的静态呈现,难以满足用户对多时区信息实时对比、动态配置与集中管理的迫切诉求,如何可以批量的设置展示的时间,并且允许用户进行交互式的控制,比如设置时间和删除时钟。在此背景下,研发一套基于现代Web技术的多时钟同步显示与控制系统,既是回应实际应用需求的必然选择,也是探索时间信息可视化新范式的有益尝试。

本系统的核心技术选型重点基于之前介绍的基础技术组件。该组件能够流畅支撑数十个时钟实例的同步动画;其灵活的坐标变换与路径绘制API,则为模拟时钟的精细刻画------包括指针运动轨迹、表盘刻度布局、光影质感渲染------提供了直接而高效的技术手段。在系统架构层面,本设计采用面向对象的思想将每个时钟封装为独立实例,内部自治时间状态与渲染逻辑,通过统一的调度器实现全局同步刷新,既保证了各时钟显示的时序一致性,又支持用户动态增删实例、自定义时区与样式,实现了扩展性与稳定性的有机统一。此外,系统深度融合了JavaScript的异步事件机制与本地存储能力,赋予用户拖拽重排、模式切换、配置持久化等丰富的交互体验。
本系统的研发价值体现在应用实践与技术探索两个维度。在应用层面,该系统可广泛部署于金融交易大厅、机场车站、指挥中心等多场景,为运营人员提供直观、精准、可定制的时间信息面板,有效降低跨时区协作的认知负荷,提升决策效率。在技术层面,本实践验证了HTML5 Canvas在实时数据可视化领域的工程可行性,多对象协同控制等关键技术的实现路径,可为同类可视化应用的开发提供参考。
一、系统简介
本家将对本应用的工程项目进行一个简单的介绍,主要包括项目的结构、界面的展示和与老的文件的一个简单对比,让大家对工程项目有一个简单的认识。
1、项目结构
这是一个基于https://github.com/KyleBing/animate-heart-canvas.git 扩展而来的小项目。可以支持多个时钟信息的同时展示和控制。
该项目的工程目录结构如下:
animate-clock-canvas-ext/
├── simple-test.html # 主页面
├── styles.css # 样式文件
├── script.js # 主脚本文件
├── animate-clock-canvas-browser.js # 原始脚本文件
└── README.md # 本说明文件
2、界面展示

纯白背景的时钟效果

黑色背景的时钟效果
以上是两种不同效果的时钟展示,这里可以自定义控制时钟的背景颜色和指针的样式还有表盘显示的格式如罗马数字还是阿拉伯数字等。大家可以根据需要进行扩展。
3、文件对比
为了让大家对修改前后的两个js文件有一个区分,这里将使用表格的形式对这两个文件进行一个简单的介绍。`script.js` 与 `animate-clock-canvas-browser.js` 的对比如下表所示:
|----------------|-----------------------------|----------------------------------------------------------------------------------------|
| 特性 | script.js | animate-clock-canvas-browser.js |
| ClockManager 类 | 包含,用于管理多个时钟实例 | 不包含 |
| 时钟标题支持 | 支持传入和绘制标题 | 不支持 |
| 时分秒显示 | 在表盘中显示 xx:xx:xx 格式的时间 | 不支持 |
| 预设配置数量 | 1 个,dateFontSize = 50 | 2 个,dateFontSize = 40 |
| 额外方法 | drawTitle()`, `drawTime() | `setCustomTime()`, `useRealTimeMode()`, `responsiveAdjust()`, `drawRefLines()` |
| 注释 | 详细的 JSDoc 注释 | 基本注释 |
| 初始化逻辑 | 自动初始化 ClockManager 并添加默认时钟 | 需要外部调用 |
二、新特性介绍
本节将重点介绍改造组件的新特性,包括时钟标题支持、时分秒显示和时钟管理器三个方面的知识。通过这些新特性的介绍,让大家增强对其的了解。
1、时钟标题支持
之前的老实例中,时钟是不支持标题的,而这里希望在展示时钟的同时还展示类似于文字的标注内容,如下图所示:

想要实现能在页面中动态的添加并且修改标题,需要在控件初始化时能传入这个问题标题。因此在时钟的构造方法中新增标题的属性,代码如下:
javascript
/**
* 构造函数,创建一个新的时钟实例
* @param {string} theme - 主题,可选值:'white' | 'black'
* @param {string} pointerType - 指针类型,可选值:'rounded' | 'pointer'
* @param {string} numberType - 数字类型,可选值:'ALB' | 'LM'
* @param {string} isSkipHourLabel - 是否跳过小时标签,'1' 表示跳过,'0' 表示不跳过
* @param {string} isZoomSecond - 是否放大秒数,'1' 表示放大,'0' 表示不放大
* @param {string} isShowDetailInfo - 是否显示详细信息,'1' 表示显示,'0' 表示不显示
* @param {string} isShowWeekDate - 是否显示星期和日期,'1' 表示显示,'0' 表示不显示
* @param {string} isShowShadow - 是否显示阴影,'1' 表示显示,'0' 表示不显示
* @param {string} preset - 预设配置,默认为 '0'
* @param {Date} customTime - 自定义时间,如果为 null 则使用实时时间
* @param {string} title - 时钟标题,可选
*/
constructor(
theme, pointerType, numberType, isSkipHourLabel,
isZoomSecond, isShowDetailInfo = '1', isShowWeekDate = '1' ,
isShowShadow = '1', preset = '0', customTime = null, title = null
) {
this.isPlayConstantly = true;
this.theme = theme || 'white';
this.numberType = (numberType || 'ALB').toUpperCase();
this.pointerType = pointerType || 'rounded';
this.isSkipHourLabel = isSkipHourLabel === '1';
this.isZoomSecond = isZoomSecond === '1';
this.isShowDetailInfo = isShowDetailInfo === '0';
this.isShowWeekDate = isShowWeekDate === '1';
this.isShowShadow = isShowShadow === '1';
this.preset = preset || '0';
this.customTime = customTime || null;
this.useRealTime = customTime === null;
this.title = title; // 时钟标题,保留空字符串
this.panelRadius = 600;
this.configFrame = {
center: {
x: 600,
y: 300,
},
width : 1200,
height: 600,
};
this.configClock = PRESETS[Number(this.preset)];
this.timeLine = 0;
this.rotateAngleHour = 0;
this.rotateAngleMinute = 0;
this.rotateAngleSecond = 0;
this.init();
}
在第 272-273 行:在 `draw()` 方法中调用 `drawTitle()` 方法,代码如下:

到这里一定别忘了新增一个drawTitle函数,具体如下:
javascript
/**
* 绘制时钟标题
* @param {CanvasRenderingContext2D} ctx - 画布上下文
* @param {Object} center - 中心点坐标
*/
drawTitle(ctx, center) {
if (this.title !== undefined && this.title !== null) {
ctx.save();
ctx.translate(center.x, center.y);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '20px Arial';
ctx.fillStyle = THEME[this.theme].colorMain;
ctx.fillText(this.title, 0, this.panelRadius / 2 + 20);
ctx.restore();
}
}
调用的方法非常简单, 在 `addClock()` 方法中获取标题输入值,将标题传递给`AnimateClockCanvas` 构造函数,最后在 UI 中显示时钟标题即可。
2、时分秒显示
除了可以显示时钟的标题之后,还需要能展示时钟的时分秒信息。在表盘中显示 xx:xx:xx 格式的实时时间,点击更新时间后时间会跟随变化。

更新时间并绘制时分秒的代码如下:
javascript
/**
* 绘制时分秒
* @param {CanvasRenderingContext2D} ctx - 画布上下文
* @param {Object} center - 中心点坐标
*/
drawTime(ctx, center){
ctx.save();
const fontSize = this.configClock.dateFontSize * 0.8;
ctx.font = `${fontSize}px Arial`;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
const currentTime = this.getCurrentTime();
const hours = String(currentTime.getHours()).padStart(2, '0');
const minutes = String(currentTime.getMinutes()).padStart(2, '0');
const seconds = String(currentTime.getSeconds()).padStart(2, '0');
const timeString = `${hours}:${minutes}:${seconds}`;
ctx.fillStyle = THEME[this.theme].colorMain;
ctx.fillText(timeString, center.x - this.panelRadius / 2 - fontSize, center.y);
ctx.restore();
}
3、时钟管理器
针对时钟的动态管理是本实例的核心,本实例需要对所有的时钟实现动态管理。可以添加、删除、更新多个时钟实例。

为了实现时钟的管理,这里需要添加一个类ClockManager。源码如下:

三、实际集成
本节将具体来介绍一下如何进行实际的时钟集成,包括时钟的添加和删除等操作。
1、添加时钟
在页面顶部的控制面板中,选择时钟主题、指针类型、数字类型;输入时钟标题(可选);点击「添加时钟」按钮;新时钟会显示在下方的时钟列表中。

默认打开页面时会自动添加一个默认的时钟,添加时钟的代码如下:
javascript
// 初始化时钟管理器
const clockManager = new ClockManager();
// 初始化时添加一个默认时钟
window.onload = function() {
const clock = new AnimateClockCanvas('white', 'rounded', 'ALB', '0', '1', '1', '1', '1', '0');
clockManager.clocks.push(clock);
clockManager.createClockElement(clock);
};
2、删除时钟
除了自定义添加时钟,时钟的删除也是重要的一环,点击时钟下方的「删除」按钮;时钟会从页面中移除。
javascript
/**
* 删除时钟
* @param {string} clockId - 时钟ID
*/
deleteClock(clockId) {
const index = this.clocks.findIndex(clock => clock.clockId === clockId);
if (index !== -1) {
this.clocks[index].isPlayConstantly = false;
const canvas = document.getElementById(clockId);
if (canvas) {
canvas.remove();
}
const clockItem = document.querySelector(`.clock-item[data-clock-id="${clockId}"]`);
if (clockItem) {
clockItem.remove();
}
this.clocks.splice(index, 1);
}
}
以上的删除方法在创建时钟时会添加删除的方法,如下图所示。在每个删除的按钮处都需绑定时钟id,移除的时候需要传入时钟id然后进行移除。

通过这个方法就能实现时钟的动态移除。
四、总结
以上就是本文的主要内容,本文详细的讲解了如何实现一个动态管理的时钟应用。文章首先对项目结构、界面展示和新旧两个文件的对比,让读者对内容有了一个基本的了解。其次结合源码对项目的新特性进行了详细的讲解。最后围绕着核心介绍如何实现时钟的动态管理,让大家掌握具体的实现。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。