Como criar um campo para assinatura digital usando canvas?

Neste exemplo, vou mostrar como implementar um campo para assinatura digital usando o elemento Canvas do HTML5.

Acesse o demo para testar no navegador.

AssinaturaDigital

Recomendo o uso de uma caneta touch screen como essa para uso em Tablet e Smartphones:

image

Estrutura do Formulário

Variáveis

  • assinaturaBase64: Variável do tipo Text, que vai receber a Imagem da assinatura no formato Base64 do lado do servidor.

Campos

  • elmCanvas: Campo do tipo HtmlElement que vai conter o elemento Canvas. Informe o código abaixo na propriedade Template Html:
    <canvas id="board"></canvas>
    
  • btnConfirmar: Campo do tipo Button. Ao clicar neste botão, a imagem será enviada para o servidor no formato Base64.

Procedimentos

  • btnConfirmar_Click: Este procedimento será chamado no evento Click do botão btnConfirmar. Neste procedimento, será possível acessar a variável assinaturaBase64 e grava-la no banco de dados. Neste exemplo, vamos apenas mostrar uma mensagem informando que o arquivo foi recebido:

    image

Arquivos JavaScript

Adicione estes dois arquivos Javascript no Formulário. O nome dos arquivos não precisa ser os mesmos:

  • signature-manager.js: Script que gerencia a area de desenho da assinatura. E neste arquivo que os eventos de mouse e a configuração do canvas são gerenciados.
    var latromi = latromi || {};
    
    latromi.esignature = function (canvas, settings) {
    
       let ctx;
       let drawing = false;
       
       function getPos(canvasDom, mouseEvent) {
          
          var clientRect = canvasDom.getBoundingClientRect();
          
          return {
             x: mouseEvent.clientX - clientRect.x,
             y: mouseEvent.clientY - clientRect.y
          }
       }
       
       function getTouchPos(canvasDom, touchEvent) {
          var rect = canvasDom.getBoundingClientRect();
          return {
             x: touchEvent.touches[0].clientX - rect.left,
             y: touchEvent.touches[0].clientY - rect.top
          };
       }
       
       function onCanvasMouseDown(evt) {
          var pos = getPos(canvas, evt);
          ctx.moveTo(pos.x, pos.y);
          drawing = true;
       }
       
       function onCanvasMouseUp(evt) {
          drawing = false;
       }
       
       function onCanvasMouseMove(evt) {
           if (drawing) {
             var pos = getPos(canvas, evt);
               ctx.lineTo(pos.x, pos.y);
               ctx.stroke();
           }
       }
       
       function applySettings() {
          if (settings.width)  canvas.width  = settings.width;
          if (settings.height) canvas.height = settings.height;
          if (settings.lineWidth) ctx.lineWidth = 4;
          ctx.imageSmoothingEnabled=true; 
          
          // Aplica cor de fundo no canvas
          if (typeof settings.backcolor === "string") {
             ctx.fillStyle = settings.backcolor;
             ctx.fillRect(0, 0, canvas.width, canvas.height);
          }
       }
       
       // ########### [Configuração do Canvas - Inicio] ###########
       ctx = canvas.getContext("2d");
       canvas.addEventListener('mousedown', onCanvasMouseDown);
       canvas.addEventListener('mouseup'  , onCanvasMouseUp);
       canvas.addEventListener('mousemove', onCanvasMouseMove);
       
       // Converte eventos TouchScreen em enventos de Mouse
       canvas.addEventListener('touchstart', function (evt) {
          var touch = evt.touches[0];
          var mouseEvent = new MouseEvent("mousedown", {
             clientX: touch.clientX,
             clientY: touch.clientY
          });
          canvas.dispatchEvent(mouseEvent);
       }, false);
       //
       canvas.addEventListener('touchend', function (evt) {
          var mouseEvent = new MouseEvent("mouseup", { });
          canvas.dispatchEvent(mouseEvent);
       }, false);
       //
       canvas.addEventListener('touchmove', function (evt) {
          var touch = evt.touches[0];
          var mouseEvent = new MouseEvent("mousemove", {
             clientX: touch.clientX,
             clientY: touch.clientY
          });
          canvas.dispatchEvent(mouseEvent);
       }, false);
    
       
       // Adiciona link para limpar e tentar novamente.
       var clearLink = document.createElement('a');
       clearLink.href='#';
       clearLink.innerHTML = 'Tentar novamente';
       clearLink.title = 'Clique aqui para limpar a assinatura e tentar novamente';
       clearLink.addEventListener('click', function(evt) {
          evt.preventDefault();
          canvas.width = canvas.width; 
          applySettings();
       });
       canvas.parentNode.insertBefore(clearLink, canvas);
       //
       // Evita rolagem quando o usuário tocar no 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);
       
       applySettings();
       
       // ########### [Configuração do Canvas - Fim] ###########
       
       return {
       
       }
    };
    
  • form-assinatura-digital.js: Este Script manda a imagem do Canvas que está no navegador para o servidor, quando o usuário clica no botão btnConfirmar:
    var latromi = latromi || {};
    
    latromi.myForm = function () {
    
       function onFormCreatedPrivate(arguments) {
          let board = document.getElementById('board');		
       
          new latromi.esignature(board, { 
             width:  $(board.parentElement).width(),
             height: $(board.parentElement).height(),
             lineWidth: 4,
             backcolor: "white"
          });
       }
    
       function onEventFiringPrivate(arguments){
          console.log('Evento ' + arguments.eventName + ' disparado.');
    
          if (arguments.eventKind   === 'Field' 
             && arguments.field.name === 'btnConfirmar' 
             && arguments.eventName === 'Click')
          {
             let board = document.getElementById('board');	
             let base64 = board.toDataURL("image/png").replace("image/png", "image/octet-stream")
             arguments.form.setVariableValue('assinaturaBase64', base64);
          }
    
       }
    
       function onFieldValueChangedPrivate(arguments){
          console.log('O valor do campo ' + arguments.field.name + ' foi alterado.');
       }
    
       return {
    
          onFormCreated: function(arguments) {
             onFormCreatedPrivate(arguments);
          },
    
          onEventFiring: function(arguments) {
             onEventFiringPrivate(arguments);
          },
    
          onFieldValueChanged: function(arguments) {
             onFieldValueChangedPrivate(arguments);
          }
       }
    };
    
    // Declara o formulário
    var myForm = new latromi.myForm();
    
    // Adiciona Callbacks no Formulário
    latromi.formManager.setOnFormCreatedCallback( myForm.onFormCreated );
    latromi.formManager.setOnEventFiringCallback( myForm.onEventFiring );
    latromi.formManager.setOnFieldValueChangedCallback( myForm.onFieldValueChanged );
    

