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

Get rid of numerous div elements in a React.js application with the help of a Remove button

I need help figuring out how to efficiently remove the multiple div's that are generated using the add button. I am struggling to grasp how to pass the parent div's id into the delete method from the child div. Additionally, I'm wondering if ...

Customizing React components based on API data

const LinkList = () => { const [links, setLinks] = useState([]); const url = 'http://localhost:5000/xyz'; const hook = () => { console.log('effect'); axios .get(url) .then(respo ...

eliminate empty lines from csv files during the uploading process in Angular

I have implemented a csv-reader directive that allows users to upload a CSV file. However, I have noticed an issue when uploading a file with spaces between words, resulting in blank lines being displayed. Here is an example: var reader = new FileReader ...

Create dynamic combo boxes using jQuery

My goal is to display a text field on the page if a user has only one credit card number in the database. However, if the user has multiple credit card numbers stored, I want to display all of them in a combo box. Below is the ajax response that I am rece ...

A versatile multi-select jQuery widget featuring the ability to choose options within a specific opt

I am on the hunt for a cool jQuery tool that has the following characteristics: It enables you to create multiple groups, such as: Group 1 - Sub 1 1 - Sub 1 2 - Sub 1 3 Group 2 - Sub 2 1 Group 3 - Sub 3 1 - Sub 3 2 By clicking on Group 1, for instance, ...

The functionality of jQuery's .hide method is not effective in this specific scenario

HTML section <div class="navigation-bar"></div> Jquery & JavaScript section function hideUserDiv(){ $('.ask-user').hide(); } var ask = '<div id="ask-user" style="block;position:absolute;height:auto;bottom:0;top ...

What is the best way to capitalize the first letter of a string in JavaScript?

Is there a way to capitalize only the first letter of a string if it's a letter, without changing the case of any other letters? For example: "this is a test" → "This is a test" "the Eiffel Tower" → "The Eiffel ...

What changes occurred to module file names following the process of minification?

I'm currently troubleshooting an issue with this particular code snippet: import globalComponents from './global-components'; // ... globalComponents.forEach((component) => { // eslint-disable-next-line no-underscore-da ...

What is the best way to select or deselect all asp.net checkboxes with just one checkbox event?

I am looking for a way to control the checkboxes on my webpage using a checkbox named Select All, allowing me to check or uncheck all checkboxes within individual div sections. Since my checkboxes are asp.net controls with runat=server, I am unsure how to ...

How can I create spacing between squares in an HTML div element?

Currently, I am working on my project using Laravel 5. I retrieve some integer values from a database and use them to create square divs in HTML. Below is the current output of my work. https://i.stack.imgur.com/sy2ru.png You may notice that there is spa ...

Sorting alphabetically, either by JAVA, JavaScript, or Idoc script

Currently, I have a task at hand that requires sorting items within categories alphabetically, with the exception of Examples. Special characters and numbers should be prioritized over letters in the sorting order. I've encountered an issue where mos ...

Retrieving information from MySQL using javascript

I am trying to fetch data from a MySQL database using JavaScript. This is the script I have used to load the JavaScript: <script type="text/javascript" src="http://www.domain.de/content/entwicklung/layer.js"></script> Here is the actual scri ...

Uploading files into an array using Angular 2

Struggling to incorporate an uploader within an array: I've got an array of users displayed in a table using "ng-repeat". I want to add a column with a button to upload an image for each user. Currently, I'm utilizing ng2-file-upload, but open t ...

"Tips for positioning the center of a map with the help of external latitude and longitude

I have developed a program that extracts an address from a database. To obtain the latitude and longitude of the address, I utilized geocoder (https://www.npmjs.com/package/geocoder). Now, every time I click a button to retrieve the address, I want the map ...

Using Discord.js to retrieve identical lines from a text file in a database

I've been working on a discord bot that is supposed to send a line from a .txt file to the message author via DM, then delete that specific line from the file. However, it seems to be sending the same line repeatedly. Any suggestions on how to resolve ...

Adding a three-dimensional perspective to an HTML5 canvas without utilizing CSS3 or WebGL

Here is a canvas I am working with: http://jsbin.com/soserubafe Here is the JavaScript code associated with it: var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); var w = canvas.width; var h = canvas.height; var cw=canvas. ...

Click a button to adjust the height of an element

My webpage features a dashboard with tabs on the left and corresponding content on the right. To ensure proper alignment, I have utilized Bootstrap to create two columns - one for the tabs and one for the content. However, I am facing an issue where the bo ...

Svelte remains static and is not experiencing re-rendering

I am currently incorporating the history component in Svelte, where it should display when changes occur in the bids array. const bidsArray = [ { player: 0, bidType: 'pass' }, { player: 1, bidType: 'level', level: '1&apos ...

How to show dates in AngularJS using local formatting

One situation I'm dealing with involves setting a date for an event. The challenge is that the date picker needs to display the date in a localized format based on the country or region. I've been exploring various solutions in AngularJS, but so ...

How can I empty the value of a UI widget (such as an input field, select menu, or date picker) in Webix UI?

Is there a way in Webix UI to clear widget values individually, rather than collectively based on form id? I'm looking for a method using a mixin like $$(<form-id>).clear(). I need control of individual elements, so is there a proper way to res ...