1

i'm using the signature-pad plugin and i'm having some issues whith the resize event: - Multiple resizes lead to a loss in quality and the signature "moves" at each resize of the browser window ending with no signature in canvas. - In some cases, the isEmpty() function wont work and i'll be able to save the empty signature.

Optional question : how can i detect an empty signature on php side ?

Thank you :)

Below my code :

$(window).resize(function() {
            resizeCanvas();
        });



        var wrapper1 = document.getElementById("signature-pad"),
            clearButton1 = wrapper1.querySelector("[data-action=clear]"),
            canvas1 = wrapper1.querySelector("canvas"),
            signaturePad1;

        var wrapper2 = document.getElementById("signature-pad-paraphe"),
            clearButton2 = wrapper2.querySelector("[data-action=clear]"),
            canvas2 = wrapper2.querySelector("canvas"),
            signaturePad2;

        // Adjust canvas coordinate space taking into account pixel ratio,
        // to make it look crisp on mobile devices.
        // This also causes canvas to be cleared.

        signaturePad1 = new SignaturePad(canvas1);
        signaturePad2 = new SignaturePad(canvas2);

        function resizeCanvas() {

            //Sauvegarde sig / par
            var sig = signaturePad1.toDataURL(); 
            var par = signaturePad2.toDataURL(); 


            var ratio =  Math.max(window.devicePixelRatio || 1, 1);
            canvas1.width = canvas1.offsetWidth * ratio;
            canvas1.height = canvas1.offsetHeight * ratio;
            canvas1.getContext("2d").scale(ratio, ratio);           
            canvas2.width = canvas2.offsetWidth * ratio;
            canvas2.height = canvas2.offsetHeight * ratio;
            canvas2.getContext("2d").scale(ratio, ratio);

            // redraw
            signaturePad1.fromDataURL(sig); 
            signaturePad2.fromDataURL(par); 

        }

        window.onresize = resizeCanvas;
        resizeCanvas();

        // Init -> retourne la bonne valeur de isEmpty -> !!? Not sure if needed
        signaturePad1.clear();
        signaturePad2.clear();

        var signature = $('#confirm_delete_signature').val();
        if(signature){
            signaturePad1.fromDataURL(signature);
        }

        var paraphe = $('#confirm_delete_paraphe').val();
        if(paraphe){
            signaturePad2.fromDataURL(paraphe);
        }

        clearButton1.addEventListener("click", function (event) {
            signaturePad1.clear();
        });

        clearButton2.addEventListener("click", function (event) {
            signaturePad2.clear();
        });
Home
  • 43
  • 2
  • 6

2 Answers2

0

Here is i developed a little solution; Here are two key DOM elements:

  • div#id_wrapper
  • canvas#id

Considered it may be applied at devices with different devicePixelRatio and on screens changins theirs width (f.i.: portrait-landscape orientation).

export class FlexSignatureComponent extends React.Component {
  state = {
     width: 0,
     lines: [],
     storedValue: undefined,
     validationClass: '', // toggles between 'is-invalid'/'is-valid'
     validationMessage: ''
  }

The lib initiation is right after the component got loaded:

  componentDidMount = () => {  
    this.signPad = new SignaturePad(document.getElementById(this.props.htmlid), {
      onEnd: this.onChangeSignaturePad,
      backgroundColor: '#fff'
    });

    if (this.valueHolder.current.value) {
      const data = JSON.parse(this.valueHolder.current.value);

      this.state.lines = data.value;
      this.state.width = 100;
    }

    //you need the next workarounds if you have other onWidnowResize handlers manarging screen width
    //setTimeout-0 workaround to move windowResizeHandling at the end of v8-enging procedures queue
    // otherwise omit setTimeout and envoke func as it is
    setTimeout(this.handleWindowResize, 0);
    window.addEventListener("resize", () => setTimeout(this.handleWindowResize, 0));
  }

First handle window resize change


  handleWindowResize = () => {
    if (this.state.storedValue) {

      const prevWrapperWidth = this.state.width;
      const currentWrapperWidth = $(`#${this.props.htmlid}_wrapper`).width();
      const scale = prevWrapperWidth / currentWrapperWidth;

      this.state.width = currentWrapperWidth;

      this.setRescaledSignature(this.state.lines, scale);
      this.resetCanvasSize();
      this.signPad.fromData(this.state.lines)

    } else
      this.resetCanvasSize()
  }

Second rescaleSignature to another width


  setRescaledSignature = (lines, scale) => {
    lines.forEach(line => {
      line.points.forEach(point => {
        point.x /= scale;
        point.y /= scale;
      });
    });
  }

Finally updated canvas size

  resetCanvasSize = () => {
    const canvas = document.getElementById(this.props.htmlid);

    canvas.style.width = '100%';
    canvas.style.height = canvas.offsetWidth / 1.75 + "px";

    canvas.width = canvas.offsetWidth * devicePixelRatio;
    canvas.height = canvas.offsetHeight * devicePixelRatio;

    canvas.getContext("2d").scale(devicePixelRatio, devicePixelRatio);
  }

Here we on every change add new drawn line to this.state.lines and prepare the lines to be submited as json. But before the submission they need to create deepCopy and to be rescaled to conventional size (its width is equal 100px and DPR is 1)