Bom dia Daniel, deu certo essa implementação seguindo o passo a passo, obrigado!
Fiquei com uma dúvida de qual tipo de dado devo utilizar para ele armazenar no banco o valor da variável (assinaturabase64) ? Queria saber se seria o mesmo procedimento para armazenar uma imagem que até já tem um artigo publicado aqui na comunidade…

Obrigado.
Agradeço desde já!

Matheus

Que bom que deu certo @espindola.matheus!

Pode gravar em um campo varchar , ou text se for no PostgreSQL.

Oi Daniel, obrigado pelo retorno!
Seria possível um exemplo desse procedimento citado acima ? Pois não consegui reescrevê-lo corretamente no meu código.
Matheus

@espindola.matheus, basta usar a variável no Comando SQL, como se fosse um campo.

Ela aparecerá assim:

image

Olá Daniel, estive realizando alguns testes e aparentemente parece que a assinatura digital não esta sendo guardada pela variável assinaturaBase64 pois fiz um teste populando o campo que deveria receber os dados da váriavel no banco e após o update do form da assinatura esse mesmo campo foi limpo. Isso procede de alguma forma ?

Obrigado.
Matheus

olá @espindola.matheus!

Verifique se o nome do botão usado para confirmar no seu formulário está informado corretamente nessa parte do código JavaScript. No meu exemplo, o botão se chama “btnConfirmar”:

function onEventFiringPrivate(arguments){
      console.log('Evento ' + arguments.eventName + ' disparado.');

      if (arguments.eventKind   === 'Field' 
         && arguments.field.name === 'btnConfirmar' 
         && arguments.eventName === 'Click')
      {
         var base64 = board.toDataURL("image/png").replace("image/png", "image/octet-stream")
         arguments.form.setVariableValue('assinaturaBase64', base64);
      }

   }

Se estiver tudo certo, por favor poste o código JavaScript completo, e um Screenshot do cadastro do Formulário, onde apareçam os campos e as variáveis.

Adicionei a opção backcolor no módulo latromi.esignature. Essa nova opção corresponde a cor de fundo do campo de assinatura. Se não for informado, a imagem gerada fica com fundo transparente.