0

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>
Esme June
  • 67
  • 5

1 Answers1

4

When you open this file in the Browser you need to look into the "Developer Tools" to find the error in the console (If you don't know how to do that: try right-clicking on your webpage and select "inspect" from the context menu)

The console will show you:

Uncaught ReferenceError: SignaturePad is not defined
    file:test.html:233

Looking in line 233 you find

const signaturePad = new SignaturePad(....

Where is the class SignaturePad defined? Did you maybe leave it in a separate file?

bjelli
  • 9,752
  • 4
  • 35
  • 50