Customizing Signature Image in Vue SignaturePad

Utilizing the Vue Signature Pad by neighborhood999 within my Larvel/Vue3 project has been a key feature. However, I am facing an issue crop the signature as users may not utilize the entire available space and might stay towards one side. Researching this problem led me to a GitHub discussion in szimek/signature_pad where multiple users proposed different approaches.

The main challenge I encountered is that all the solutions provided in the post require access to the canvas element of the Signature Pad, which is not exposed by Vue Signature Pad by neighborhood999. I am unsure how to access the canvas element in Vue3. Implementing the code snippets from the post resulted in a common error message:

error canvas.getContext is not a function
, regardless of whether using signaturePad.value or signaturePad. Here's what I have tried so far:

Note: The signature data resides in records, which is an array of signature records, with the actual data contained within records.[0].signature_data. Omitted for clarity are next and prev buttons used for navigation, which I believe are not crucial to the current issue at hand:

<template>
    <div class="container w-[650px] h-[350px] flex flex-col border-2 border-primary-500 p-2 rounded-lg">
        <div class="container overflow-auto">
            <VueSignaturePad width="620px" height="300px" :options="options" :scaleToDevicePixelRatio="true"
                ref="signaturePad" />
        </div>
    </div>

    ...

    <div class="flex flex-row space-x-3 md:justify-start justify-around py-3">
       ...
        <button type="button" class="btn-primary md:w-72 md:flex-none flex-1" @click="saveToFile">Save to file</button>
    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import { VueSignaturePad } from 'vue-signature-pad';
import useSignatureOps from '@/Composables/useSignatureOps';
import axios from 'axios';

// const devStore = useDeveloperStore();
const { convertLegacySignature, StringToArray } = useSignatureOps;

const props = defineProps({
    records: Array,
})

const options = ref({
    minWidth: 0.5,
    maxWidth: 2.5,
    penColor: 'black',
    backgroundColor: 'white',
});

const saveSignatureForm = useForm({
    url: null,
    signature_id: null,
})

const signaturePad = ref(null);

const currentIndex = ref(0);
const currentSignature = ref(props.records[currentIndex.value]);

....

const saveToFile = () => {
    const { isEmpty, data } = signaturePad.value.saveSignature();
    
    // without cropping the data object returned is perfect with the signature as in the signature pad

    if (!isEmpty) {
        // i want to crop the signature here and download the png to the user...
        // this below line is throwing an exception canvas.getContext is not a function
        const cropped = getCroppedCanvasImage(signaturePad.value); 
        console.log(cropped);
        
        // this below line is another implementation i found also throwing an exception canvas.getContext is not a function
        const cropped = getCroppedCanvasImage(signaturePad.value); 
        console.log(cropped);
    } else {
        console.warn('Warning: Signature is empty.');
    }

}

function getCroppedCanvasImage(canvas) { // here how do I pass the canvas object??

    let originalCtx = canvas.getContext('2d');

    let originalWidth = canvas.width;
    let originalHeight = canvas.height;
    let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);

    let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;

    for (y = 0; y < originalHeight; y++) {
        for (x = 0; x < originalWidth; x++) {
            currentPixelColorValueIndex = (y * originalWidth + x) * 4;
            let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
            if (currentPixelAlphaValue > 0) {
                if (minX > x) minX = x;
                if (maxX < x) maxX = x;
                if (minY > y) minY = y;
                if (maxY < y) maxY = y;
            }
        }
    }

    let croppedWidth = maxX - minX;
    let croppedHeight = maxY - minY;
    if (croppedWidth < 0 || croppedHeight < 0) return null;
    let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);

    let croppedCanvas = document.createElement('canvas'),
        croppedCtx = croppedCanvas.getContext('2d');

    croppedCanvas.width = croppedWidth;
    croppedCanvas.height = croppedHeight;
    croppedCtx.putImageData(cuttedImageData, 0, 0);

    return croppedCanvas.toDataURL();
}

const cropSignatureCanvas = (canvas) => {

    // First duplicate the canvas to not alter the original
    var croppedCanvas = document.createElement('canvas'),
        croppedCtx = croppedCanvas.getContext('2d');

    croppedCanvas.width = canvas.width;
    croppedCanvas.height = canvas.height;
    croppedCtx.drawImage(canvas, 0, 0);

    // Next do the actual cropping
    var w = croppedCanvas.width,
        h = croppedCanvas.height,
        pix = { x: [], y: [] },
        imageData = croppedCtx.getImageData(0, 0, croppedCanvas.width, croppedCanvas.height),
        x, y, index;

    for (y = 0; y < h; y++) {
        for (x = 0; x < w; x++) {
            index = (y * w + x) * 4;
            if (imageData.data[index + 3] > 0) {
                pix.x.push(x);
                pix.y.push(y);

            }
        }
    }
    pix.x.sort(function (a, b) { return a - b });
    pix.y.sort(function (a, b) { return a - b });
    var n = pix.x.length - 1;

    w = pix.x[n] - pix.x[0];
    h = pix.y[n] - pix.y[0];
    var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

    croppedCanvas.width = w;
    croppedCanvas.height = h;
    croppedCtx.putImageData(cut, 0, 0);

    return croppedCanvas.toDataURL();
}

</script>

Answer №1

To successfully utilize the hidden canvas within the ref value and incorporate it into the signaturePad Object, follow these steps:

cropSignatureCanvas(signaturePadRef.value.signaturePad.canvas)

In this scenario, the signaturePadRef is essentially the same as signaturePad in your existing code. Adjust your code accordingly:

