Loading javascript libraries that are contained within an appended SVG document

I am currently working on developing a browser-based SVG rasterizer. The unique aspect of this project is that the SVG files may contain JavaScript code that directly impacts the output, such as randomly changing element colors, and utilizes external libraries like chroma.js.

The workflow I am aiming to establish includes:

  1. loading the SVG file
  2. loading linked libraries within the SVG file
  3. executing the included JavaScript code within script tags
  4. displaying on a canvas
  5. saving the canvas as a PNG image

This process is crucial because simply appending the SVG to an HTML element does not execute the JavaScript contained within the SVG itself.

While most components are functional, the challenge lies in loading external libraries (as outlined in step 2). The snippet responsible for this task looks like this:

$.get('/some_svg_file.svg', function(d) {
      // functionality will be updated for file drag/drop feature
      var getLibs = Array.from(d.querySelectorAll('script'))
        .filter(p => p.getAttribute('xlink:href'))
        .map(p => {
          return axios.get(p.getAttribute('xlink:href'))
        })

      // ensure embedded JS runs after library downloads
      Promise.all(getLibs).then((values) => {
            values.forEach(d => {
              try {
                eval(d.data);
              } catch (e) {
                console.log(e)
              }
            });

            Array.from(d.querySelectorAll('script'))
              .forEach(p => {
                if (p.textContent.length) {
                  try {
                    eval(p.textContent)
                  } catch (e) {
                    console.log(e)
                  }
                }
              })
           // include code for writing to canvas here
     })
})

When attempting to load chroma.js from an external source, all scripts within the SVG fail with the error

ReferenceError: chroma is not defined
.

An alternative approach involved using the following script tag method:

$.get('/foo_with_js.svg', function(d) {
      var getLibs = Array.from(d.querySelectorAll('script'))
        .filter(p => p.getAttribute('xlink:href'))
        .map(p => {
          var s = document.createElement('script');
          s.src = p.getAttribute('xlink:href');
          s.type = "text/javascript";
          s.async = false;
          document.querySelector('head').appendChild(s);
        })
   // complete script loading and save to canvas
})

This method also resulted in a similar failure, despite chroma.js being correctly placed in the head section.

Hence, my question remains - how can I ensure seamless loading of SVG, embedding it into HTML, and executing internal scripts without manually pre-linking all potential scripts in the HTML?

And if you're wondering why not opt for another conversion technique, the simple answer is "due to inconsistent support for SVG filters."

Answer №1

Is it possible to integrate any JavaScript library into SVG? That's an interesting question. In this example, I've utilized chroma.js as per your suggestion.

The SVG document is linked to the data attribute of the <object>. While currently static, it could potentially be updated dynamically. The SVG performs tasks like setting color on the <rect>, and animating the cy attribute of the <circle>. At any given point, I can access the contentDocument (contentDocument.documentElement.outerHTML) of the <object>, representing the SVG document at a specific moment. Any changes in values within the document will reflect in the contentDocument. This outerHTML can then be converted into a data URI, set as src on an image object, and rendered in the <canvas> element. Notably, the JavaScript library and code references within the outerHTML are excluded from execution when the image is rendered in the <canvas>.

SVG document:

<?xml version="1.0"?>
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
    <defs>
        <filter id="drop-shadow">
            <feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
            <feoffset in="blur" dx="4" dy="4" result="offsetBlur"/>
            <feMerge>
                <feMergeNode in="offsetBlur"/>
                <feMergeNode in="SourceGraphic"/>
            </feMerge>
        </filter>
    </defs>
    <script href="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.2/chroma.min.js"/>
    <rect x="0" y="0" width="200" height="200" />
    <circle cx="40" cy="40" r="20" fill="blue" style="filter: url(#drop-shadow);"/>
    <script>
        // <![CDATA[
        var cy = 40;
        var speed = 5;
        document.querySelector('rect').style.fill = chroma('hotpink');
        setInterval(function(){
            if(cy > 160 || cy < 40) speed *= -1;
            cy += speed;
            document.querySelector('circle').attributes['cy'].value = cy;
        }, 500);
        // ]]>
    </script>
</svg>

HTML document:

<!DOCTYPE html>
<html>
    <head>
        <title>SVG</title>
        <script type="text/javascript">
            var object, canvas, img, loop;

            document.addEventListener('DOMContentLoaded', e => {
                object = document.querySelector('object');
                canvas = document.querySelector('canvas');
                img = new Image();

                img.addEventListener('load', e => {
                    canvas.getContext('2d').drawImage(e.target, 0, 0);
                });

                object.addEventListener('load', e => {
                    loop = setInterval(function(){
                        var svg64 = btoa(e.target.contentDocument.documentElement.outerHTML);
                        var b64Start = 'data:image/svg+xml;base64,';
                        img.src = b64Start + svg64; 
                    }, 500);
                });
            });
        </script>
    </head>
    <body>
        <object type="image/svg+xml" width="200" height="200" data="test.svg"></object>
        <canvas width="200" height="200"></canvas>
    </body>
</html>

To run these documents, they must be served from a server rather than directly from the filesystem.

Based on my tests, the SVG animations and CSS animations do not function in this configuration. Only the "animations" in JavaScript that involve DOM manipulation work effectively.

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

