javascriptcanvasresponsivesignaturesignaturepad

How to make responsive signature pad?


How to make a signature pad canvas in signature-pad.js work responsively?

My challenge is the following:

  1. I either get the cursor positioning while drawing correctly but lose the input on resize

    // OR //

  2. I save the input while resizing but then the canvas is not calculated correctly and it does not track the cursor position anymore

Right now I am sure the issue comes from this line in my code: signaturePad.fromData(signaturePad.toData());

Commenting this out or not decides the outcome of my two observed behaviors.

Does someone have an easy solution to this? I am getting crazy about it already. I also created a JS Fiddle for you to see what I have so far: https://jsfiddle.net/xetrzi9/hort1ubj/3/

Additional info: When scrolling on the phone it also resets the input for some reason which is really weird.

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/signature_pad@4.1.7/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>


Solution

  • I ended up building my own little library that also integrates with Webflow components super easily.

    If you want to check it out, you can find it here:

    https://github.com/vierless/attributes/tree/main/signature-pad