Add signature pad and animated visor/cord canvas
Added signature-pad.js: a minimal, dependency-free signature pad with attach, clear, toDataUrl, and detach methods, supporting mouse and touch events and high-DPI displays. Updated error-space.js to draw a visor shape and animate a cord using bezier curves for a dynamic visual effect.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
function drawVisor() {
|
||||
const canvas = document.getElementById('visor');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(5, 45);
|
||||
ctx.bezierCurveTo(15, 64, 45, 64, 55, 45);
|
||||
|
||||
ctx.lineTo(55, 20);
|
||||
ctx.bezierCurveTo(55, 15, 50, 10, 45, 10);
|
||||
|
||||
ctx.lineTo(15, 10);
|
||||
|
||||
ctx.bezierCurveTo(15, 10, 5, 10, 5, 20);
|
||||
ctx.lineTo(5, 45);
|
||||
|
||||
ctx.fillStyle = '#2f3640';
|
||||
ctx.strokeStyle = '#f5f6fa';
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
const cordCanvas = document.getElementById('cord');
|
||||
const ctx = cordCanvas.getContext('2d');
|
||||
|
||||
let y1 = 160;
|
||||
let y2 = 100;
|
||||
let y3 = 100;
|
||||
|
||||
let y1Forward = true;
|
||||
let y2Forward = false;
|
||||
let y3Forward = true;
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
ctx.clearRect(0, 0, innerWidth, innerHeight);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(130, 170);
|
||||
ctx.bezierCurveTo(250, y1, 345, y2, 400, y3);
|
||||
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.lineWidth = 8;
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
if (y1 === 100) {
|
||||
y1Forward = true;
|
||||
}
|
||||
|
||||
if (y1 === 300) {
|
||||
y1Forward = false;
|
||||
}
|
||||
|
||||
if (y2 === 100) {
|
||||
y2Forward = true;
|
||||
}
|
||||
|
||||
if (y2 === 310) {
|
||||
y2Forward = false;
|
||||
}
|
||||
|
||||
if (y3 === 100) {
|
||||
y3Forward = true;
|
||||
}
|
||||
|
||||
if (y3 === 317) {
|
||||
y3Forward = false;
|
||||
}
|
||||
|
||||
y1Forward ? y1 += 1 : y1 -= 1;
|
||||
y2Forward ? y2 += 1 : y2 -= 1;
|
||||
y3Forward ? y3 += 1 : y3 -= 1;
|
||||
}
|
||||
|
||||
drawVisor();
|
||||
animate();
|
||||
@@ -0,0 +1,114 @@
|
||||
// Minimal signature-pad implementation used by SignaturePadDialog.razor.
|
||||
// Exposes window.signaturePad with attach() / clear() / toDataUrl() / detach().
|
||||
//
|
||||
// Why a hand-rolled implementation instead of a library?
|
||||
// • Zero external dependencies (no npm / no bundler).
|
||||
// • Works identically under InteractiveServer and InteractiveWebAssembly
|
||||
// because the Blazor side only calls four trivial JS functions.
|
||||
// • Supports both mouse and pointer/touch events.
|
||||
window.signaturePad = (function () {
|
||||
const instances = new Map();
|
||||
|
||||
function resize(canvas) {
|
||||
// Backing store size must follow the DPR so the line stays crisp.
|
||||
const ratio = window.devicePixelRatio || 1;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = Math.max(1, rect.width * ratio);
|
||||
canvas.height = Math.max(1, rect.height * ratio);
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.scale(ratio, ratio);
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.strokeStyle = '#0d2540';
|
||||
}
|
||||
|
||||
function attach(canvasId) {
|
||||
const canvas = document.getElementById(canvasId);
|
||||
if (!canvas) return false;
|
||||
if (instances.has(canvasId)) detach(canvasId);
|
||||
|
||||
resize(canvas);
|
||||
const ctx = canvas.getContext('2d');
|
||||
let drawing = false;
|
||||
let hasInk = false;
|
||||
let last = { x: 0, y: 0 };
|
||||
|
||||
function pos(ev) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const clientX = ev.touches ? ev.touches[0].clientX : ev.clientX;
|
||||
const clientY = ev.touches ? ev.touches[0].clientY : ev.clientY;
|
||||
return { x: clientX - rect.left, y: clientY - rect.top };
|
||||
}
|
||||
function start(ev) {
|
||||
ev.preventDefault();
|
||||
drawing = true;
|
||||
last = pos(ev);
|
||||
}
|
||||
function move(ev) {
|
||||
if (!drawing) return;
|
||||
ev.preventDefault();
|
||||
const p = pos(ev);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(last.x, last.y);
|
||||
ctx.lineTo(p.x, p.y);
|
||||
ctx.stroke();
|
||||
last = p;
|
||||
hasInk = true;
|
||||
}
|
||||
function end() { drawing = false; }
|
||||
|
||||
const onResize = () => {
|
||||
const data = canvas.toDataURL();
|
||||
resize(canvas);
|
||||
const img = new Image();
|
||||
img.onload = () => ctx.drawImage(img, 0, 0, canvas.getBoundingClientRect().width, canvas.getBoundingClientRect().height);
|
||||
img.src = data;
|
||||
};
|
||||
|
||||
canvas.addEventListener('mousedown', start);
|
||||
canvas.addEventListener('mousemove', move);
|
||||
canvas.addEventListener('mouseup', end);
|
||||
canvas.addEventListener('mouseleave', end);
|
||||
canvas.addEventListener('touchstart', start, { passive: false });
|
||||
canvas.addEventListener('touchmove', move, { passive: false });
|
||||
canvas.addEventListener('touchend', end);
|
||||
window.addEventListener('resize', onResize);
|
||||
|
||||
instances.set(canvasId, {
|
||||
canvas, ctx,
|
||||
hasInk: () => hasInk,
|
||||
clear: () => { ctx.clearRect(0, 0, canvas.width, canvas.height); hasInk = false; },
|
||||
toDataUrl: () => hasInk ? canvas.toDataURL('image/png') : null,
|
||||
cleanup: () => {
|
||||
canvas.removeEventListener('mousedown', start);
|
||||
canvas.removeEventListener('mousemove', move);
|
||||
canvas.removeEventListener('mouseup', end);
|
||||
canvas.removeEventListener('mouseleave', end);
|
||||
canvas.removeEventListener('touchstart', start);
|
||||
canvas.removeEventListener('touchmove', move);
|
||||
canvas.removeEventListener('touchend', end);
|
||||
window.removeEventListener('resize', onResize);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function clear(canvasId) {
|
||||
instances.get(canvasId)?.clear();
|
||||
}
|
||||
|
||||
function toDataUrl(canvasId) {
|
||||
return instances.get(canvasId)?.toDataUrl() ?? null;
|
||||
}
|
||||
|
||||
function detach(canvasId) {
|
||||
const inst = instances.get(canvasId);
|
||||
if (inst) {
|
||||
inst.cleanup();
|
||||
instances.delete(canvasId);
|
||||
}
|
||||
}
|
||||
|
||||
return { attach, clear, toDataUrl, detach };
|
||||
})();
|
||||
Reference in New Issue
Block a user