What is the method to identify the key responsible for triggering a textbox input event?

Suppose there is a textbox on the webpage: <input id='Sub' type='text'> To capture each time the input changes, you can use the following code: sub = document.getElementById('Sub'); sub.addEventListener('input&a ...

What are the benefits of storing dist in both a GitHub repository and on npm?

I'm curious about why some repositories include a dist folder. Shouldn't repositories just store source code and not any builds or compiled files? Let's take a look at an example with ES6 code. package.json { "files": [ "dist", ...

I'm curious if the response order will mirror the URL order in my situation

QUERY: Upon reviewing the following link: Promise.all: Order of resolved values I am doubtful about its relevance to my specific scenario. Can I always expect responses to be in the same order as urls? EXAMPLE: var urls = []; for (var i = 0; i < d ...

JavaScript live search dynamically loads MySQL data into memory

Currently, I have implemented a live search feature through a text box on my website. This feature queries a MySQL database to return matching rows as the user types. However, I have noticed that this has significantly increased the memory load on my datab ...

What is the best way to display the JSON data?

<!DOCTYPE HTML> <html> <head> <title></title> <link href="/bundles/hoaxpartner/css/style.css" type="text/css" rel="stylesheet" /> </head> <body> <div id="header">Backbone</div> &l ...

Are there any available npm modules for accurately tallying the total word count within a document saved in the .doc or .doc

I Need to tally the number of words in doc/docx files stored on a server using express.js. Can anyone recommend a good package for this task? ...

What could be the reason for the malfunction of the select (mongoose query)?

I'm trying to retrieve a User's highest score post. To accomplish this, I am querying the Post model and looking for posts where their user._id matches the author in the post. Everything is functioning correctly in this regard. However, my goal ...

Transforming the output of a MySQL query into a JSON format organized like a tree

This question has been asked before, but no one seems to have answered yet. Imagine a scenario where a MySQL query returns results like this: name | id tarun | 1 tarun | 2 tarun | 3 If we were to standardize the JSON encoding, it would l ...

Having issues with ajax posting functionality

Is the following form correct? <form> <input type="submit" value="X" id="deleteButton" onclick="ConfirmChoice(<?php echo $id; ?>)" /> </form> Here is the function associated with the form: <script type='text/javasc ...

Unable to receive a response in React-Native after sending a post request

My current challenge involves sending a response back after successfully making a post request in react-native. Unfortunately, the response is not arriving as expected. router.route("/addUser").post((req, res) => { let name= req.body.name; connection ...

Handling multiple promises with JavaScript/Express Promise.all()

For my latest project, I am developing a movie discussion forum where users can create profiles and list their favorite films. To display the details of these movies, I have integrated the OMDB API with a backend route built using Express. In my initial t ...

Utilizing the closest method to retrieve the form element

As I review code written by another developer, I came across a surprising method used to retrieve a form object. HTML for Login Form <form id="frm_login" action=""> <input type="text" name="username" id="username"> <input type="passwor ...

What is the best way to transfer a value to the database using a distinct module?

I have recently set up a basic express project and added a file named lib/userhandler.js in the main directory. //lib/userhandler.js exports.addUser = function(req, res){ // Accessing our internal database variable var db = req.db; // Retrieving ...

Adding a QR code on top of an image in a PDF using TypeScript

Incorporating TypeScript and PdfMakeWrapper library, I am creating PDFs on a website integrated with svg images and QR codes. Below is a snippet of the code in question: async generatePDF(ID_PRODUCT: string) { PdfMakeWrapper.setFonts(pdfFonts); ...

Continuously calling setState within the useEffect hooks causes an infinite loop

After extensive research and reading various articles on the topic, I am still facing the issue of infinite loops with useEffect and useState. One article that caught my attention was this one. Prior to reading the article, my useState function inside use ...

What steps are necessary to add a Contact Us form to my HTML website?

Currently, I am searching for a way to add a "Contact Us" section to my website that is completely HTML-based. I believe the best approach would involve using a PHP script to handle sending emails from a form on the Contact Us page, but I am not familiar ...

Tips for displaying HTML content using an array in Vue JS

Hi, I'm a beginner with Vue JS and I'm working on passing an HTML table using this array. I have a dropdown where I can select the option I want, but I'm struggling to figure out how to include HTML within it. Whenever I try, it ends up disp ...

Combining the jquery-UI slider functionality with the canvas rotate() method

Is there a way to rotate an image using html2canvas plugin and jQuery UI slider? I am new to programming and need some guidance on how to achieve this feature. Here is my current progress: http://jsfiddle.net/davadi/3d3wbpt7/3/ ` $("#slider").slider({ ...

Iterate through a JavaScript grid and display the items

I am attempting to iterate through a JavaScript table, aiming to display the elements located between two selections made by the user. Here is an example of the table structure: if the user selects IDs 2 and 5, the loop should display rows 3 and 4. I trie ...

Tips for locking the button in the navigation bar while scrolling

I noticed that when I have 6 fields in my navbar, with 5 of them being links and one as a dropdown, the scrolling of the page causes all fields to remain fixed except for the dropdown field.Check out this image description for reference https://i.stack.im ...