Canvas签名功能常见的几种问题
2025年5月18日 23:52
. 如何实现基础签名功能?
<!DOCTYPE html>
<canvas id="signature" width="500" height="300"></canvas>
<script>
const canvas = document.getElementById('signature');
const ctx = canvas.getContext('2d');
let isDrawing = false;
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
function startDrawing(e) {
isDrawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
}
function draw(e) {
if (!isDrawing) return;
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}
function stopDrawing() {
isDrawing = false;
}
</script>
1. 如何检测签名是否为空?
function isCanvasBlank(canvas) {
// 获取画布像素数据
const context = canvas.getContext('2d');
const pixelBuffer = new Uint32Array(
context.getImageData(0, 0, canvas.width, canvas.height).data.buffer
);
// 检查是否有非透明像素
return !pixelBuffer.some(color => color !== 0);
}
2. 如何处理不同设备DPI问题?
function setupHighDPICanvas(canvas) {
// 获取设备像素比
const dpr = window.devicePixelRatio || 1;
// 获取CSS显示尺寸
const rect = canvas.getBoundingClientRect();
// 设置实际尺寸为显示尺寸乘以像素比
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
// 缩放上下文以匹配CSS尺寸
const ctx = canvas.getContext('2d');
ctx.scale(dpr, dpr);
// 设置CSS尺寸保持不变
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
}
3. 如何实现撤销/重做功能?
class SignatureHistory {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.history = [];
this.currentStep = -1;
}
saveState() {
// 截取当前画布状态
const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
// 如果当前不是最新状态,截断历史
if (this.currentStep < this.history.length - 1) {
this.history = this.history.slice(0, this.currentStep + 1);
}
this.history.push(imageData);
this.currentStep++;
}
undo() {
if (this.currentStep > 0) {
this.currentStep--;
this.ctx.putImageData(this.history[this.currentStep], 0, 0);
}
}
redo() {
if (this.currentStep < this.history.length - 1) {
this.currentStep++;
this.ctx.putImageData(this.history[this.currentStep], 0, 0);
}
}
}
4. 如何添加签名压力感应效果?
// 监听指针事件(支持压力感应设备)
canvas.addEventListener('pointerdown', startDrawing);
canvas.addEventListener('pointermove', drawWithPressure);
canvas.addEventListener('pointerup', stopDrawing);
function drawWithPressure(e) {
if (!isDrawing) return;
// 获取压力值(0-1),默认0.5用于鼠标
const pressure = e.pressure || 0.5;
// 根据压力调整线条宽度
ctx.lineWidth = pressure * 10 + 2; // 2-12px范围
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
}
5. 如何防止签名图片被篡改?
function generateSignatureHash(canvas) {
// 获取画布数据
const imageData = canvas.toDataURL('image/png');
// 使用SHA-256生成哈希
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(imageData))
.then(hash => {
// 转换为十六进制字符串
const hashArray = Array.from(new Uint8Array(hash));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
});
}
6. 如何加密存储签名数据?
async function encryptSignatureData(canvas) {
// 获取画布数据
const imageData = canvas.toDataURL('image/png');
// 生成加密密钥
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
// 加密数据
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
new TextEncoder().encode(imageData)
);
return {
key,
iv,
encryptedData: Array.from(new Uint8Array(encrypted))
};
}
7. 如何实现多人协同签名?
class CollaborativeSignature {
constructor(canvas, socket) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.socket = socket;
// 本地绘制事件
canvas.addEventListener('mousedown', this.handleLocalDrawStart.bind(this));
canvas.addEventListener('mousemove', this.handleLocalDrawing.bind(this));
canvas.addEventListener('mouseup', this.handleLocalDrawEnd.bind(this));
// 远程绘制事件
socket.on('remote-draw-start', this.handleRemoteDrawStart.bind(this));
socket.on('remote-drawing', this.handleRemoteDrawing.bind(this));
socket.on('remote-d