Svg clipPath in AngularJS app with HTML Base Element - angularjs

I use svg clipPath in my AngularJS project. I have a problem with specifying a relative url to the clipPath because i need to use the <base> element in my project.
For example this code works in a project without base, but not in a project with <base href="/">
<svg width="120" height="120"
viewPort="0 0 120 120" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="myClip">
<rect x="10" y="10" width="60" height="60"></rect>
</clipPath>
</defs>
<g clip-path="url(#myClip)">
<circle cx="30" cy="30" r="20"/>
<circle cx="70" cy="70" r="20"/>
</g>
</svg>
How can this be solved? I use ui-router, if that is relevant to the question...
This question is about the same, but the "solution" the OP found was to remove the base which isn't a solution in my case.

Change
<g clip-path="url(#myClip)">
So that you're using an absolute URL + a fragment identifier rather than just a fragment identifier on its own. E.g. url(http://mydomain.com/mypage#myClip)
You may be able to remove some parts e.g. the http and the domain if the base tag matches the absolute URL so /mypage#myClip might work.

In case anyone needs a function to do it here's one in pure JS:
It stores the original clipPath in the element data attribute and each time called, uses document.location.href to use absolute paths for url()
function fixClipPaths(svg, restore) {
Array.prototype.forEach.call(svg.querySelectorAll('*[clip-path]'), function (el) {
var clipUrl = el.getAttribute('clip-path');
if(!el.getAttribute('data-original-clip-path')) {
el.setAttribute('data-original-clip-path', clipUrl);
}
el.setAttribute('clip-path', 'url('+ (!restore ? document.location.href : '') + el.getAttribute('data-original-clip-path').substr(4));
});
}
// usage example
fixClipPaths(document.getElementsByTagName('svg')[0]);

Related

How to register custom icons by reading a SVG file from path?

I am building an application making use of #fluentui/react (version 8.34.7) and I am using create-react-app.
One of the things I would like to do is register custom icons which are in SVG format. Following the example here, I came up with the following code:
registerIcons({
icons: {
'hamburger-icon': (
<svg viewBox="0 0 16 16" role="presentation" focusable="false" aria-hidden="true">
<g>
<path d="M1 4.5v-1h14v1zm0 4v-1h14v1zm0 4v-1h14v1z"></path>
</g>
</svg>
)
}
});
and this works perfectly fine. I can reference hamburger-icon in my code and get the icon to display.
However, the issue with the code above is that I have to include svg file contents in my code. What I would like to do is read the SVG file contents and then use it in registerIcons method. I am not sure how I can do that.
If I do something like:
import Hamburger from 'assets/images/hamburger.svg';
I get a URL.
If I do something like:
import {ReactComponent as HamburgerIcon} from 'assets/images/hamburger.svg';
I get a React component whereas registerIcon method expects a React element.
Is it possible to simply specify a file path of an SVG file and register icon from that?
Well, it was ridiculously simple! All I had to do was this:
import {ReactComponent as HamburgerIcon} from 'assets/images/hamburger.svg';
registerIcons({
icons: {
'hamburger-icon': React.createElement(HamburgerIcon)
}
});
and all worked well.

Is there a way to copy the contents of a static file in Sveltekit into a string?

