闽公网安备 35020302035485号

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>堆代码 duidaima.com</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="signature-container">
<canvas id="signature-pad" width="400" height="200"></canvas>
<button id="clear">清除</button>
</div>
<script src="script.js"></script>
</body>
</html>
我们将采用 <canvas> 标签来构建签名板。选择画布作为工具是因为它为我们提供了以下功能:4.利用 toDataURL 方法将签名以图像形式导出(如 PNG 或 JPEG),这在保存签名或将其上传至服务器时非常有用。
body {
display: flex;
justify - content: center;
align - items: center;
height: 100vh;
background - color: #f0f0f0;
margin: 0;
}
.signature - container {
display: flex;
flex - direction: column;
align - items: center;
}
canvas {
border: 1px solid#000;
background - color: #fff;
}
button {
margin - top: 10px;
padding: 5px 10px;
cursor: pointer;
}
然后向目录中添加一个 script.js 文件:document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var ctx = canvas.getContext('2d');
var drawing = false;
canvas.addEventListener('mousedown',
function(e) {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
});
canvas.addEventListener('mousemove',
function(e) {
if (drawing) {
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
}
});
canvas.addEventListener('mouseup',
function() {
drawing = false;
});
canvas.addEventListener('mouseout',
function() {
drawing = false;
});
document.getElementById('clear').addEventListener('click',
function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
});
让我们详细解释一下这段代码的工作原理:5.为了结束绘图,我们设置了 mouseup 和 mouseout 两个事件监听器,分别在鼠标按钮释放和鼠标光标移出画布时停止绘图。此外,当用户点击清除按钮时,ctx.clearRect(0, 0, canvas.width, canvas.height); 用于清除整个画布,为新的绘图做准备。
document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var ctx = canvas.getContext('2d');
var drawing = false;
function startDrawing(e) {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
}
function draw(e) {
if (drawing) {
ctx.lineTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
ctx.stroke();
}
}
function stopDrawing() {
drawing = false;
}
// 鼠标事件
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);
canvas.addEventListener('touchcancel', stopDrawing);
document.getElementById('clear').addEventListener('click',
function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
});
对于触摸事件,使用 e.touches[0].clientX 和 e.touches[0].clientY 来获取触摸坐标。为了考虑画布的位置,使用 canvas.offsetLeft 和 canvas.offsetTop 进行调整:<div class="controls">
<select id="stroke-style">
<option value="pen">
钢笔
</option>
<option value="brush">
刷子
</option>
</select>
<button id="clear">
JavaScript签名板
</button>
</div>
然后我们更新我们的 CSS:body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
margin: 0;
}
.signature-container {
display: flex;
flex-direction: column;
align-items: center;
}
canvas {
border: 1px solid #000;
background-color: #fff;
}
.controls {
margin-top: 10px;
display: flex;
gap: 10px;
}
button, select {
padding: 5px 10px;
cursor: pointer;
}
最后,更新 script.js:document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var ctx = canvas.getContext('2d');
var drawing = false;
var strokeStyle = 'pen';
function startDrawing(e) {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
}
function draw(e) {
if (drawing) {
ctx.lineTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
ctx.stroke();
}
}
function stopDrawing() {
drawing = false;
}
// 鼠标事件
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);
canvas.addEventListener('touchcancel', stopDrawing);
document.getElementById('clear').addEventListener('click',
function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
document.getElementById('stroke-style').addEventListener('change',
function(e) {
strokeStyle = e.target.value;
if (strokeStyle === 'pen') {
ctx.lineWidth = 2;
ctx.lineCap = 'round';
} else if (strokeStyle === 'brush') {
ctx.lineWidth = 5;
ctx.lineCap = 'round';
}
});
// 设置初始笔画样式
ctx.lineWidth = 2;
ctx.lineCap = 'round';
});
处理响应性2.将画布元素的宽度设置为 width: 100%,高度设置为 height: auto,以实现响应式效果。
body {
display: flex;
justify - content: center;
align - items: center;
height: 100vh;
background - color: #f0f0f0;
margin: 0;
}
.signature - container {
display: flex;
flex - direction: column;
align - items: center;
width: 90 % ;
max - width: 600px;
}
canvas {
border: 1px solid#000;
background - color: #fff;
width: 100 % ;
height: auto;
}
.controls {
margin - top: 10px;
display: flex;
gap: 10px;
}
button,
select {
padding: 5px 10px;
cursor: pointer;
}
这是在添加之后 script.js 的样子:document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var ctx = canvas.getContext('2d');
var drawing = false;
var strokeStyle = 'pen';
function resizeCanvas() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.lineWidth = strokeStyle === 'pen' ? 2 : 5;
ctx.lineCap = 'round';
}
function startDrawing(e) {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
}
function draw(e) {
if (drawing) {
ctx.lineTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
ctx.stroke();
}
}
function stopDrawing() {
drawing = false;
}
// 鼠标事件
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);
canvas.addEventListener('touchcancel', stopDrawing);
document.getElementById('clear').addEventListener('click',
function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
document.getElementById('stroke-style').addEventListener('change',
function(e) {
strokeStyle = e.target.value;
ctx.lineWidth = strokeStyle === 'pen' ? 2 : 5;
});
// 初始画布设置
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
});
你可能会观察到,当改变浏览器窗口大小时,画布上的签名会消失,这是因为调整画布尺寸会自动清除其内容。这是 <canvas> 元素的标准行为。不过,我们可以通过一些技巧来避免这个问题。为了在缩放画布时保持签名的完整性,并且能够正确地对其进行缩放,我们需要先保存画布上的签名,然后在调整画布尺寸后再重新绘制它。我们可以通过 toDataURL 方法来保存画布上的签名。document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var ctx = canvas.getContext('2d');
var drawing = false;
var strokeStyle = 'pen';
var signatureData = null;
function resizeCanvas() {
if (signatureData) {
var img = new Image();
img.src = signatureData;
img.onload = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
setStrokeStyle();
};
} else {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
setStrokeStyle();
}
}
function setStrokeStyle() {
if (strokeStyle === 'pen') {
ctx.lineWidth = 2;
ctx.lineCap = 'round';
} else if (strokeStyle === 'brush') {
ctx.lineWidth = 5;
ctx.lineCap = 'round';
}
}
function startDrawing(e) {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
}
function draw(e) {
if (drawing) {
ctx.lineTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
ctx.stroke();
}
}
function stopDrawing() {
drawing = false;
signatureData = canvas.toDataURL();
}
// 鼠标事件
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);
canvas.addEventListener('touchcancel', stopDrawing);
document.getElementById('clear').addEventListener('click',
function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
signatureData = null;
});
document.getElementById('stroke-style').addEventListener('change',
function(e) {
strokeStyle = e.target.value;
setStrokeStyle();
});
// 堆代码 duidaima.com
// 初始画布设置
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
});
保存和导出<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
JavaScript签名板
</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="signature-container">
<canvas id="signature-pad" width="400" height="200">
</canvas>
<div class="controls">
<button id="clear">
清除
</button>
<button id="export-png">
导出png格式
</button>
<button id="export-jpeg">
导出jpeg格式
</button>
</div>
</div>
<script src="script.js">
</script>
</body>
</html>
这是在添加之后 JavaScript 文件的样子:document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var ctx = canvas.getContext('2d');
var drawing = false;
var strokeStyle = 'pen';
var signatureData = null;
function resizeCanvas() {
if (signatureData) {
var img = new Image();
img.src = signatureData;
img.onload = function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
setStrokeStyle();
};
} else {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
setStrokeStyle();
}
}
function setStrokeStyle() {
if (strokeStyle === 'pen') {
ctx.lineWidth = 2;
ctx.lineCap = 'round';
} else if (strokeStyle === 'brush') {
ctx.lineWidth = 5;
ctx.lineCap = 'round';
}
}
function startDrawing(e) {
drawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
}
function draw(e) {
if (drawing) {
ctx.lineTo(e.offsetX || e.touches[0].clientX - canvas.offsetLeft, e.offsetY || e.touches[0].clientY - canvas.offsetTop);
ctx.stroke();
}
}
function stopDrawing() {
drawing = false;
signatureData = canvas.toDataURL();
}
function exportCanvas(format) {
var exportCanvas = document.createElement('canvas');
exportCanvas.width = canvas.width;
exportCanvas.height = canvas.height;
var exportCtx = exportCanvas.getContext('2d');
// 用白色填充背景
exportCtx.fillStyle = '#fff';
exportCtx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
// 绘制签名
exportCtx.drawImage(canvas, 0, 0);
// 导出画布
var dataURL = exportCanvas.toDataURL(`image / $ {
format
}`);
var link = document.createElement('a');
link.href = dataURL;
link.download = `signature.$ {
format
}`;
link.click();
}
// 鼠标事件
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);
canvas.addEventListener('touchcancel', stopDrawing);
document.getElementById('clear').addEventListener('click',
function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
signatureData = null;
});
document.getElementById('stroke-style').addEventListener('change',
function(e) {
strokeStyle = e.target.value;
setStrokeStyle();
});
document.getElementById('export-png').addEventListener('click',
function() {
exportCanvas('png');
});
document.getElementById('export-jpeg').addEventListener('click',
function() {
exportCanvas('jpeg');
});
// 初始画布设置
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
});
添加更多功能<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.7/dist/signature_pad.umd.min.js"></script>然后添加必要的按钮。这是最终 HTML 的样子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
JavaScript签名板
</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.1.7/dist/signature_pad.umd.min.js">
</script>
</head>
<body>
<div class="signature-container">
<canvas id="signature-pad" width="600" height="400">
</canvas>
<div class="controls">
<button id="undo">
撤销
</button>
<button id="redo">
重做
</button>
<button id="clear">
清除
</button>
<button id="save-png">
导出png格式
</button>
<button id="save-jpeg">
导出jpeg格式
</button>
</div>
</div>
<script src="script.js">
</script>
</body>
</html>
更新样式:body {
display: flex;
justify - content: center;
align - items: center;
height: 100vh;
background - color: #f0f0f0;
margin: 0;
}
.signature - container {
display: flex;
flex - direction: column;
align - items: center;
width: 90 % ;
max - width: 600px;
}
canvas {
border: 1px solid#000;
background - color: #fff;
width: 100 % ;
height: auto;
}
.controls {
margin - top: 10px;
display: flex;
gap: 10px;
flex - wrap: wrap;
}
button {
padding: 5px 10px;
cursor: pointer;
}
为了进一步完善我们的签名板功能,script.js 脚本需要进行以下更新:document.addEventListener('DOMContentLoaded',
function() {
var canvas = document.getElementById('signature-pad');
var signaturePad = new SignaturePad(canvas);
var undoStack = [];
var redoStack = [];
function saveState() {
undoStack.push(deepCopy(signaturePad.toData()));
redoStack = [];
}
function undo() {
if (undoStack.length > 0) {
redoStack.push(deepCopy(signaturePad.toData()));
undoStack.pop();
signaturePad.clear();
if (undoStack.length) {
var lastStroke = undoStack[undoStack.length - 1];
signaturePad.fromData(lastStroke, {
clear: false
});
}
}
}
function redo() {
if (redoStack.length > 0) {
undoStack.push(deepCopy(signaturePad.toData()));
var nextState = redoStack.pop();
signaturePad.clear();
if (nextState.length) {
signaturePad.fromData(nextState);
}
}
}
document.getElementById('undo').addEventListener('click', undo);
document.getElementById('redo').addEventListener('click', redo);
document.getElementById('clear').addEventListener('click',
function() {
signaturePad.clear();
undoStack = [];
redoStack = [];
});
document.getElementById('save-png').addEventListener('click',
function() {
if (!signaturePad.isEmpty()) {
var dataURL = signaturePad.toDataURL('image/png');
var link = document.createElement('a');
link.href = dataURL;
link.download = 'signature.png';
link.click();
}
});
document.getElementById('save-jpeg').addEventListener('click',
function() {
if (!signaturePad.isEmpty()) {
var dataURL = signaturePad.toDataURL('image/jpeg');
var link = document.createElement('a');
link.href = dataURL;
link.download = 'signature.jpeg';
link.click();
}
});
// 绘图结束时保存状态
signaturePad.addEventListener("endStroke", () = >{
console.log("Signature end");
saveState();
});
// 初始画布设置
function resizeCanvas() {
var ratio = Math.max(window.devicePixelRatio || 1, 1);
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
canvas.getContext('2d').scale(ratio, ratio);
signaturePad.clear(); // 否则 isEmpty() 可能会返回错误值
if (undoStack.length > 0) {
signaturePad.fromData(undoStack[undoStack.length - 1]);
}
}
function deepCopy(data) {
return JSON.parse(JSON.stringify(data));
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
});
