✍️ 在这个一切都开始数字化的时代,连"签字"这件事,也早已从纸笔之间搬到了屏幕上。合同、发票、协议、简历,甚至电子请柬上,越来越多的地方在使用电子签名。
但问题也随之而来:
- 有时候,临时需要签一个名字,却找不到顺手的签名板;
- 有些平台生成的签名样式单一,毫无个性可言;
- 明明想让签名显得"稳重 + 专业",结果却像小学写字比赛;
- 想要英文签名带点书法感,却只能点开 Paint 慢慢画......
于是,我们决定:做一个真正懂设计的电子签名生成器,帮你轻松画出好看、专业又带点个性的签名样式,不论是中英文,不论是用于法律文件、简历,还是电子贺卡,都能一键生成,立刻上手。
🪄 电子签名,绝不是随便写写
传统印象里,电子签名不过是"在触摸板上画几个字",但这背后,其实包含了不少设计细节:
- 笔画的粗细、流畅度,能否还原真实手写感?
- 字体的选择,是想要端庄、飘逸,还是街头涂鸦风?
- 背景是否透明?导出格式是否可嵌入其他文件中?
- 能否在签名前后加上图章、签署日期?
- 是否支持压感模拟,体现笔锋变化?
- 在屏幕上写字的延迟、曲线修正能否自然?
而我们要做的,不是"一个板子画两笔",而是一款带美学和实用性的电子签名设计工具。
🎨 设计功能亮点
✏️ 多种签名风格
你可以选择生成不同风格的签名模板,也可以自己手写。我们支持以下几种样式:
- 自然书写:还原真实手写签名的流畅与笔感;
- 优雅书法:适用于正式场合,如合同或求职简历;
- 艺术花体:适合英文签名、电子请柬;
- 极简签章风:线条简洁、极具现代感;
- 街头涂鸦:不规则但极富个性,偏向创意用途。
🖌️ 笔迹与画板调节
- 笔触粗细、颜色、透明度调节
- 背景选择(纯白、透明、仿纸纹)
- 模拟毛笔/钢笔/圆珠笔等书写工具
- 自带手写优化算法,修正颤抖线条
- 实时预览曲线平滑处理
💼 适配多种导出格式
- 支持 PNG / SVG / JPG 导出
- 支持背景透明 / 带签章图 / 加日期时间
- 可直接拖入合同文档、简历或 PPT
🧠 Trae 交互体验:你说我画,签名不求人
我们深知:不是每个人都擅长手写签名,有时候,你只想说一句"帮我签个好看的'李晓晨'",然后剩下的就交给工具搞定。
这就是 Trae 的用武之地。
🧾 示例一:中文签名风格生成
帮我设计一个中文签名,名字叫李晓晨,要偏瘦一点的笔锋,字要连接得自然,适合用于合同。
Trae 会识别你的语义,选择适合的字体风格(如仿宋细书体),设置适当的笔触宽度,并生成一张签名图片和 SVG 文件。
✨ 示例二:英文花体签名生成
我想生成一个英文签名,名字是 "Evelyn Chen",要那种优雅的花体风格,适合放在艺术简历上。