  onChangeSignaturePad = () => {
    const value = this.signPad.toData();
    this.state.lines = value;

    const currentWrapperWidth = $(`#${this.props.htmlid}_wrapper`).width();

    const scale = currentWrapperWidth / 100;
    const ratio = 1 / devicePixelRatio;

    const linesCopy = JSON.parse(JSON.stringify(value));
    this.setRescaledSignature(linesCopy, scale, ratio);

    const data = {
      signature_configs: {
        devicePixelRatio: 1,
        wrapper_width: 100
      },
      value: linesCopy
    };
    this.state.storedValue = JSON.stringify(data);

    this.validate()
  }

One more thing is the red button to swipe the previous signatures

  onClickClear = (e) => {
    e.stopPropagation();
    this.signPad.clear();
    this.valueHolder.current.value = null;
    this.validate()
  }

  render() {
    let {label, htmlid} = this.props;
    const {validationClass = ''} = this.state;
    return (
      <div className="form-group fs_form-signature">
        <label>{Label}</label>
        <div className="fs_wr-signature">
          <button className={'fs_btn-clear'} onClick={this.onClickClear}>
            <i className="fas fa-times"></i>
          </button>
          <div id={htmlid + '_wrapper'} className={`w-100 fs_form-control ${validationClass}`}>
            <canvas id={htmlid}/>
          </div>
        </div>
        <div className={' invalid-feedback fs_show-feedback ' + validationClass}>Signature is a mandatory field</div>
      </div>
    )
  }

  postWillUnmount() {
    this.signPad.off();
  }

the used lib signature pad by szimek Used React and Bootstrap and some custome styles

the result would be

enter image description here

enter image description here

Alexey Nikonov
  • 4,958
  • 5
  • 39
  • 66
0

You didn't provide a full example, or much explanation of the code, so it's hard to tell what all is going on here, but I'll do my best to give as full an answer as I can.

Saving

First, if I understand the docs correctly, $(window).resize will be triggered at the same time as window.onresize. You use both. That might be causing some issues, maybe even the issues with saving.

The following code is run once, and I'm not sure what it's supposed to do:

var signature = $('#confirm_delete_signature').val();
if(signature){
    signaturePad1.fromDataURL(signature);
}

var paraphe = $('#confirm_delete_paraphe').val();
if(paraphe){
    signaturePad2.fromDataURL(paraphe);
}

It looks like it's supposed to be deleting the signature (since the selector is #confirm_delete_signature), but it instead, it's restoring a signature from some data stored in the node as a string. That might be causing issues too.

That said, I'm not sure why saving isn't working, but I can't find the code of your saving function, so it's very hard to say. Maybe I missed something.

I'm not familiar with php, sorry.

Resizing

For resizing, I think the React version that @Alexey Nikonov made might work with React (I didn't run it). You have to scale the positions of the points of the lines along with the changing size of the canvas.

I wanted a version closer to vanilla js, so I recreated it with just signature_pad v4.1.4 and jQuery at https://jsfiddle.net/j2Lurpd5/1/ (with an improvement to ratio calculation).

The code is as follows, though it doesn't have a button to clear the canvas:

<div id="wrapper">
  <canvas id="pad" width="200" height="100"></canvas>
</div>
canvas {
  border: red 1px solid;
}
// Inspiration: https://stackoverflow.com/a/60057521
// Version with no React

const canvas = document.querySelector('#pad');
const signPad = new SignaturePad(canvas);
// Doesn't work without the #wrapper. Probably because #pad
// needs it to be able to be 100% of it. Not sure exactly
// why that makes a difference when #wrapper doesn't have
// a width set on it. Though #pad alone does work after the
// first resize.
let prevWidth = $('#wrapper').width();
let lines = [];


setTimeout(resizeSignatureAndCanvas, 0);
window.addEventListener("resize", () => setTimeout(resizeSignatureAndCanvas, 0));
window.addEventListener("orientationchange", () => setTimeout(resizeSignatureAndCanvas, 0));


function resizeSignatureAndCanvas () {
  // Get the current canvas contents
  lines = signPad.toData();
  
  // if there are no lines drawn, don't need to scale them
  if ( signPad.isEmpty() ) {
    // Set initial size
    resizeCanvas();
  } else {
    
    // Calculate new size
    let currentWidth = $('#wrapper').width();
    let scale = currentWidth / prevWidth;
    prevWidth = currentWidth;  // Prepare for next time
    // Scale the contents along with the width
    setRescaledSignature(lines, scale);
    // Match canvas to window size/device change
    resizeCanvas();
    // Load the adjusted canvas contents
    signPad.fromData(lines);
  }
};


// This is really the key to keeping the contents
// inside the canvas. Getting the scale right is important.
function setRescaledSignature (lines, scale) {
  lines.forEach(line => {
    line.points.forEach(point => {
      // Same scale to avoid warping
      point.x *= scale;
      point.y *= scale;
    });
  });
};


function resizeCanvas () {
  /** Have to resize manually to keep the canvas the width of the
  *   window without distorting the location of the "pen". */
  
  // I'm not completely sure of everything in here
  const canvas = $('#pad')[0];

  // Not sure why we need both styles and props
  canvas.style.width = '100%';
  canvas.style.height = (canvas.offsetWidth / 1.75) + 'px';
  
  // 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.
  let 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);
};

As you can see from my notes, I'm not completely sure why every part works, but from what I can tell it does preserve the behavior of the version that @Alexey Nikonov made.

Mixchange
  • 893
  • 1
  • 8
  • 14