如何在 signature-pad.js 中使签名板画布响应式工作?
我的挑战如下:
我要么在正确绘制时获得光标定位,但在调整大小时丢失输入
// 或 //
我在调整大小时保存输入,但画布计算不正确,并且不再跟踪光标位置
现在我确信问题来自我的代码中的这一行:
signaturePad.fromData(signaturePad.toData());
评论与否决定了我观察到的两个行为的结果。
有人有一个简单的解决方案吗?我已经为之疯狂了。我还创建了一个 JS Fiddle 供您查看到目前为止我所拥有的内容: https://jsfiddle.net/xetrzi9/hort1ubj/3/
附加信息: 当在手机上滚动时,它还会由于某种原因重置输入,这真的很奇怪。
function initializeSignaturePad() {
const wrapper = document.querySelector('.form_signature-wrapper');
const canvas = wrapper.querySelector('#signature-pad');
const clearButton = wrapper.querySelector('[data-action=clear]');
let signaturePad;
function resizeCanvas() {
const ratio = Math.max(window.devicePixelRatio || 1, 1);
const rect = wrapper.getBoundingClientRect();
canvas.width = rect.width * ratio;
canvas.height = rect.height * ratio;
canvas.getContext('2d').scale(ratio, ratio);
// signaturePad.fromData(signaturePad.toData());
}
function initPad() {
signaturePad = new SignaturePad(canvas, {
penColor: 'rgb(255, 255, 255)',
minWidth: 0.5,
maxWidth: 2.5,
throttle: 0,
minDistance: 0,
velocityFilterWeight: 0.4,
});
updateMousePosition();
}
function updateMousePosition() {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
function getPointFromEvent(event) {
let x, y;
if (event.touches && event.touches.length > 0) {
const touch = event.touches[0];
x = touch.clientX;
y = touch.clientY;
} else {
x = event.clientX;
y = event.clientY;
}
x = (x - rect.left) * scaleX;
y = (y - rect.top) * scaleY;
return new SignaturePad.Point(x, y);
}
const originalOnMouseDown = signaturePad._handleMouseDown;
signaturePad._handleMouseDown = function(event) {
const point = getPointFromEvent(event);
originalOnMouseDown.call(signaturePad, event, point);
};
const originalOnMouseMove = signaturePad._handleMouseMove;
signaturePad._handleMouseMove = function(event) {
const point = getPointFromEvent(event);
originalOnMouseMove.call(signaturePad, event, point);
};
const originalOnTouchStart = signaturePad._handleTouchStart;
signaturePad._handleTouchStart = function(event) {
const point = getPointFromEvent(event);
originalOnTouchStart.call(signaturePad, event, point);
};
const originalOnTouchMove = signaturePad._handleTouchMove;
signaturePad._handleTouchMove = function(event) {
const point = getPointFromEvent(event);
originalOnTouchMove.call(signaturePad, event, point);
};
}
initPad();
resizeCanvas();
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
window.addEventListener(
'resize',
debounce(() => {
resizeCanvas();
}, 250)
);
clearButton.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
signaturePad.clear();
});
return signaturePad;
}
document.addEventListener('DOMContentLoaded', function() {
initializeSignaturePad();
});
html,
body {
margin: 0;
padding: 0;
}
body {
background: #0c0c0c;
font-family: sans-serif;
}
.form_field-wrapper {
max-width: 600px;
margin: 0 auto;
padding: 2rem 0;
}
.form_label {
color: #fff;
margin-bottom: 0.5rem;
}
.form_signature-wrapper {
width: 100%;
height: auto;
aspect-ratio: 5/3;
}
.form_signature-canvas {
height: 100%;
width: 100%;
display: block;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.25rem 0.25rem 0 0;
}
.form_signature-wrapper {
position: relative;
}
.form_signature-wrapper button {
appearance: none;
outline: none;
border: none;
box-shadow: none;
font-weight: 600;
color: #fff;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem;
border-radius: 0.25rem;
position: absolute;
top: 0.75rem;
right: 0.75rem;
transition: 100ms ease-in-out background;
}
.form_signature-wrapper button:hover {
background: rgba(255, 255, 255, 0.2);
cursor: pointer;
}
.form_info-wrapper {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 0 0 0.25rem 0.25rem;
font-size: 0.875rem;
}
.form_info-icon {
height: 1rem;
width: 1rem;
}
.form_info-icon svg {
display: flex;
height: auto;
width: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/signature_pad.umd.min.js"></script>
<div class="form_field-wrapper">
<div class="form_label">Signature</div>
<div class="form_signature-wrapper">
<canvas id="signature-pad" class="form_signature-canvas" width="" height="" style="touch-action: none"></canvas
><button
data-action="clear"
type="button"
class="form_signature-btn is-clear">
Delete
</button>
</div>
<div class="form_info-wrapper is-signature form_label">
<div class="form_info-icon">
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14">
<path
d="M7 14a7 7 0 1 1 7-7 7.007 7.007 0 0 1-7 7ZM7 1.167A5.833 5.833 0 1 0 12.833 7 5.84 5.84 0 0 0 7 1.167Z"
fill="currentColor"></path>
<path
d="M7.583 8.75H6.416v-.434a2.306 2.306 0 0 1 1.146-2.041A1.167 1.167 0 1 0 5.833 5.25H4.666a2.333 2.333 0 1 1 3.46 2.044 1.156 1.156 0 0 0-.543 1.022v.434ZM7.583 9.916H6.416v1.167h1.167V9.916Z"
fill="currentColor"></path>
</svg>
</div>
<div class="form_info">Please add your signature</div>
</div>
</div>
基本上,我们仅在初始化时设置画布尺寸(根据 CSS 规则),并缩放/投影鼠标/指针坐标以满足当前画布缩放比例。因此画布保持其尺寸并仅通过其 CSS 布局属性进行缩放。
const wrapper = document.querySelector('.form_signature-wrapper');
const canvas = wrapper.querySelector('#signature-pad');
const clearButton = wrapper.querySelector('[data-action=clear]');
function initializeSignaturePad() {
//set initial width and height
let ratio = Math.max(window.devicePixelRatio || 1, 1);
let bb = wrapper.getBoundingClientRect();
let w = bb.width * ratio;
let h = bb.height * ratio;
canvas.width = w;
canvas.height = h;
const signaturePad = new SignaturePad(canvas, {
penColor: 'rgb(255, 255, 255)',
minWidth: 0.5,
maxWidth: 2.5,
throttle: 0,
minDistance: 0,
velocityFilterWeight: 0.4,
});
// add scaling to point creation
signaturePad._createPoint = (x, y, pressure) => {
let bb = canvas.getBoundingClientRect();
let scale = w / bb.width;
return new Point((x - bb.left) * scale, (y - bb.top) * scale, pressure, new Date().getTime());
}
class Point {
constructor(x, y, pressure, time) {
if (isNaN(x) || isNaN(y)) {
throw new Error(`Point is invalid: (${x}, ${y})`);
}
this.x = +x;
this.y = +y;
this.pressure = pressure || 0;
this.time = time || Date.now();
}
distanceTo(start) {
return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
}
equals(other) {
return (this.x === other.x &&
this.y === other.y &&
this.pressure === other.pressure &&
this.time === other.time);
}
velocityFrom(start) {
return this.time !== start.time ?
this.distanceTo(start) / (this.time - start.time) :
0;
}
}
clearButton.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
signaturePad.clear();
});
return signaturePad;
}
document.addEventListener('DOMContentLoaded', function() {
initializeSignaturePad();
});
html,
body {
margin: 0;
padding: 0;
}
body {
background: #0c0c0c;
font-family: sans-serif;
}
.form_field-wrapper {
max-width: 100%;
margin: 0 auto;
padding: 2rem 0;
}
.form_label {
color: #fff;
margin-bottom: 0.5rem;
}
.form_signature-wrapper {
width: 100%;
height: auto;
aspect-ratio: 5/3;
resize: horizontal;
overflow: auto;
outline: 1px solid red;
}
.form_signature-canvas {
height: 100%;
width: 100%;
display: block;
box-sizing: border-box;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.25rem 0.25rem 0 0;
}
.form_signature-wrapper {
position: relative;
}
.form_signature-wrapper button {
appearance: none;
outline: none;
border: none;
box-shadow: none;
font-weight: 600;
color: #fff;
background: rgba(255, 255, 255, 0.1);
padding: 0.5rem;
border-radius: 0.25rem;
position: absolute;
top: 0.75rem;
right: 0.75rem;
transition: 100ms ease-in-out background;
}
.form_signature-wrapper button:hover {
background: rgba(255, 255, 255, 0.2);
cursor: pointer;
}
.form_info-wrapper {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 0 0 0.25rem 0.25rem;
font-size: 0.875rem;
}
.form_info-icon {
height: 1rem;
width: 1rem;
}
.form_info-icon svg {
display: flex;
height: auto;
width: 100%;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/signature_pad.umd.min.js"></script>
<div class="form_field-wrapper">
<div class="form_label">Signature</div>
<div class="form_signature-wrapper">
<canvas id="signature-pad" class="form_signature-canvas" width="" height="" style="touch-action: none"></canvas
><button
data-action="clear"
type="button"
class="form_signature-btn is-clear">
Delete
</button>
</div>
<div class="form_info-wrapper is-signature form_label">
<div class="form_info">Please add your signature</div>
</div>
</div>