const saveToFile = () => {
  const { isEmpty, data } = signaturePad.value.saveSignature();
  if (!isEmpty) {
      const cropped = 
      cropSignatureCanvas(signaturePad.value.signaturePad.canvas); 
      console.log(cropped);
  } else {
      console.warn('Warning: Signature is empty.');
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

My JavaScript code is not triggering during the page load. Could this be related to .net

I've been attempting to trigger a JavaScript function upon the refresh of an update panel, but for some reason it's not working. Oddly enough, I'm using the same approach that has worked for other functions. In C#, this is what I have in th ...

Why does the width of my image appear differently on an iPhone compared to other devices?

I recently encountered an issue with the responsiveness of an image on my website. While it displayed correctly on Android and desktop devices, the image appeared distorted on iPhones as if the CSS width attribute was not applied properly. This problem spe ...

(Javascript - Arrays) Find the leftmost and rightmost connected characters

Looking for the leftmost and topmost connected '1' character in a 2D matrix? Find the most left & top connected '1' character Find the most right & bottom connected '1' character EDIT 2.0: To start, provide the coordina ...

Using Internet Explorer with Vuejs may require adding a polyfill for Promise support

Having no prior experience with Vuejs, I found myself in a situation where I needed to debug code for Internet Explorer. The first problem was fixing all arrow Functions, which wasn't too difficult to handle. The second issue is that I am not able to ...

JS roles encountered an issue with a TypeError: it is unable to read the property 'push' because it is undefined

I'm experiencing a problem with setting roles in the admin panel of my website. The roles I've configured are mmUser, mmMerchant, mmAdmin. When a user signs up, they are automatically assigned the mmUser role. In the admin panel, I'm attemp ...

$routeProvider is erroring out with the message "Uncaught Error: [ng:areq] Argument 'fn' is not a function, received a string instead."

Currently, I am in the process of creating routing logic using ngRoute within Angular JS. Below is the snippet of code I am working with. index.js (function() { 'use strict'; function configureRoutes($routeProvider, $httpProvider, cfpLoadi ...

Execute a JavaScript function once the ASP.NET page finishes loading

Is there a way to execute a javascript function from ASP.NET code behind once the page has fully loaded? I've tried the following code, but it seems that the hidden field is not populated with the value before the javascript function is called, resul ...

jsLint reports an unusual assignment error during type casting

When coding in JS, I rely on both the google-closure compiler and jsLint tool. The closure compiler requires proper JSDoc tags to avoid errors, which means I often need to cast variables to the correct type. The code below works fine with no compiler warni ...

Error Message: SCRIPT5 - Permission Denied When Trying to Open PDF with Javascript

Despite searching through multiple posts on SO, I have yet to find a solution to my issue. We operate a web form within our LAN that utilizes Javascript's OPEN function to open PDF files. Up until recently, everything was working smoothly. However, ...

An error was returned by Ajax when attempting to make the ajax call

I have a custom ajax function that successfully updates my database. After the update, I call the successAlert() function. Now, I want to incorporate an error handling mechanism by calling the error function in case of any errors. However, during testing, ...

Twice the fetch is triggered

I've implemented a simple JavaScript function that handles fetching a URL and updating the HTML page upon receiving a response. Here's an example of how it works: function postToDatabase(self) { // other code here async function postData() ...

Incorporate the newest version of SASS in your Nuxt project using the @

Currently, I am implementing Sass into my project. I have successfully installed "node-sass" and "sass-loader", allowing me to utilize imports, variables, and other features of Sass. However, I am encountering an issue with using "@use" to employ a @mixin ...

Sharing pictures using filepond and vue 3

I am currently facing an issue while trying to upload an image using the filepond library. The problem I am encountering is that it returns a "419 error." As I construct my component in Vue, I have been experimenting with the following code: <file-pond ...

Modifying the CSS properties of elements with identical IDs

Encountering an issue while attempting to modify the CSS property of elements with similar IDs using JavaScript. Currently, running a PHP loop through hidden elements with IDs such as imgstat_1, imgstat_2, and so forth. Here is the code snippet being emp ...

Discovering the method to read a file that is currently downloading in either JavaScript or Python

Imagine a scenario where I am actively downloading a file while simultaneously wanting to read its contents. However, the file is being continuously updated during the download process. For instance, if I start reading the file when the progress bar shows ...

I need a regex pattern that will only match numeric values

Looking for a regular expression that can extract only numbers from a string. Specifically, numbers not preceded by a character. For example: "(a/(b1/8))*100 In this case, we do not want to include b1. We are interested in retrieving numbers like 8, 100, ...

Clearing console logs in webkit: A step-by-step guide

Does anyone know how to clear the console in webkit? I have a function that displays debug data in the console, but it becomes difficult to read due to the number of lines. Is there a simple way to empty the console? When I check the console itself, I onl ...

I encountered an ECONNREFUSED error while attempting to fetch data from a URL using NodeJS on my company-issued computer while connected to the company network. Strangely

After searching through forums and conducting extensive Google searches, I have come across a problem that seems unique to me. No one else has posted about the exact same issue as far as I can tell. The issue at hand is that I am able to successfully make ...

Executing a jQuery function automatically & Invoking a jQuery function using the onClick attribute in an HTML element

Having some issues with jQuery and could use some assistance... Currently, I am trying to have a jQuery function automatically execute when the page loads, as well as when a button is clicked. [Previous Issue] Previously, the jQuery function would automa ...

Tips for updating the HTTP Request URL using an Angular 6 interceptor

In my current project, I am utilizing an Angular app to communicate with a back end API endpoint using the HTTP module's get/post methods. Recently, I came across another Angular app that cleverly hides the actual API endpoint and replaces it with a d ...