Load an SVG image where the elements become part of the DOM - html

Consider this simple page:
<html>
<body>
<svg width="250" height="250">
<g>
<path id="foo" d="M20,230 Q40,205 50,230 T90,230" stroke="red" stroke-width="3" fill="none" onclick="setBlue()" />
</g>
</svg>
<script>
function setBlue() {
document.getElementById("foo").setAttribute("stroke", "blue");
}
</script>
</body>
</html>
It will display a red squiggly line. If you click on the line, it will turn blue. This demonstrates that JavaScript functionality is working inside this SVG object and also that the path element foo was added to the DOM itself.
Now instead load a static SVG that a browser could cache:
<img width="250" height="250" src="images/somesvg.svg" />
The embedded JavaScript does not hit. Asking the DOM for foo via JavaScript or jQuery returns nothing.
Does this mean the only way to name elements inside the SVG or to add JavaScript functionality is via rendering the SVG inside the HTML itself? I could shorten a page up significantly if I could add IDs to paths in an SVG file and then access them.

If it is an external *.svg file, A native Web Component <load-file> can fetch it and inject it in the DOM; either in shadowDOM or replacing the <load-file> Element, thus becoming part of the DOM you can access and style with CSS.
customElements.define("load-file", class extends HTMLElement {
// declare default connectedCallback as async so await can be used
async connectedCallback(
// call connectedCallback with parameter to *replace* SVG (of <load-file> persists)
src = this.getAttribute("src"),
// attach a shadowRoot if none exists (prevents displaying error when moving Nodes)
shadowRoot = this.shadowRoot || this.attachShadow({mode:"open"})
) {
// load SVG file from src="" async, parse to text, add to shadowRoot.innerHTML
shadowRoot.innerHTML = await (await fetch(src)).text()
// append optional <tag [shadowRoot]> Elements from inside <load-svg> after parsed <svg>
shadowRoot.append(...this.querySelectorAll("[shadowRoot]"))
// if "replaceWith" attribute
// then replace <load-svg> with loaded content <load-svg>
// childNodes instead of children to include #textNodes also
this.hasAttribute("replaceWith") && this.replaceWith(...shadowRoot.childNodes)
}
})
Full explanation in Dev.To post: 〈load-file〉Web Component, add external content to the DOM

As #diopside and #RobertLongson mention in the comments, the question was asked a different way here: Do I use <img>, <object>, or <embed> for SVG files?
The solution was to use <object> and embed the SVG in that. Now I can interact with it yet the browser doesn't need to reload the image each time the page loads.

Related

Weird Image with SVG in src attribute in Edge browser

