Last active
June 18, 2024 03:10
-
-
Save michitheonlyone/c1a92d4169e674da5b5edfedac2325cc to your computer and use it in GitHub Desktop.
Signature Pad for Symfony Forms (it can be used in any website to create digital signatures with a pen on touchscreen
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{# | |
Signature Pad for Symfony Forms | |
© 2024 by Michael Ihde wemida.com | |
https://github.com/wemida | |
Variables to use with include: | |
{% include with ... %} => see below example | |
{{ sig_title ?? 'E-Signature' }} | |
{{ sig_info_text ?? 'Sign in the canvas below and save your signature as an image!' }} | |
{{ sig_canvas_id ?? 'sig-canvas' }} | |
{{ sig_save_btn_id ?? 'sig-submitBtn' }} | |
{{ sig_save_btn_name ?? 'Save signature' }} | |
{{ sig_clear_btn_id ?? 'sig-clearBtn' }} | |
{{ sig_clear_btn_name ?? 'Clear signature' }} | |
{{ sig_data_output_textarea_id ?? 'sig-dataUrl' }} | |
{{ sig_data_output_textarea_name ?? 'sig-dataUrl' }} | |
{{ sig_image_output ?? 'sig-image' }} | |
{% include 'create_customer_report/_signaturepad.html.twig' with { | |
'sig_title': 'Signatur', | |
'sig_info_text': '', | |
'sig_canvas_id': 'sig-canvas-reporter', | |
'sig_save_btn_id': 'sig-btn-save-reporter', | |
'sig_save_btn_name': 'Signatur speichern', | |
'sig_clear_btn_id': 'sig-btn-clear-reporter', | |
'sig_clear_btn_name': 'Signatur löschen', | |
'sig_data_output_textarea_id': 'sig-data-reporter', | |
'sig_data_output_textarea_name': 'sig-data-reporter', | |
'sig_image_output': 'sig-image-reporter' | |
'form_widget_function': form.reporterSignature | |
} %} | |
#} | |
<style> | |
#{{ sig_canvas_id ?? 'sig-canvas' }} { | |
border: 2px dotted #CCCCCC; | |
border-radius: 15px; | |
cursor: crosshair; | |
} | |
</style> | |
<!-- Content --> | |
<div class="container"> | |
<div class="row"> | |
<div class="col-md-12"> | |
<h1>{{ sig_title ?? 'E-Signature' }}</h1> | |
<p>{{ sig_info_text ?? 'Sign in the canvas below and save your signature as an image!' }}</p> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-md-12"> | |
{# <canvas id="sig-canvas" width="620" height="160">#} | |
<canvas id="{{ sig_canvas_id ?? 'sig-canvas' }}" width="620" height="160"> | |
Get a better browser, bro! | |
</canvas> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-md-12"> | |
{# These buttons must be any tag except button to prevent formsubmitting! #} | |
<div class="btn btn-info" id="{{ sig_save_btn_id ?? 'sig-submitBtn' }}">{{ sig_save_btn_name ?? 'Save signature' }}</div> | |
<div class="btn btn-warning" id="{{ sig_clear_btn_id ?? 'sig-clearBtn' }}">{{ sig_clear_btn_name ?? 'Clear signature' }}</div> | |
</div> | |
</div> | |
<br/> | |
<div class="row"> | |
{# This textfield is only for demo and can be hidden to submit the signature via form #} | |
<div class="col-md-12"> | |
{# TODO convert into custom formfield to prevent this hassle!! #} | |
{{ form_widget(form_widget_function) }} | |
{# {{ form_widget(form.customerSignature) }}#} | |
{# <input type="hidden" id="{{ sig_data_output_textarea_id ?? 'sig-dataUrl' }}" name="{{ sig_data_output_textarea_name ?? 'sig-dataUrl' }}" />#} | |
</div> | |
</div> | |
<br/> | |
<div class="row"> | |
<div class="col-md-12"> | |
<img id="{{ sig_image_output ?? 'sig-image' }}" src="{{ form_widget_function.vars.value ?? '' }}" alt="Your signature will go here!"/> | |
</div> | |
</div> | |
</div> | |
<script> | |
(function() { | |
window.requestAnimFrame = (function(callback) { | |
return window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimaitonFrame || | |
function(callback) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
})(); | |
var canvas = document.getElementById("{{ sig_canvas_id ?? 'sig-canvas' }}"); | |
var ctx = canvas.getContext("2d"); | |
ctx.strokeStyle = "#222222"; | |
ctx.lineWidth = 4; | |
var drawing = false; | |
var mousePos = { | |
x: 0, | |
y: 0 | |
}; | |
var lastPos = mousePos; | |
canvas.addEventListener("mousedown", function(e) { | |
drawing = true; | |
lastPos = getMousePos(canvas, e); | |
}, false); | |
canvas.addEventListener("mouseup", function(e) { | |
drawing = false; | |
}, false); | |
canvas.addEventListener("mousemove", function(e) { | |
mousePos = getMousePos(canvas, e); | |
}, false); | |
// Add touch event support for mobile | |
canvas.addEventListener("touchstart", function(e) { | |
}, false); | |
canvas.addEventListener("touchmove", function(e) { | |
var touch = e.touches[0]; | |
var me = new MouseEvent("mousemove", { | |
clientX: touch.clientX, | |
clientY: touch.clientY | |
}); | |
canvas.dispatchEvent(me); | |
}, false); | |
canvas.addEventListener("touchstart", function(e) { | |
mousePos = getTouchPos(canvas, e); | |
var touch = e.touches[0]; | |
var me = new MouseEvent("mousedown", { | |
clientX: touch.clientX, | |
clientY: touch.clientY | |
}); | |
canvas.dispatchEvent(me); | |
}, false); | |
canvas.addEventListener("touchend", function(e) { | |
var me = new MouseEvent("mouseup", {}); | |
canvas.dispatchEvent(me); | |
}, false); | |
function getMousePos(canvasDom, mouseEvent) { | |
var rect = canvasDom.getBoundingClientRect(); | |
return { | |
x: mouseEvent.clientX - rect.left, | |
y: mouseEvent.clientY - rect.top | |
} | |
} | |
function getTouchPos(canvasDom, touchEvent) { | |
var rect = canvasDom.getBoundingClientRect(); | |
return { | |
x: touchEvent.touches[0].clientX - rect.left, | |
y: touchEvent.touches[0].clientY - rect.top | |
} | |
} | |
function renderCanvas() { | |
if (drawing) { | |
ctx.moveTo(lastPos.x, lastPos.y); | |
ctx.lineTo(mousePos.x, mousePos.y); | |
ctx.stroke(); | |
lastPos = mousePos; | |
} | |
} | |
// Prevent scrolling when touching the canvas | |
document.body.addEventListener("touchstart", function(e) { | |
if (e.target == canvas) { | |
e.preventDefault(); | |
} | |
}, false); | |
document.body.addEventListener("touchend", function(e) { | |
if (e.target == canvas) { | |
e.preventDefault(); | |
} | |
}, false); | |
document.body.addEventListener("touchmove", function(e) { | |
if (e.target == canvas) { | |
e.preventDefault(); | |
} | |
}, false); | |
(function drawLoop() { | |
requestAnimFrame(drawLoop); | |
renderCanvas(); | |
})(); | |
function clearCanvas() { | |
// must be dumb to reset the canvas | |
canvas.width = canvas.width; | |
var ctx = canvas.getContext("2d"); | |
ctx.strokeStyle = "#222222"; | |
ctx.lineWidth = 4; | |
} | |
// Set up the UI | |
var sigText = document.getElementById("{{ sig_data_output_textarea_id ?? 'sig-dataUrl' }}"); | |
var sigImage = document.getElementById("{{ sig_image_output ?? 'sig-image' }}"); | |
var clearBtn = document.getElementById("{{ sig_clear_btn_id ?? 'sig-clearBtn' }}"); | |
var submitBtn = document.getElementById("{{ sig_save_btn_id ?? 'sig-submitBtn' }}"); | |
clearBtn.addEventListener("click", function(e) { | |
clearCanvas(); | |
sigText.value = "Data URL for your signature will go here!"; | |
sigImage.setAttribute("src", ""); | |
}, false); | |
submitBtn.addEventListener("click", function(e) { | |
var dataUrl = canvas.toDataURL(); | |
sigText.value = dataUrl; | |
sigImage.setAttribute("src", dataUrl); | |
}, false); | |
})(); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment