动态时钟系统:SpringMVC + Canvas粒子动画 + 毛玻璃UI
动态时钟系统:SpringMVC + Canvas粒子动画 + 毛玻璃UI
本项目实现了一个全动态时钟Web应用,包含模拟机械时钟、数字时钟、动态粒子背景及鼠标交互。后端基于 SpringMVC,前端使用原生 JavaScript 与 Canvas,并成功部署至阿里云 ECS。
| 项目信息 | 内容 |
|---|---|
| 作者 | 方致沅、王鑫杰、李润哲、康奕彬 |
| 日期 | 2026年6月 |
| 技术栈 | SpringMVC 5, JSP, JavaScript, Canvas, CSS3 |
一、项目概览
页面中央是一个毛玻璃风格的卡片面板,包含:
- 模拟机械时钟:时针、分针、秒针连续平滑运动,与系统时间完全同步。
- 数字时钟:显示当前时、分、秒,格式
HH:MM:SS。 - 日期与星期:显示年-月-日及中文星期。
- 动态背景:180个彩色粒子缓慢漂浮,粒子之间自动连线,鼠标靠近时产生斥力,点击页面生成涟漪波纹。
- 智能重置:点击按钮立即同步系统时间并重置动画。
项目采用 MVC 设计模式,视图层使用 JSP,控制器由 SpringMVC 管理,前端资源完全分离。
二、项目组成
task9/
│
├── src/
│ └── main/
│ └── java/
│ └── com/
│ └── task9/
│ ├── bean/
│ │ ├── ClockStyleBean.java
│ │ └── TimeBean.java
│ └── controller/
│ └── ClockController.java
│
├── WebContent/
│ ├── css/
│ │ ├── analog.css
│ │ ├── background.css
│ │ ├── digital.css
│ │ ├── glass.css
│ │ └── layout.css
│ ├── js/
│ │ ├── analogClock.js
│ │ ├── digitalClock.js
│ │ ├── particle.js
│ │ ├── reset.js
│ │ ├── resize.js
│ │ ├── ring.js
│ │ └── star.js
│ ├── WEB-INF/
│ │ ├── jsp/
│ │ │ └── clock.jsp
│ │ ├── lib/
│ │ ├── springmvc-servlet.xml
│ │ └── web.xml
│ └── META-INF/
│ └── MANIFEST.MF
│
├── build/
├── lib/
├── target/
├── .classpath
├── .project
└── task9.war
三、后端核心代码
3.1 SpringMVC 控制器
ClockController 处理 /clock 请求,返回 clock 视图。
package com.task9.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ClockController {
@RequestMapping("/")
public String index() {
return "clock";
}
@RequestMapping("/clock")
public String clock() {
return "clock";
}
}
package com.task9.bean;
import java.io.Serializable;
public class TimeBean implements Serializable {
private static final long serialVersionUID = 1L;
private int hour;
private int minute;
private int second;
private String date;
private String week;
// getters / setters 省略
}
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="...">
<mvc:resources mapping="/css/" location="/css/" />
<mvc:resources mapping="/js/" location="/js/" />
(function () { const hourHand = document.getElementById("hour-hand"); const minuteHand = document.getElementById("minute-hand"); const secondHand = document.getElementById("second-hand");
function updateClock() {
const now = new Date();
const ms = now.getMilliseconds();
const second = now.getSeconds() + ms / 1000;
const minute = now.getMinutes() + second / 60;
const hour = (now.getHours() % 12) + minute / 60;
secondHand.style.transform = rotate(${second * 6 - 90}deg);
minuteHand.style.transform = rotate(${minute * 6 - 90}deg);
hourHand.style.transform = rotate(${hour * 30 - 90}deg);
requestAnimationFrame(updateClock);
}
updateClock();
})();
(function () { const timeEl = document.getElementById("digital-time"); const dateEl = document.getElementById("digital-date"); const weekEl = document.getElementById("digital-week");
function padZero(n) { return n < 10 ? '0' + n : n; }
function updateDigital() {
const now = new Date();
timeEl.textContent = ${padZero(now.getHours())}😒{padZero(now.getMinutes())}😒{padZero(now.getSeconds())};
dateEl.textContent = ${now.getFullYear()}-${padZero(now.getMonth()+1)}-${padZero(now.getDate())};
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
weekEl.textContent = weekDays[now.getDay()];
setTimeout(updateDigital, 1000);
}
updateDigital();
})();
// particle.js 核心逻辑(精简) window.onload = () => { const canvas = document.getElementById('particleCanvas'); const ctx = canvas.getContext('2d'); let mouse = { x: -1000, y: -1000, radius: 160 };
window.addEventListener('mousemove', (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
class Particle {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 2 + 1;
this.speedX = (Math.random() - 0.5) * 0.18;
this.speedY = (Math.random() - 0.5) * 0.18;
this.color = hsla(${Math.random() * 360}, 80%, 80%, 0.85);
}
update() {
this.x += this.speedX;
this.y += this.speedY;
// 边界反弹
if (this.x <= 0 || this.x >= canvas.width) this.speedX = -1;
if (this.y <= 0 || this.y >= canvas.height) this.speedY = -1;
// 鼠标排斥
const dx = this.x - mouse.x, dy = this.y - mouse.y;
const dist = Math.sqrt(dxdx + dydy);
if (dist < mouse.radius) {
this.x += dx / dist * 2.2;
this.y += dy / dist * 2.2;
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();
}
}
// 创建180个粒子,并实现连线动画...
};
本地效果截图
https://./images/local_demo.png
(请替换为您本地的实际截图,应显示毛玻璃面板、模拟钟、数字时钟及动态粒子)
6.2 阿里云 ECS 部署
将项目打包为 task9.war,上传至云服务器 Tomcat 的 webapps 目录。
启动 Tomcat:bin/startup.sh
在安全组中开放 8080 端口。
公网访问地址:http://118.31.44.31:8080/task9/clock
云端访问截图
https://./images/cloud_demo.png
(请替换为您在公网环境下的实际截图,确保时钟显示及所有动画正常)
七、常见问题与解决方案
问题现象 原因分析 解决方案
模拟钟指针初始角度错误 CSS 变换中心点未设置 设置 transform-origin: 0% 50%,角度减 90deg
数字时钟与模拟钟不同步 模拟钟未使用毫秒,秒针跳跃 模拟钟中用 ms/1000 计算连续秒数,使用 requestAnimationFrame
静态资源(CSS/JS)返回 404 SpringMVC 拦截了所有请求 在 springmvc-servlet.xml 中添加 mvc:resources 映射
粒子背景无鼠标交互 Canvas 未绑定鼠标事件或坐标未更新 全局监听 mousemove,在 update 中计算距离并施加斥力
ECS 部署后页面布局混乱 文件大小写不一致或 WAR 解压不完整 检查 Tomcat 日志,确保目录名与 mvc:resources 中完全一致
重置按钮无法清除 Canvas 残留 Canvas 未完全清空,粒子数组未重置 调用 ctx.clearRect 并重新触发 resize 事件初始化粒子
八、总结与源码获取
本次项目完整实践了 SpringMVC + JSP + Canvas 动画 的全流程开发:
MVC 架构清晰分离控制器、视图与模型,便于维护。
前端 Canvas 粒子系统带来了丰富的视觉体验,180 个粒子 + 动态连线仍保持 60fps 流畅度。
全流程部署从本地 Eclipse 调试到阿里云 ECS 上线,加深了对 Tomcat 配置、Linux 服务器运维的理解。
完整源码可参考实验报告附件或小组 GitHub 仓库:github.com/your-repo/task9-clock (示例链接)。
欢迎交流探讨!
博客园发布提示:

浙公网安备 33010602011771号