I have an SVG that I plan to put on a website.
The SVG was generated from illustrator using using the method described here
When viewed in Firefox 84.0.1 I get this:
When viewed in Safari 14.0.2 (16610.3.7.1.9) and Chrome 87.0.4280.88 I get this:
Is this an issue with Firefox or an issue with the SVG?
What's the best way to debug?
Edit
I was able to isolate the problem to the following path. As pointed out by others the issue appears to be with invalid path descriptions in the SVG.
I was able to solve problem by simplifying the path in illustrator and re-exporting.
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="138.71" height="102.25" viewBox="0 0 138.71 102.25"><path d="M73.72,95.49a9.59,9.59,0,0,1,5.2,1.3,1990328647664.52,1990328647664.52,0,0,0,.2.2,9,9,0,0,1,3.7,3.8,8.75,8.75,0,0,0,7.7,4.8,8.46,8.46,0,0,0,7.7-4.8A9.27,9.27,0,0,1,102,97c.1-.1.2-.1.3-.2a10.56,10.56,0,0,1,5.2-1.3,8.71,8.71,0,0,0,7.8-4.3,8.39,8.39,0,0,0-.2-8.9,9.55,9.55,0,0,1-1.5-5.2v-.3a9.69,9.69,0,0,1,1.4-5.2,9,9,0,0,0,.3-9.1,8.64,8.64,0,0,0-8-4.3,9.59,9.59,0,0,1-5.2-1.3,2487910809616.32,2487910809616.32,0,0,1-.2-.2,9,9,0,0,1-3.7-3.8,8.75,8.75,0,0,0-7.7-4.8,8.46,8.46,0,0,0-7.7,4.8,9.27,9.27,0,0,1-3.8,3.8c-.1.1-.2.1-.3.2a10.56,10.56,0,0,1-5.2,1.3,8.71,8.71,0,0,0-7.8,4.3,8.39,8.39,0,0,0,.2,8.9,9.55,9.55,0,0,1,1.5,5.2v.3a9.69,9.69,0,0,1-1.4,5.2,9,9,0,0,0-.3,9.1A8.83,8.83,0,0,0,73.72,95.49Z" transform="translate(-61.55 -45.07)" fill="none" stroke="#6d6665" stroke-linejoin="round" stroke-width="6"/><path d="M154.45,95.49a9.11,9.11,0,0,1,5.3,1.4.1.1,0,0,1,.1.1c4.25,2.56,5.31,9,11.4,8.6,6.22.44,7.09-6.08,11.5-8.6s10.63-.31,13.3-5.8c3.54-5.22-1.82-9.29-1.7-14.4,0-4.93,5-9.26,1.7-14.3-2.73-5.6-9-3.13-13.3-5.7a.1.1,0,0,1-.1-.1c-4.25-2.55-5.31-9-11.4-8.6-6.23-.44-7.09,6.09-11.5,8.6s-10.64.31-13.3,5.8c-3.55,5.23,1.81,9.29,1.7,14.4a9.69,9.69,0,0,1-1.4,5.2C142.83,87.73,147.57,96.09,154.45,95.49Z" transform="translate(-61.55 -45.07)" fill="none" stroke="#6d6665" stroke-linejoin="round" stroke-width="6"/><text transform="translate(9.1 84.85)" font-size="12" fill="#231f20" font-family="MyriadPro-Regular, Myriad Pro">O<tspan x="8.27" y="0" letter-spacing="0em">r</tspan><tspan x="12.24" y="0">i</tspan><tspan x="15.05" y="0" letter-spacing="-0.01em">g</tspan><tspan x="21.68" y="0">inal</tspan><tspan x="4.68" y="14.4">32 pts</tspan></text><text transform="translate(84.57 84.85)" font-size="12" fill="#231f20" font-family="MyriadPro-Regular, Myriad Pro">Simplified<tspan x="9.92" y="14.4">15 pts</tspan></text></svg>
Here is an excerpt from the start of one of the paths in question:
M 279.17, 522.38
a 9.59, 9.59, 0,0,1, 5.2,1.3,
a 497582161915.69, 497582161915.69, 0,0,1, 0.2, 0.2,
a 9, 9, 0,0,1, 3.7, 3.8,
M is a Move path command. a means draw a elliptical Arc.
The first two numbers in the arc command are the X and Y radii of the ellipse. Somehow that second arc has ended up with an enormous radius.
With an radius that large, you could approximate a small section of it with a straight line. That is probably what Chrome and Safari are doing. However Firefox is clearly getting confused by values that large.
How that arc ended up like that, I have no idea. But it appears you have struck an edge case that FF isn't handling very well.
I would suggest reporting it as an Illustrator bug and possibly a FireFox bug. In the meantime, to work around the problem, you would need to edit those shapes and try and fix those faulty arc segments.
Edit: trying to create a mcve I wasn't able to repro the issue. Now I'm completely baffled. Works on codesandbox, not in my project.
Initial question
I want to create a dynamic inline SVG element and map its rotation to an [(ngModel)]. Nothing fancy.
The fancy part is that I want to use a <filter> with a <feDropShadow>. And I want the shadow to be dynamic (always point up, regardless of the needle's rotation). It's something I've done before using Vue.
Here's a fiddle demonstrating the effect: https://jsfiddle.net/websiter/y4ghan0k/
But, for the life of me, I can't get the <feDropShadow> to work in Angular when the <svg> is inlined in the template. It just won't display. No error or warning. If I insert it as <img src="path/to/svg"> it works as expected (the shadow is displayed), but then I can't rotate the path anymore, as the element transformed needs to be a child of the element bearing the filter.
Note it's not because of this url() filter issue - I am prefixing the filter with this.location.path().
Here's the gist of my Angular code:
component.ts:
import { Location } from '#angular/common';
export class SomeComponent {
constructor(private location: Location) {}
dsLink = `url(${this.location.path()}#drop-shadow)`;
}
component.html:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="180" y="100"
viewBox="0 0 180 100" xml:space="preserve">
<defs>
<filter xmlns="http://www.w3.org/2000/svg" id="drop-shadow" height="130%">
<feDropShadow dx="0" dy="-4" flood-color="rgba(0,0,0,.65)"/>
</filter>
</defs>
<g [attr.filter]="dsLink">
<path fill="#fff" d="M102.2,89.5c0-0.1,0-0.1,0-0.2c0-0.2,0-0.4-0.1-0.6L92.9,6.8c-0.1-0.8-3.2-0.9-3.3,0
L78.7,88.5c-0.1,0.2-0.1,0.4-0.1,0.6c0,0.1,0,0.1,0,0.2l0,0.1c0,0,0,0.1,0.1,0.1c0.5,2.4,5.6,4.4,11.7,4.4
c6.2,0.1,11.2-1.8,11.8-4.2c0,0,0.1-0.1,0.1-0.1L102.2,89.5z">
</path>
</g>
</svg>
For simplicity, I've removed the [(ngModel)] from path which is supposed to rotate the needle.
The filter url() appears to be correct, there's no error. But the shadow is not displayed.
Is there anything special I need to do/know in order to make Angular handle <svg> elements inline?
What am I missing?
I finally cracked it so I'm posting it here, hoping it will help others.
In short: use unique ids for filters in each of your component instances. Otherwise, each instance will use the first filter found in DOM (with that id) and if that filter happens to be inside a parent with display: none, visibility:hidden or opacity: 0, applying the filter will make whatever you apply it to invisible as well.
The issue had to do with the fact I was using the same component in different tabs. This created separate instances of the component, each of them using the same id (#drop-shadow). While having duplicate ids is obviously invalid HTML, this wouldn't actually have been a problem if we weren't dealing with filters. Because, since the <defs> are identical, it wouldn't really matter if the component on the 4th tab would use the <defs> defined by the component on the first tab.
Except when dealing with <filter>s, because they are actually calculating, pixel by pixel, the rendering result, dynamically. Which means that, when the <svg> defining the <filter> is not rendered, using the filter will make the browser calculate (pixel by pixel) the result applying the filter and it always result in all the pixels being invisible.
So the solution is to assign a unique id in each separate instance of the component.
Background:
I running an express server with a route to all my static files.
In the static files, I have a folder full of map .svg's.
On the client side (React), I fetch the maps and adding them to to a component.
Question:
I need to rewrite the .svg's, and add this to their path attribute: fill=url(#some_flag).
Svg before:
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024pt" height="1024pt" viewBox="0 0 1024 1024"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0,1024) scale(0.1,-0.1)"
fill="#000000" stroke="none">
<path d="... numbers...-78 -28 -115 "/>
</g>
</svg>
Svg After:
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="1024pt" height="1024pt" viewBox="0 0 1024 1024"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0,1024) scale(0.1,-0.1)"
fill="#000000" stroke="none">
<path d="... numbers...-78 -28 -115"
fill="url(#some_flag)"/> <!--THE ADDED ATTRIBUTE -->
</g>
</svg>
I had a few directions in mind:
Pure Javascript: setAttribute of path, which means I'll have to give each path an id?
somehow penetrate the .svg with CSS? I tried it and its impossible to override inline SVG attributes
Have you dealt with it before (of course you did...)? What are my options?
I'm answering this part: << I had a few directions in mind:
1- Pure Javascript: setAttribute of path, which means I'll have to give each path an id? >>.
It can be the best solution, but I wouldn't use "id" because it is not reliable enough (no unicity guaranty, if inadvertently duplicated).
You want to add an attribute fill="url(#some_flag)", and your #some_flag is probably indexed somewhere (flag[i]), and linkable to the sequence of path. If #some_flag must be computed from the geometry, you may pre-process it into an array, or compute at run-time. The CSS selector can be more complex also, depending on your needs. This may answer that part of your question:
document.querySelectorAll("g > path")
.forEach((x,i) => x.setAttribute("fill",`url(${flag[i]})`));
Actually I did experiment it with a svg map excerpted from The Economist Big Mac Index (a local copy):
fetch("./bigMacSvg1.txt", {method:"GET"})
.then(res => res.text())
.then(res => {
//console.log(res);
document.querySelector(".here").innerHTML = res; //div for svg
document.querySelectorAll("path:nth-child(n+2)")
.forEach(x => x.setAttribute("fill","#a1b2c3"));
})
.catch(err => {console.log(err)});
The above code sets a light color to every country, but not to the ocean (first path).
As I think you've discovered, including SVG's via an img tag is limiting. Avoid it.
You can set up a sort of proxy component that fetches the SVG contents, converts them to JSX, and lets you include the SVG contents inline. This will allow them to be easily manipulated with CSS and JavaScript.
Here's a library that will handle translating the files to JSX format.
https://www.npmjs.com/package/svg-to-jsx
I am currently working on svg animations.
I have a SVG element which follows a path with the animateMotion property.
My shape :
<g id="dart" transform="scale(-1,1) translate(-587, -145)">
<path d="..." />
</g>
My path :
<path id="motionPath" fill="none" stroke="none" stroke-miterlimit="10" d="..."/>
My AnimateMotion :
<animateMotion xlink:href="#dart" dur="1s" begin="0s" fill="freeze" rotate="auto"><mpath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#motionPath"></mpath></animateMotion>
It works perfectly. However, it begins to move after 2s when page is loaded. I would like to launch the animation on an JS event. Indeed, I need to scroll down in the page for see the SVG and when he is visible the animation starts.
The JS :
var pos = $("#dart").offset().top;
$(window).scroll(function(){
var scrollTop=$(window).scrollTop();
if(scrollTop>=pos){
/* --- start animation here with injecting --- */
var motion=document.createElementNS("http://www.w3.org/2000/svg","animateMotion");
motion.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#dart");
motion.setAttributeNS("http://www.w3.org/2000/svg", "dur", "1s");
motion.setAttributeNS("http://www.w3.org/2000/svg", "begin", "0s");
motion.setAttributeNS("http://www.w3.org/2000/svg", "fill", "freeze");
motion.setAttributeNS("http://www.w3.org/2000/svg", "rotate", "auto");
var mpath = document.createElementNS("http://www.w3.org/2000/svg","mpath");
mpath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", "#motionPath");
motion.appendChild(mpath);
document.getElementById("target").appendChild(motion);
}
});
I tried to inject (with createElementNS) the <animateMotion> or to change the begin's value in order to start the animation on the event but it does not work.
If someone has an idea...
To summarise what I originially wrote in comments...
You'd normally set the animate motion to begin on click by setting its begin attribute e.g. begin="dart.click"
jquery is designed to work with html, any svg support is mostly by accident. So when it creates elements it creates them in the html namespace rather than the SVG namespace. So the first step is to convert your code to using standard DOM methods such as document.createElementNS.
If you do use the DOM to create elements and attributes, take care to create them in the correct namespace.
SVG elements should be created in the SVG namespace
most attributes are in the null namespace and should be set by calling element.setAttribute
xlink:href attributes are in the xlink namespace however and must be created by calling element.setAttributeNS, passing the xlink namespace as the first argument.
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);
});