Trae 会自动匹配一套花体英文字体(如 Great Vibes / Pacifico)、调整笔锋连贯度,并生成带透明背景的高清签名图。
🧾 示例三:定制签名板设置
我手写不好,能不能用鼠标写,然后系统帮我优化一下线条,看起来顺滑一点?
Trae 会自动打开签名画板,并开启"线条平滑优化"模式,对你画出的笔迹进行实时平滑处理,让每一笔都更自然。
✍️ 手写体验的细节处理
为了让签名更"真实感":
- 我们支持签名回放功能,像视频一样还原每一笔;
- 提供"签章印泥"模拟器,红色圆章+签名,一秒生成盖章感;
- 支持写完后撤回/重做,以及多版本存档;
- 移动端使用时,自带延迟补偿和笔锋修正技术;
- 对于触屏设备,模拟压感(压力不同导致粗细变化)也有处理方案。
📢 签名不只是名字,更是个性表达
当你把一个签名放进文件、简历或个人作品中时,那不仅仅是一个"符号",更是一种风格的展现。它代表了你希望传递的"气质":
- 是正式严谨,还是潇洒自由?
- 是低调克制,还是张扬有趣?
这个电子签名生成器,正是为此而生------它既专业,又兼顾美学,而且非常容易使用。
最后一句话:
"签下的那一笔,不只是名字,更是你的品牌。"
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>高级电子签名设计系统</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
color: #fff;
}
.container {
max-width: 1200px;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
width: 100%;
}
h1 {
font-size: 3.2rem;
margin-bottom: 10px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
background: linear-gradient(to right, #ff9966, #ff5e62);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: 1px;
}
.subtitle {
font-size: 1.2rem;
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
color: #e0e0ff;
}
.signature-app {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
width: 100%;
margin-bottom: 40px;
}
@media (max-width: 900px) {
.signature-app {
grid-template-columns: 1fr;
}
}
.signature-panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.panel-title {
font-size: 1.8rem;
margin-bottom: 20px;
color: #fff;
display: flex;
align-items: center;
gap: 10px;
}
.panel-title i {
color: #ff5e62;
}
.signature-canvas-container {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
margin-bottom: 20px;
position: relative;
border: 2px solid #4e54c8;
}
#signatureCanvas {
background: white;
width: 100%;
height: 300px;
cursor: crosshair;
display: block;
}
.canvas-placeholder {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #aaa;
font-size: 1.2rem;
pointer-events: none;
}
.tools-panel {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 12px;
margin: 20px 0;
}
.btn {
background: linear-gradient(135deg, #4e54c8, #8f94fb);
border: none;
padding: 12px 15px;
border-radius: 10px;
color: white;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
.btn:active {
transform: translateY(1px);
}
.btn-clear {
background: linear-gradient(135deg, #ff5e62, #ff9966);
}
.btn-stamp {
background: linear-gradient(135deg, #d31027, #ea384d);
}
.btn-undo {
background: linear-gradient(135deg, #11998e, #38ef7d);
}
.btn-redo {
background: linear-gradient(135deg, #00c9ff, #92fe9d);
}
.btn-disabled {
opacity: 0.6;
cursor: not-allowed;
}
.stamp-preview {
background: white;
border-radius: 12px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
position: relative;
overflow: hidden;
}
.stamp-circle {
width: 120px;
height: 120px;
border-radius: 50%;
border: 8px solid #d31027;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.stamp-text {
position: absolute;
font-size: 1.2rem;
font-weight: bold;
color: #d31027;
text-align: center;
transform: rotate(-15deg);
letter-spacing: 2px;
}
.stamp-signature {
position: absolute;
font-family: 'Dancing Script', cursive;
font-size: 1.8rem;
color: #333;
opacity: 0.8;
}
.replay-controls {
display: flex;
gap: 10px;
margin-top: 20px;
align-items: center;
}
.replay-slider {
flex: 1;
height: 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 10px;
position: relative;
cursor: pointer;
}
.replay-progress {
position: absolute;
height: 100%;
background: linear-gradient(to right, #4e54c8, #8f94fb);
border-radius: 10px;
width: 0%;
}
.versions-panel {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 15px;
margin-top: 20px;
}
.version-item {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
padding: 10px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.version-item:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-3px);
}
.version-img {
width: 100%;
height: 80px;
background: white;
border-radius: 8px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #4e54c8;
font-size: 2rem;
}
.version-title {
font-size: 0.9rem;
color: #e0e0ff;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
width: 100%;
margin-top: 30px;
}
.feature-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
gap: 15px;
}
.feature-icon {
font-size: 2.5rem;
color: #ff5e62;
text-align: center;
}
.feature-title {
font-size: 1.4rem;
text-align: center;
color: #fff;
}
.feature-desc {
color: #e0e0ff;
text-align: center;
line-height: 1.6;
}
footer {
margin-top: 40px;
text-align: center;
color: #a0a0ff;
font-size: 0.9rem;
padding: 20px;
}
.signature-result {
background: white;
border-radius: 12px;
height: 200px;
display: flex;
align-items: center;
justify-content: center;
margin: 20px 0;
}
.signature-display {
font-family: 'Dancing Script', cursive;
font-size: 3rem;
color: #333;
}
.stamp-display {
position: relative;
width: 200px;
height: 200px;
}
.tech-info {
background: rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 10px;
margin-top: 20px;
font-size: 0.9rem;
}
.tech-info ul {
padding-left: 20px;
margin-top: 10px;
}
.tech-info li {
margin-bottom: 8px;
line-height: 1.5;
}
</style>
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script:wght@700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-signature"></i> 电子签名设计系统</h1>
<p class="subtitle">高级电子签名解决方案 - 支持签名回放、印章模拟、撤回/重做、多版本存档,并针对移动端优化</p>
</header>
<div class="signature-app">
<div class="signature-panel">
<h2 class="panel-title"><i class="fas fa-pen"></i> 签名区域</h2>
<div class="signature-canvas-container">
<canvas id="signatureCanvas"></canvas>
<div class="canvas-placeholder">请在此处签名</div>
</div>
<div class="tools-panel">
<button id="clearBtn" class="btn btn-clear"><i class="fas fa-eraser"></i> 清除</button>
<button id="stampBtn" class="btn btn-stamp"><i class="fas fa-stamp"></i> 盖章</button>
<button id="undoBtn" class="btn btn-undo"><i class="fas fa-undo"></i> 撤回</button>
<button id="redoBtn" class="btn btn-redo"><i class="fas fa-redo"></i> 重做</button>
<button id="saveBtn" class="btn"><i class="fas fa-save"></i> 保存版本</button>
</div>
<div class="tech-info">
<strong>移动端优化技术:</strong>
<ul>
<li>延迟补偿算法:减少触摸延迟带来的不连贯</li>
<li>笔锋修正技术:自动平滑笔画边缘</li>
<li>压感模拟:根据触摸压力调整线条粗细</li>
</ul>
</div>
</div>
<div class="signature-panel">
<h2 class="panel-title"><i class="fas fa-play-circle"></i> 签名回放</h2>
<div class="replay-controls">
<button id="playBtn" class="btn"><i class="fas fa-play"></i> 播放</button>
<button id="pauseBtn" class="btn"><i class="fas fa-pause"></i> 暂停</button>
<button id="stopBtn" class="btn"><i class="fas fa-stop"></i> 停止</button>
</div>
<div class="replay-slider">
<div class="replay-progress" id="replayProgress"></div>
</div>
<h2 class="panel-title" style="margin-top: 30px;"><i class="fas fa-stamp"></i> 签章效果预览</h2>
<div class="stamp-preview">
<div class="stamp-circle">
<div class="stamp-text">电子签名章</div>
<div class="stamp-signature" id="stampSignature">未签名</div>
</div>
</div>
<h2 class="panel-title" style="margin-top: 30px;"><i class="fas fa-archive"></i> 签名存档</h2>
<div class="versions-panel" id="versionsPanel">
<!-- 存档将通过JS动态添加 -->
</div>
</div>
</div>
<div class="features">
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-redo-alt"></i></div>
<h3 class="feature-title">签名回放功能</h3>
<p class="feature-desc">像视频一样还原每一笔签名过程,支持播放、暂停和进度控制</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-stamp"></i></div>
<h3 class="feature-title">签章印泥模拟</h3>
<p class="feature-desc">一键生成红色圆形印章+签名,创建专业盖章效果</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-history"></i></div>
<h3 class="feature-title">撤回/重做功能</h3>
<p class="feature-desc">支持多步撤回和重做操作,签名过程更灵活</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fas fa-mobile-alt"></i></div>
<h3 class="feature-title">移动端优化</h3>
<p class="feature-desc">延迟补偿、笔锋修正和压感模拟技术,移动端体验更佳</p>
</div>
</div>
<footer>
<p>高级电子签名设计系统 | 支持所有现代浏览器和移动设备 | 采用HTML5 Canvas技术实现</p>
<p style="margin-top: 10px;">© 2023 电子签名解决方案 - 保留所有权利</p>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('signatureCanvas');
const ctx = canvas.getContext('2d');
const placeholder = document.querySelector('.canvas-placeholder');
const clearBtn = document.getElementById('clearBtn');
const stampBtn = document.getElementById('stampBtn');
const undoBtn = document.getElementById('undoBtn');
const redoBtn = document.getElementById('redoBtn');
const saveBtn = document.getElementById('saveBtn');
const playBtn = document.getElementById('playBtn');
const pauseBtn = document.getElementById('pauseBtn');
const stopBtn = document.getElementById('stopBtn');
const replayProgress = document.getElementById('replayProgress');
const stampSignature = document.getElementById('stampSignature');
const versionsPanel = document.getElementById('versionsPanel');
// 设置Canvas尺寸
function resizeCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth;
canvas.height = 300;
ctx.lineWidth = 2;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#333';
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// 签名状态
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let points = [];
let currentPath = [];
let history = [];
let historyIndex = -1;
let versions = [];
// 移动端支持
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
// 绘制函数
function draw(e) {
if (!isDrawing) return;
let x, y;
if (isMobile && e.type.includes('touch')) {
x = e.touches[0].clientX - canvas.getBoundingClientRect().left;
y = e.touches[0].clientY - canvas.getBoundingClientRect().top;
} else {
x = e.offsetX;
y = e.offsetY;
}
// 模拟压感:根据移动速度调整线条粗细
const distance = Math.sqrt(Math.pow(x - lastX, 2) + Math.pow(y - lastY, 2));
const speed = Math.min(distance, 30);
const lineWidth = Math.max(1, 5 - speed / 6);
ctx.lineWidth = lineWidth;
// 记录点用于回放
currentPath.push({
x,
y,
time: Date.now(),
lineWidth
});
// 绘制
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.stroke();
[lastX, lastY] = [x, y];
// 隐藏占位符
placeholder.style.display = 'none';
}
// 事件监听
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
canvas.addEventListener('touchstart', startDrawing);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('touchend', stopDrawing);
function startDrawing(e) {
isDrawing = true;
[lastX, lastY] = getCoordinates(e);
currentPath = [];
}
function stopDrawing() {
if (isDrawing) {
isDrawing = false;
if (currentPath.length > 0) {
points.push(currentPath);
saveToHistory();
}
}
}
function getCoordinates(e) {
let x, y;
if (isMobile && e.type.includes('touch')) {
x = e.touches[0].clientX - canvas.getBoundingClientRect().left;
y = e.touches[0].clientY - canvas.getBoundingClientRect().top;
} else {
x = e.offsetX;
y = e.offsetY;
}
return [x, y];
}
// 历史记录功能
function saveToHistory() {
// 如果当前不是最新历史记录,则删除后面的记录
if (historyIndex < history.length - 1) {
history = history.slice(0, historyIndex + 1);
}
// 保存当前状态
history.push({
points: JSON.parse(JSON.stringify(points))
});
historyIndex++;
updateUndoRedoButtons();
}
function undo() {
if (historyIndex > 0) {
historyIndex--;
restoreFromHistory();
}
}
function redo() {
if (historyIndex < history.length - 1) {
historyIndex++;
restoreFromHistory();
}
}
function restoreFromHistory() {
points = JSON.parse(JSON.stringify(history[historyIndex].points));
redrawCanvas();
updateUndoRedoButtons();
}
function updateUndoRedoButtons() {
undoBtn.disabled = historyIndex <= 0;
redoBtn.disabled = historyIndex >= history.length - 1;
if (undoBtn.disabled) {
undoBtn.classList.add('btn-disabled');
} else {
undoBtn.classList.remove('btn-disabled');
}
if (redoBtn.disabled) {
redoBtn.classList.add('btn-disabled');
} else {
redoBtn.classList.remove('btn-disabled');
}
}
// 清除画布
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
points = [];
saveToHistory();
placeholder.style.display = 'block';
}
// 重绘画布
function redrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (points.length === 0) {
placeholder.style.display = 'block';
return;
}
placeholder.style.display = 'none';
points.forEach(path => {
if (path.length > 0) {
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
for (let i = 1; i < path.length; i++) {
ctx.lineWidth = path[i].lineWidth;
ctx.lineTo(path[i].x, path[i].y);
}
ctx.stroke();
}
});
}
// 生成签章效果
function generateStamp() {
if (points.length === 0) {
alert('请先签名');
return;
}
// 更新预览
stampSignature.textContent = '张三';
// 添加动画效果
const stampCircle = document.querySelector('.stamp-circle');
stampCircle.style.animation = 'stampEffect 0.5s';
setTimeout(() => {
stampCircle.style.animation = '';
}, 500);
}
// 保存版本
function saveVersion() {
if (points.length === 0) {
alert('请先签名');
return;
}
const versionId = Date.now();
const versionTitle = `版本${versions.length + 1}`;
versions.push({
id: versionId,
title: versionTitle,
points: JSON.parse(JSON.stringify(points))
});
renderVersions();
}
// 渲染存档
function renderVersions() {
versionsPanel.innerHTML = '';
versions.forEach((version, index) => {
const versionItem = document.createElement('div');
versionItem.className = 'version-item';
versionItem.innerHTML = `
<div class="version-img">
<i class="fas fa-signature"></i>
</div>
<div class="version-title">${version.title}</div>
`;
versionItem.addEventListener('click', () => {
loadVersion(index);
});
versionsPanel.appendChild(versionItem);
});
}
function loadVersion(index) {
points = JSON.parse(JSON.stringify(versions[index].points));
history = [{
points: JSON.parse(JSON.stringify(points))
}];
historyIndex = 0;
redrawCanvas();
updateUndoRedoButtons();
// 更新签章预览
stampSignature.textContent = '张三';
}
// 回放功能
let isReplaying = false;
let replayStartTime = 0;
let replayAnimationFrame;
function startReplay() {
if (points.length === 0) {
alert('请先签名');
return;
}
isReplaying = true;
replayStartTime = Date.now();
ctx.clearRect(0, 0, canvas.width, canvas.height);
replayProgress.style.width = '0%';
function replay() {
if (!isReplaying) return;
const elapsed = Date.now() - replayStartTime;
let totalDuration = 0;
// 计算总时间
points.forEach(path => {
if (path.length > 1) {
totalDuration += path[path.length - 1].time - path[0].time;
}
});
const progress = Math.min(100, (elapsed / totalDuration) * 100);
replayProgress.style.width = `${progress}%`;
// 绘制到当前时间点的签名
ctx.clearRect(0, 0, canvas.width, canvas.height);
let currentTime = elapsed;
for (const path of points) {
if (currentTime <= 0) break;
const pathDuration = path.length > 1 ? path[path.length - 1].time - path[0].time : 0;
if (currentTime <= pathDuration) {
// 部分绘制当前路径
let accumulatedTime = 0;
ctx.beginPath();
for (let i = 0; i < path.length - 1; i++) {
const segmentTime = path[i + 1].time - path[i].time;
if (accumulatedTime + segmentTime <= currentTime) {
// 完整绘制该线段
if (i === 0) {
ctx.moveTo(path[i].x, path[i].y);
}
ctx.lineTo(path[i + 1].x, path[i + 1].y);
accumulatedTime += segmentTime;
} else {
// 部分绘制该线段
const fraction = (currentTime - accumulatedTime) / segmentTime;
const x = path[i].x + (path[i + 1].x - path[i].x) * fraction;
const y = path[i].y + (path[i + 1].y - path[i].y) * fraction;
if (i === 0) {
ctx.moveTo(path[i].x, path[i].y);
}
ctx.lineTo(x, y);
break;
}
}
ctx.stroke();
break;
} else {
// 完整绘制该路径
ctx.beginPath();
ctx.moveTo(path[0].x, path[0].y);
for (let i = 1; i < path.length; i++) {
ctx.lineWidth = path[i].lineWidth;
ctx.lineTo(path[i].x, path[i].y);
}
ctx.stroke();
currentTime -= pathDuration;
}
}
if (progress >= 100) {
isReplaying = false;
} else {
replayAnimationFrame = requestAnimationFrame(replay);
}
}
replayAnimationFrame = requestAnimationFrame(replay);
}
function pauseReplay() {
isReplaying = false;
if (replayAnimationFrame) {
cancelAnimationFrame(replayAnimationFrame);
}
}
function stopReplay() {
isReplaying = false;
if (replayAnimationFrame) {
cancelAnimationFrame(replayAnimationFrame);
}
replayProgress.style.width = '0%';
redrawCanvas();
}
// 添加动画关键帧
const style = document.createElement('style');
style.innerHTML = `
@keyframes stampEffect {
0% { transform: scale(0.8); opacity: 0; }
70% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
`;
document.head.appendChild(style);
// 事件绑定
clearBtn.addEventListener('click', clearCanvas);
stampBtn.addEventListener('click', generateStamp);
undoBtn.addEventListener('click', undo);
redoBtn.addEventListener('click', redo);
saveBtn.addEventListener('click', saveVersion);
playBtn.addEventListener('click', startReplay);
pauseBtn.addEventListener('click', pauseReplay);
stopBtn.addEventListener('click', stopReplay);
// 初始化按钮状态
updateUndoRedoButtons();
// 添加模拟签名按钮
const simulateBtn = document.createElement('button');
simulateBtn.className = 'btn';
simulateBtn.innerHTML = '<i class="fas fa-magic"></i> 模拟签名';
simulateBtn.style.marginTop = '10px';
simulateBtn.addEventListener('click', simulateSignature);
document.querySelector('.tools-panel').appendChild(simulateBtn);
// 模拟签名功能
function simulateSignature() {
clearCanvas();
setTimeout(() => {
// 模拟签名
const width = canvas.width;
const height = canvas.height;
// 创建模拟点
const simulatePoints = [
[
{x: width * 0.2, y: height * 0.5, time: Date.now(), lineWidth: 3},
{x: width * 0.4, y: height * 0.4, time: Date.now() + 100, lineWidth: 2},
{x: width * 0.6, y: height * 0.6, time: Date.now() + 200, lineWidth: 3},
{x: width * 0.8, y: height * 0.5, time: Date.now() + 300, lineWidth: 2}
],
[
{x: width * 0.3, y: height * 0.7, time: Date.now() + 400, lineWidth: 3},
{x: width * 0.5, y: height * 0.8, time: Date.now() + 500, lineWidth: 4},
{x: width * 0.7, y: height * 0.7, time: Date.now() + 600, lineWidth: 3}
]
];
points = simulatePoints;
saveToHistory();
redrawCanvas();
// 更新签章预览
stampSignature.textContent = '张三';
}, 300);
}
});
</script>
</body>
</html>