Save image as base64, or alternatives? | XM Community
Skip to main content
Solved

Save image as base64, or alternatives?


Forum|alt.badge.img+3

Hi all!

 

I added a custom drag-and-drop function to my questions (finally). I want to save what the final position of the dragged images looks like, and it would be ideal if I could save it as an image. One way I saw this is that I could save the image as base64 encoding and decode it later to get the image. I can do this in console printing, however it won’t save as embedded data to my survey. I have the HTML code, JS code (which is where I set up the image saving), and the error below. The error seems to be due to Qualtrics thinking that the image is not coming from Qualtrics, but this is the url from the image library!

 

Error:

E API Error:  SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
    at saveFinalImage (eval at <anonymous> (jfeLib.fa2484b7040929bdf0ac.min.js:2:125561), <anonymous>:81:28)
    at A.i.eval (eval at <anonymous> (jfeLib.fa2484b7040929bdf0ac.min.js:2:125561), <anonymous>:89:4)
    at A.i.<anonymous> (jfe.f9dd9f5e15c3385f2b68.min.js:2:268179)
    at A.i.r (jfeLib.fa2484b7040929bdf0ac.min.js:2:29148)
    at A.i.s (jfeLib.fa2484b7040929bdf0ac.min.js:2:29191)
    at s (jfeLib.fa2484b7040929bdf0ac.min.js:2:28461)
    at A.i._trigger (jfeLib.fa2484b7040929bdf0ac.min.js:2:30008)
    at A.i.r [as _trigger] (jfe.f9dd9f5e15c3385f2b68.min.js:2:359422)
    at A.i.<anonymous> (jfe.f9dd9f5e15c3385f2b68.min.js:2:267208)
    at jfe.f9dd9f5e15c3385f2b68.min.js:2:372392 ƒ () {
            saveFinalImage();
            const tomorrowCoordinates = document.getElementById("tomorrowB1-coordinates").value;
            const yesterdayCoordinates = document.getElementById("yesterdayB… 

 

HTML:

<div style="position: relative; width: 600px; height: 400px; 

            background-image: url('https://uwartsandsciences.pdx1.qualtrics.com/ControlPanel/Graphic.php?IM=IM_eXemGs5D7yFpvhz'); 

            background-size: contain; 

            background-position: center; 

            background-repeat: no-repeat; 

            border: 1px solid #ccc; 

            margin: 0 auto; 

            overflow: hidden;" id="image-containerB1D" >

  <img style="position: absolute; top: 150px; left: 460px; width: 50px; cursor: grab; z-index: 10;"

       alt="Yesterday" 

       src="https://uwartsandsciences.pdx1.qualtrics.com/ControlPanel/Graphic.php?IM=IM_LtUZPmmUGH5uBbA"

       id="yesterdayB1">

    

  <img style="position: absolute; top: 175px; left: 230px; width: 50px; height: 50px; cursor: grab; z-index: 10;"

       alt="Today"

       src="https://uwartsandsciences.pdx1.qualtrics.com/ControlPanel/Graphic.php?IM=IM_a7VqGJ0RqP8RSH0"

       id="todayB1"> 

    

  <img style="position: absolute; top: 220px; left: 460px; width: 50px; cursor: grab; z-index: 10;"

       alt="Tomorrow"

       src="https://uwartsandsciences.pdx1.qualtrics.com/ControlPanel/Graphic.php?IM=IM_d5Oei9ZlTDYwxxp" 

       id="tomorrowB1">

</div>

<input id="yesterdayB1-coordinates" type="hidden">

<input id="todayB1-coordinates" type="hidden"> 

<input id="tomorrowB1-coordinates" type="hidden">

 

JS:

Qualtrics.SurveyEngine.addOnload(function () {

    // Ensure the script only runs for the visible question

    if (this.getQuestionContainer().style.display !== "none") {

        let draggingElement = null;

        let offsetX = 0;

        let offsetY = 0;

        const container = document.getElementById("image-containerB1D");

        // Add drag-and-drop functionality

        document.querySelectorAll('img').forEach(function (img) {

            img.addEventListener('mousedown', function (event) {

                draggingElement = img;

                offsetX = event.clientX - img.getBoundingClientRect().left;

                offsetY = event.clientY - img.getBoundingClientRect().top;

                // Set a high z-index while dragging to ensure it's on top

                img.style.zIndex = 1000;

                document.addEventListener('mousemove', onMouseMove);

                document.addEventListener('mouseup', onMouseUp);

            });

        });

        function onMouseMove(event) {

            if (!draggingElement) return;

            const containerRect = container.getBoundingClientRect();

            // Calculate the new position

            let left = event.clientX - containerRect.left - offsetX;

            let top = event.clientY - containerRect.top - offsetY;

            // Prevent dragging outside the container

            left = Math.max(0, Math.min(left, containerRect.width - draggingElement.offsetWidth));

            top = Math.max(0, Math.min(top, containerRect.height - draggingElement.offsetHeight));

            // Update position

            draggingElement.style.left = left + "px";

            draggingElement.style.top = top + "px";

        }

        function onMouseUp() {

            if (!draggingElement) return;

            // Update the hidden input for the dragged element

            const hiddenInput = document.getElementById(draggingElement.id + "-coordinates");

            if (hiddenInput) {

                hiddenInput.value = draggingElement.style.left.replace("px", "") + "," + draggingElement.style.top.replace("px", "");

            }

            // Reset z-index after dragging

            draggingElement.style.zIndex = 10;

            draggingElement = null;

            document.removeEventListener('mousemove', onMouseMove);

            document.removeEventListener('mouseup', onMouseUp);

        }

        

        // Save image as embedded data

        function saveFinalImage() {

            const container = document.getElementById("image-containerB1D");

            if (!container) return;

            const containerRect = container.getBoundingClientRect();

            const canvas = document.createElement('canvas');

            canvas.width = containerRect.width;

            canvas.height = containerRect.height;

            const ctx = canvas.getContext('2d');

            

            // Draw all images onto the canvas at their current positions

            document.querySelectorAll('#image-containerB1D img').forEach(function (img) {

                const rect = img.getBoundingClientRect();

                const imgLeft = rect.left - containerRect.left;

                const imgTop = rect.top - containerRect.top;

                ctx.drawImage(img, imgLeft, imgTop, img.width, img.height);

            });

            

            // Convert the canvas to a base64 image

            const imageUrl = canvas.toDataURL('image/png');

            

            // Store base64 image in Embedded Data

            Qualtrics.SurveyEngine.setEmbeddedData("finalImage_1", imageUrl);

        }

        

        // Save coordinates on page submission

        Qualtrics.SurveyEngine.addOnPageSubmit(function () {

            saveFinalImage();

            const tomorrowCoordinates = document.getElementById("tomorrowB1-coordinates").value;

            const yesterdayCoordinates = document.getElementById("yesterdayB1-coordinates").value;

            // Save coordinates into Embedded Data fields

            Qualtrics.SurveyEngine.setEmbeddedData("tomorrowCoordinates", tomorrowCoordinates);

            Qualtrics.SurveyEngine.setEmbeddedData("yesterdayCoordinates", yesterdayCoordinates);

        });

    }

});

Best answer by ahmedA

Everything looks fine. I would just recommend moving your  saveFinalImage();  to the mouseup event. As you are calling it on page submit, its possible that the data processing is taking some time and therefore the page is moving on without storing the img url.

View original

4 replies

Forum|alt.badge.img+22
  • Level 7 ●●●●●●●
  • 2028 replies
  • February 1, 2025

Add this to your JS:

Qualtrics.SurveyEngine.addOnReady(function () {
	document.querySelectorAll("img").forEach((item) => item.setAttribute("crossOrigin", "anonymous"));
});

 


Forum|alt.badge.img+3
  • Author
  • Level 2 ●●
  • 14 replies
  • February 3, 2025

@ahmedA Thank you so much! I tried this, and I get the console message just fine still, and now I don’t get the error, but it still does not save to my embedded data. I reduced the image size and checked that the string is not more than 20,000 characters. I also added a test embedded data line right under the base64 string saving the word “hello”, and that doesn’t save either. Do you see anything wrong in the structure of the code that would prevent this from being saved to the embedded data?


Forum|alt.badge.img+22
  • Level 7 ●●●●●●●
  • 2028 replies
  • Answer
  • February 3, 2025

Everything looks fine. I would just recommend moving your  saveFinalImage();  to the mouseup event. As you are calling it on page submit, its possible that the data processing is taking some time and therefore the page is moving on without storing the img url.


Forum|alt.badge.img+3
  • Author
  • Level 2 ●●
  • 14 replies
  • February 3, 2025

@ahmedA that resolves it, thank you so much!!!