I am trying to build a web app with Sveltekit.
Currently I am working on a simple navigation which should include a text and an icon for each path. I want to generate this dynamically so I am doing this:
<script lang="ts">
export let currentRoute: string;
type Route = { icon: string; name: string; path: string };
const routes: Route[] = [
{ icon: '', name: 'Home', path: '/' },
{ icon: 'icons/servers.svg', name: 'Servers', path: '/servers' }
];
</script>
<nav>
<ul>
{#each routes as route}
<li class:active={currentRoute === route.path}>
<a href={route.path}>
<img src={route.icon} alt="" />
<p>{route.name}</p>
</a>
</li>
{/each}
</ul>
</nav>
This all works fine, but I actually want to change the color of the text and the svg when the corresponding li tag has the class active (which means it is the current route).
As I am using img I cannot change the color directly in css so my thought was to somehow read the static svg files via code and output them to keep everything dynamic.
So the question is: Is there a way to read the contents of each static svg file and just copy them into a variable to keep everything clean?
There are several ways to go around this.
svg as a component
One is to make all svg files components on their own, just name the file Server.svelte and keep the content of the file itself the same. Then include this component and use it as normal. This will inline the svg in your html and you can style it as normal.
I always have
<svg viewBox="0 0 24 24" height="1em" width="1em" fill="currentColor">
this ensures the icon scales with the surrounding text and takes it's color.
(if you don't want to rename the file to .svelte, you can use a plugin to import .svg files as well)
Refer to an included svg
Another technique is to write ```html
This requires you have somewhere else in your html the following
```html
<svg xmlns="http://www.w3.org/2000/svg" style="display: none" viewBox="0 0 24 24">
<defs>
<g id="myicon">
<path d="..." fill="currentColor" />
</g>
</defs>
</svg>
This will do more or less the same as the previous method. How you get all of this included is p to you. One idea would be to make an icons.svelte file with all the icons in it as defenition and include this at the top level. And another component Icon.svelte that sets up the xlink:href block for you so you can simply do <Icon id="myicon" /> in places where you need an icon

React JSX - Filling an svg with an image

I am trying fill a simple circle SVG with an image in React. The sources I found have not been helpful, for they don't seem to apply to JSX.
Here is the code I currently have:
import React, {Component} from 'react';
import Anime from '../images/Anime.jpeg';
class Circle extends Component {
render() {
return (
<div>
<svg height="300" width="300">
<circle cx="150" cy="150" r="100" stroke="black" stroke-width="3" fill={Anime} />
</svg>
</div>
);
}
}
export default Circle;
My first instinct was to tweak the fill attribute by either replacing the original color ="red" with ={Anime} or =url("../images/Anime.jpeg"), but neither worked.
(I know the path to the Anime image is correct, after testing it by rendering <img src={Anime} alt={Anime}></img> inside the div.
I see people using <defs> and <pattern> tags, as such (referring to this post):
<svg id="graph" width="100%" height="400px">
<!-- pattern -->
<defs>
<pattern id="image" x="0%" y="0%" height="100%" width="100%"
viewBox="0 0 512 512">
<image x="0%" y="0%" width="512" height="512" xlink:href="https://cdn3.iconfinder.com/data/icons/people-professions/512/Baby-512.png"></image>
</pattern>
</defs>
<circle id="sd" class="medium" cx="5%" cy="40%" r="5%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
</svg>
But these tags don't seem to work in React / JSX; I keep getting this error when I try to use them: Namespace tags are not supported by default. React's JSX doesn't support namespace tags. You can turn on the 'throwIfNamespace' flag to bypass this warning.
Is there a way around this? I want to be able to fit the following picture into a simple circle svg with a thick black border around it.
I figured out a way and wanted to post / share it, in case anyone is here, looking at this post and wondering what the (one of the) solution(s) is!
Here is my final code:
import React, {Component} from 'react';
import AnimeCropped from '../images/AnimeCropped.png';
class Circle extends Component {
render() {
return (
<div>
<svg height="670" width="670">
<circle cx="350" cy="350" r="300" stroke="black" stroke-width="5" fill="none" />
<image className='img-circle' xlinkHref={AnimeCropped} x='75.5' y="15" height="670" width="670"/>
</svg>
</div>
);
}
}
export default Circle;
Essentially what I did was:
Crop the photo to be a circle. I could have followed cubrr's recommendation above and clip-path'ed it (which is a great idea!), but instead - I just found and used a photo cropping tool online. I then named the newly-cropped picture "AnimeCropped" and saved it in the same directory.
Imported the picture into the component file and rendered via a <image> tag after the <circle> tag. It wasn't mentioned in my original question above, but I also hypothesized that inserting the image inside the circle might solve the problem. As such:
<svg height="670" width="670">
<circle cx="350" cy="350" r="300" stroke="black" stroke-width="5" fill="none">
<img src={AnimeCropped} alt={AnimeCropped}/>
</circle>
</svg>
But this didn't work.
In my final code, you'll see that the <image> tag comes after the <circle> tag, so technically, it's not really inside the circle, but just overlaying on top of it. Also note that I had to use <image>, not <img>. I'll have to do a bit of research on this, but <img> was giving me an error message.
Also important: In React, xlink:href won't work, but xlinkHref will! This really tripped me off, but I found this helpful post.
Using classNames, I edited the size of the cropped picture in my App.css file.
Configured the x- and y- coordinates so that it somewhat fits perfectly in the middle of the circle.
That's it! Just wanted to post this for closure / help me review what I learned.

Avoid duplicate filter definitions when rendering SVGs with React

I am using React to render some SVG elements that include filters. Here is a simplified example:
function ShadowCircle() {
return (
<g>
<defs>
<filter id="dropShadow" x="-20%" y="-20%" width="200%" height="200%">
<feOffset result="offOut" in="SourceAlpha" dx="1" dy="1" />
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="1" />
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
</filter>
</defs>
<circle r="4" cx="50" cy="50" fill="blue" filter="url(#dropShadow)" />
</g>
);
}
function App() {
return <svg width="100" height="100">
<ShadowCircle />
</svg>
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This works fine as long as I only render a single instance of my <ShadowCircle />, otherwise I end up with duplicate filters (and duplicate IDs) in the DOM.
Ideally I would like to be able to:
Ensure that only one filter is rendered whenever one or more shadow circles are rendered
Remove the filter when it is not in use
I have tried a custom approach using createPortal and the useEffect hook, but I faced a couple of problems:
It is not possible to render SVG Elements without wrapping them in <svg> tags, so the portal has to be a <div> and all my filters end up in separate <svg> documents.
Calling getElementById inside useEffect to check whether the filter was already on the page turned out to be unreliable, especially when multiple elements are rendered to the page at the same time.
How can I render one filter whenever needed (by one or more components), and remove it afterwards?

svg inside svg translates by browser resize

I have a svg and I am adding different svg elements to it:
<svg> .... <rect or circle and so on </svg>
What I did was I loaded an icon with an svg library to one on my circles. Everything was fine until I changed my browser size and I noticed the svg icons are moving outside of my circle.
<svg> <circle> <g> <svg ....the icon> </g> </svg>
Looks like because I have 2 svgs nested, the icon is moving, and the offset for it is maybe the browser!
How can I fix that? I need to load my images like this:
Image1 = require('svg-react-loader?name=Image1!./svgFiles/default.svg');
React.createElement(Image1, {});

Resources