I'm rendering image and I'm passing svg file as source attribute to image. It works perfect on all browsers, except Edge. I cant find an reason why it renders this weird black box with cross:
Html code of is like: <img width="3029" height="3920" id="id_of_image" src="data:image/svg+xml;charset=utf-8,<svg xmlns=....................CONTENT OF SVG............></svg>"/>
How can I show this kind of image also on edge? I'm filling src attribute from JS where I have element as string. I need to put it into image attribute, but question is how is that possible?
Problem solved. Issue was that in Edge it had problem with rendering svg when you are using tags directly in attributes. So solution was to replace and convert string in JS. I created function to generate SVG attribute for image from ajax request, when you have your SVG string:
function fixSVGDiagram(svgString) {
svgString = svgString.replace("<![CDATA[", "").replace("]]>", ""); //If styles occured, Edge crashes on that
svgString = svgString.replace(/#/g,"temporaryhash") //Because of hasthag issues (in styles)
svgString = encodeURI(svgString) //Magic happens
svgString = svgString.replace(/temporaryhash/g, "%23") //Get back hashtag
return "data:image/svg+xml;charset=utf-8," + svgString
}
If you are loading SVG as attribute in ajax request then you can store it as string:
svgData = (new XMLSerializer).serializeToString(responseData);
and then
var img = new Image
img.src = fixSVGDiagram(svgData)
and you can put image where you want.
svg code doesn't work like this for svg image.
Without the use of image tag you can directly use the svg tag in the html code.
OR.
You can create an svg file with *.svg extension and set the filename as the src in the image attribute.

Use an SVG image with non `.svg` extension in <img> tag

I'm trying to display an SVG in an <img> tag, but the SVG files don't have the .svg extension.
So a few things,
The file extension is not .svg, but the content within the file IS svg.
I need to use the <img> tag because it's important that I ignore the width and height in the SVG file (if any) and instead use the ones defined in my HTML/CSS. I cannot edit the contents of the SVG file, these are randomly uploaded and I don't want to be modifying them each time they are uploaded.
I can use PHP to read the data from the SVG, but it still needs to be displayed in the <img> tag.
Currently, when I try to use the SVG that has SVG content but no SVG file extension, it will act as though it couldn't load the image.
However, if I visit that same URL in my web browser, it will display the SVG fine regardless of the fact that it doesn't have a .svg extension.
<img src="https://di.community/uploads/monthly_2019_02/D-00023_svg.614976c7cefbcc2e89ab406b11f87800" />
How would I get the SVGs to display properly while still using the <img> tag to determine width/height, without modifying the SVG file and without an .svg extension.
I've tried the following, but the width/height is still used from the SVG and not the ones i define in HTML/CSS.
<span style='display: inline-block; margin: 0px; width: 35px !important; height: 35px !important;'>
<?php echo file_get_contents('https://di.community/uploads/monthly_2019_02/D-00023_svg.614976c7cefbcc2e89ab406b11f87800'); ?>
</span>
You can base-64 encode the text directly on the server and inject it directly into an image, or use the source as a remote reference.
<img src="data:image/svg+xml;base64,<?php echo PHP_BASE64_STRING ?>" />
<img src="https://di.community/uploads/monthly_2019_02/D-00023_svg.614976c7cefbcc2e89ab406b11f87800" />
The element's naturalWidth and naturalHeight properties will give the actual size of the image rather than the displayed/rendered size.
The script below will have the SVG data injected as-is into a block that shouldn't display.
HTML:
<script id="svgToCheck" type="script/no-execute">
<?php echo file_get_contents('https://di.community/uploads/monthly_2019_02/D-00023_svg.614976c7cefbcc2e89ab406b11f87800'); ?>
</script>
JS:
function getImageSizeFromUrl(url) {
return new Promise(function(resolve, reject) {
const image = document.createElement('img');
image.addEventListener('load', _ => resolve({ width: image.naturalWidth, height: image.naturalHeight }), false);
image.addEventListener('error', reject, false);
image.src = url;
}
}
// get the SVG
const svgText = document.getElementById("svgToCheck").innerHTML.trim();
// convert to data url
const svgUrl = `data:image/svg+xml;base64,${window.btoa(svgText)}`;
// get the size
getImageSizeFromUrl(svgUrl).then(size => console.log(size.width, size.height));
Use an <object> tag instead. It gives you the opportunity to state the MIME type directly. Sizing works just as with <img> tags.
<object type="image/svg+xml" data="myFile.extension"></object>

How to specify initial position of browser when viewing SVG file or embedded SVG code

I have an SVG file with a network diagram (rectangle with arrows connecting the rectangles). It is much larger that the browser's viewport. The logical starting point when reading the diagram is at a rectangle near the center of the diagram. I would like the initial view to be centered on this particular rectangle so the user doesn't have to hunt it down.
I am aware of the HTML anchor tag that allows you to open a webpage at a particular location. It is my understanding the tag within the SVG file does not work the same way. Is there another way of controlling the initial position of the browser to start at an element of an SVG file? I don't mind having to embed the SVG code within an html file if there is a solution that would require that.
The solution that seems to work with Google Chrome, Firefox and Android WebView is to use window.scroll(x,y) in JavaScript. For example:
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="5000" height="5000">
<rect id="rect1" x="3000" y="3000" width="200" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)"/>
<script type="text/javascript">
var rect1 = document.getElementById("rect1");
var x = 3100 - window.innerWidth/2;
var y = 3050 - window.innerHeight/2;
window.scroll(x,y);
</script>
</svg>

Using base tag on a page that contains SVG marker elements fails to render marker

I've run into a problem while attempting to use SVG marker elements in an SVG based visualization. I'm adding my changes to a web application which happens to include a base tag on every page, so that any references to CSS files, javascript files, etc can be relative.
I have some example code below which reproduces the issue. There is a line element, and a marker element defined. The marker element is referenced by the line in its 'marker-end' attribute, via uri and id of marker. Without the base tag, the arrow displays fine. With the base tag, it is not shown. The reason is because the base tag changes the way the browser resolves urls.. even for the simple id based url specified in the marker-end attribute of the line.
Is there any way I can get around this problem without having to remove the base tag?
I can't really remove it because the use of it is fairly ingrained in the product I'm working on. I need to support Firefox, Chrome and IE9+ for my webapp. Firefox and chrome both produce this problem. IE works fine (ie. arrow marker displays).
<html>
<head>
<base href=".">
<style>
.link { stroke: #999; stroke-opacity: .6; }
marker#arrow { fill: black; }
</style>
</head>
<body>
<svg width="100%" height="100%">
<defs>
<marker id="arrow" viewBox="0 -5 10 10" refX="0" refY="0" markerWidth="20" markerHeight="20" orient="auto">
<path d="M0,-5L10,0L0,5"></path>
</marker>
</defs>
<line x1="100" y1="100" x2="333" y2="333" marker-start="url(#arrow)" class="link"></line>
</svg>
</body>
</html>
The HTML <base> element is used to say "resolve all relative URLs relative not to this page, but to a new location". In your case, you've told it to resolve relative to the directory with the HTML page.
The SVG marker-mid="url(…)" attribute is a FuncIRI Reference. When you use a value like url(#foo) that relative IRI is normally resolved relative to the current page, finding the element with the foo id. But, when you use <base>, you change where it looks.
To solve this problem, use a better value. Since your base reference is the current directory, you can simply use the name of the current file:
<line … marker-mid="url(this_page_name.html#arrow)" />
If you have a different <base> href, than what you've shown, like:
<base href="http://other.site.com/whee/" />
then you will need to use an absolute href, e.g.
<line … marker-mid="url(http://my.site.com/this_page_name.html#arrow)" />
Try with javascript:
<line id="something" />
With native:
document.getElementById('something').setAttribute('marker-mid', 'url(' + location.href + '#arrow)');
With jQuery:
$('#something').attr('marker-mid', 'url(' + location.href + '#arrow)');
It just works.
In the context of a rich web app like one built on Angular, where you need to set the <base> tag to make HTML5-style navigation work, it can get messy to try to fix that in a permanent way.
In my case, the app I was working on was showing a SVG-based interactive diagram builder that would change the app url as I selected elements therein.
What I did was to add a global event handler that would fix all url(#...) inline styles in any <path> element found in the page:
$rootScope.$on 'fixSVGReference', ->
$('path').each ->
$path = $ this
if (style = $path.attr 'style')?
$path.attr 'style', style.replace /url\([^)#]*#/g, "url(#{location.href}\#"
Then trigger this handler in key places, like when the app state changes (I'm using ui-router)
$rootScope.$on '$stateChangeSuccess', ->
$timeout (-> $rootScope.$emit 'fixSVGReference'), 5
As well as anywhere where I know there'd be new/updated paths like these. Here, the $timeout thing is to account for the fact that the DOM nodes really are changed asynchronously sometime after the $stateChangeSuccess event is triggered.
In Angular 2+, you can inject the base path in your app module instead of using the <base> tag. This resolved the issue in Edge and Firefox for me.
import { APP_BASE_HREF } from '#angular/common';
#NgModule({
providers: [{
provide: APP_BASE_HREF,
useValue: '/'
}]
})
export class AppModule { }
https://angular.io/docs/ts/latest/api/common/index/APP_BASE_HREF-let.html
Ember 2.7 will replace the <base> tag with rootURL which should fix this issue.
In the meantime in my d3 for gradients I'm using the following:
.attr('fill', `url(${Ember.$(location).attr('href')}#my-gradient)`);
If you don't do this, the item you are targeting will seem to be transparent.
On Windows currently (04-2017) all Browsers behave as expected ( mask=url("#svgmask") ). Chrome, Firefox, even IE 11!! - but Edge comes up with an error.
So for Microsoft Edge you still need to give the absolute path ( mask="url(path/to/this-document.htm#svgmask)" ) for your mask ID´s when you are using a base tag in your document:
<svg viewBox="0 0 600 600" >
<defs>
<mask id="svgmask">
<image width="100%" height="100%" xlink:href="path/to/mask.svg" ></image>
</mask>
</defs>
<image mask="url(path/to/this-document.htm#svgmask)" width="600" height="600" xlink:href="path/to/image.jpg"></image>
</svg>
If you do not want want to modify / animate the svg there is a simpler solution than changing the url() parameter.
Include the svg as image:
<img src="yourpath/image.svg">
You can archive it with:
$("[marker-mid]").attr("marker-mid", function () {
return $(this).attr("marker-mid").replace("url(", "url(" + location.href);
});

Does canvas render markup inside the tag

I'm creating an application that would preferably use canvas, however I need to support IE8.
I know I can use SVG but I was wondering if I could put the SVG element inside the canvas, but I don't want to load it twice, if you get me
I would suggest you to look into canvg.
https://code.google.com/p/canvg/
canvg is a SVG parser and renderer. It takes a URL to a SVG file or the text of an SVG file, parses it in JavaScript, and renders the result on a Canvas element.
Example code:
var ctx = document.getElementById('test').getContext('2d');
ctx.drawSvg('<svg><rect x="0" y="0" width="100" height="100" fill="red" /></svg>', 0 , 0 , 500, 500);
Working DEMO