I am trying to make a signature pad but the JS (scribbling line) works in a developing sandbox but not when I combine the files. I have placed my CSS in between the head tags and script just before closing the body tags to allow the JS to run after the other components have run. Not sure what is causing it not to run. Beginner here, so I apologise if my question is too entry-level. Any assistance would be greatly appreciated.
<html>
<head>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
height: 100vh;
width: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 0;
padding: 32px 16px;
font-family: Helvetica, Sans-Serif;
}
.signature-pad {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
font-size: 10px;
width: 100%;
height: 100%;
max-width: 700px;
max-height: 460px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
border-radius: 4px;
padding: 16px;
cursor: crosshair;
cursor: url('https://staging-dovetail.kinsta.cloud/wp-content/uploads/2021/05/Pen-Cursor-1.png') 53 53, crosshair;
}
}
.signature-pad::before,
.signature-pad::after {
position: absolute;
z-index: -1;
content: "";
width: 40%;
height: 10px;
bottom: 10px;
background: transparent;
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4);
}
.signature-pad::before {
left: 20px;
-webkit-transform: skew(-3deg) rotate(-3deg);
transform: skew(-3deg) rotate(-3deg);
}
.signature-pad::after {
right: 20px;
-webkit-transform: skew(3deg) rotate(3deg);
transform: skew(3deg) rotate(3deg);
}
.signature-pad--body {
position: relative;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
border: 1px solid #f4f4f4;
}
canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
}
.signature-pad--footer {
color: #C3C3C3;
text-align: center;
font-size: 1.2em;
margin-top: 8px;
}
.signature-pad--actions {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
margin-top: 8px;
}
#github img {
border: 0;
}
#fileupload {
display: none;
}
form {
display: table-row;
margin-right: 5px;
}
span[role=button] {
display: table-cell;
font-size: 1.2em;
}
span[role=button],
button {
cursor: pointer;
background-color: #e1e1e1;
color: #000000;
border: none;
padding: 8px;
margin-bottom: 8px;
}
@media (max-width: 940px) {
#github img {
width: 90px;
height: 90px;
}
}
</style>
</head>
<body>
<div id="signature-pad" class="signature-pad">
<div class="signature-pad--body">
<canvas>
Update your browser to support the canvas element!
</canvas>
</div>
<div class="signature-pad--footer">
<div class="description">Sign above</div>
<div class="signature-pad--actions">
<form action="#" enctype="multipart/form-data">
<label for="fileupload" id="buttonlabel">
</label>
<input type="file" id="fileupload" accept="image/*">
</form>
<div>
<button type="button" class="button clear" data-action="clear">Clear</button>
</div>
</div>
</div>
</div>
<script>
const wrapper = document.getElementById("signature-pad")
const clearButton = wrapper.querySelector("[data-action=clear]")
const changeColorButton = wrapper.querySelector("[data-action=change-color]")
const undoButton = wrapper.querySelector("[data-action=undo]")
const savePNGButton = wrapper.querySelector("[data-action=save-png]")
const saveJPGButton = wrapper.querySelector("[data-action=save-jpg]")
const saveSVGButton = wrapper.querySelector("[data-action=save-svg]")
const canvas = wrapper.querySelector("canvas")
const fileSelector = document.getElementById('fileupload')
// https://medium.com/the-everyday-developer/detect-file-mime-type-using-magic-numbers-and-javascript-16bc513d4e1e
const verifyAndSetPictureAsBackground = (event) => {
const file = event.target.files[0]
const fReader = new FileReader()
fReader.onloadend = (e) => {
if (e.target.readyState === FileReader.DONE) {
const uint = new Uint8Array(e.target.result)
let bytes = []
uint.forEach((byte) => bytes.push(byte.toString(16)))
const hex = bytes.join('').toUpperCase()
if (!(getMimeType(hex) === 'image/png' || getMimeType(hex) === 'image/gif' || getMimeType(hex) === 'image/jpeg')) {
alert('Please choose a picture(.png, .gif, or .jpeg)')
// https://stackoverflow.com/a/35323290/1904223
file = null
fileSelector.value = ''
if (!/safari/i.test(navigator.userAgent)) {
fileSelector.type = ''
fileSelector.type = 'file'
}
}
if (file) {
const dataURL = window.URL.createObjectURL(file)
signaturePad.fromDataURL(dataURL)
}
}
}
fReader.readAsArrayBuffer(file.slice(0, 4))
}
const getMimeType = (signature) => {
switch (signature) {
case '89504E47':
return 'image/png'
case '47494638':
return 'image/gif'
case 'FFD8FFDB':
case 'FFD8FFE0':
case 'FFD8FFE1':
return 'image/jpeg'
default:
return 'Not allowed filetype'
}
}
fileSelector.addEventListener('change', verifyAndSetPictureAsBackground, false)
const signaturePad = new SignaturePad(canvas, {
// It's Necessary to use an opaque color when saving image as JPEG
// this option can be omitted if only saving as PNG or SVG
backgroundColor: 'rgb(255, 255, 255)'
})
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
const resizeCanvas = () => {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
const ratio = Math.max(window.devicePixelRatio || 1, 1)
// This part causes the canvas to be cleared
canvas.width = canvas.offsetWidth * ratio
canvas.height = canvas.offsetHeight * ratio
canvas.getContext("2d").scale(ratio, ratio)
// This library does not listen for canvas changes, so after the canvas is automatically
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
// that the state of this library is consistent with visual state of the canvas, you
// have to clear it manually.
signaturePad.clear()
}
// On mobile devices it might make more sense to listen to orientation change,
// rather than window resize events.
window.onresize = resizeCanvas
resizeCanvas()
const download = (dataURL, filename) => {
const blob = dataURLToBlob(dataURL)
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.style = "display: none"
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
}
// One could simply use Canvas#toBlob method instead, but it's just to show
// that it can be done using result of SignaturePad#toDataURL.
function dataURLToBlob(dataURL) {
// Code taken from https://github.com/ebidel/filer.js
const parts = dataURL.split('base64,')
const contentType = parts[0].split(":")[1]
const raw = window.atob(parts[1])
const rawLength = raw.length
const uInt8Array = new Uint8Array(rawLength)
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i)
}
return new Blob([uInt8Array], { type: contentType })
}
clearButton.addEventListener("click", () => signaturePad.clear())
undoButton.addEventListener("click", () => {
const data = signaturePad.toData()
if (data) {
data.pop() // remove the last dot or line
signaturePad.fromData(data)
}
})
changeColorButton.addEventListener("click", () => {
const r = Math.round(Math.random() * 255)
const g = Math.round(Math.random() * 255)
const b = Math.round(Math.random() * 255)
const color = "rgb(" + r + "," + g + "," + b +")"
signaturePad.penColor = color
})
savePNGButton.addEventListener("click", () => {
if (signaturePad.isEmpty()) {
alert("Please provide a signature first.")
} else {
const dataURL = signaturePad.toDataURL()
download(dataURL, "signature.png")
}
})
saveJPGButton.addEventListener("click", () => {
if (signaturePad.isEmpty()) {
alert("Please provide a signature first.")
} else {
const dataURL = signaturePad.toDataURL("image/jpeg")
download(dataURL, "signature.jpg")
}
})
saveSVGButton.addEventListener("click", () => {
if (signaturePad.isEmpty()) {
alert("Please provide a signature first.")
} else {
const dataURL = signaturePad.toDataURL('image/svg+xml')
download(dataURL, "signature.svg")
}
})
</script>
</body>
</html>