Why does svg not get requested with app load - svg

I've got a live site https://courageous-kulfi-58c13b.netlify.app/. Whenever a checkbox is clicked, it should turn green with a tick svg on it. However, on the first app load, clicking a checkbox will show a noticeable delay between the background turning green and the tick svg actually showing. NextJS only requests for the svg once I click on the checkbox. The svg is used in my css as a background image in a ::before pseudo element. How can I make next request for the svg in initial render so there isn't this delay?
const Checkbox = (props: Props) => {
const [checked, setChecked] = useState(false);
const handleClick = () => {
setChecked((prevChecked) => !prevChecked);
};
return (
<>
<input
type="checkbox"
onClick={handleClick}
className={cn({
"before:bg-tick": checked,
})}
></input>
</>
);
};
tailwind.config.js
module.exports = {
extend: {
backgroundImage: {
tick: "url(/Tick.svg)",
},
}
}

You can inline the svg with something like the following. Then there will be no delay in downloading the svg.
.tick:before {
content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 342.357 342.357" style="enable-background:new 0 0 342.357 342.357" xml:space="preserve"><path d="M290.04 33.286 118.861 204.427l-66.541-66.52L0 190.226l118.862 118.845L342.357 85.606z"/></svg>');
}
Note I have run the svg through svgo to optimize it.
Example here: https://play.tailwindcss.com/TlRvKKs53I?file=css
Preload image
The other option would be to preload the image in the html head.
<link rel="preload" as="image" href="/Tick.svg">
The old school hacky way was to include an image tag on the page somewhere, but use css to make it not visible, but the browser would still load it.

Related

I don't see SVG on my website while using NEXTjs

i have problem with importing SVG to my NextJS website.
I've tried using <Image /> tag, <img /> tag too and <svg> tag also.
Webstorm sees the image, but browser doesn't.
code:
import Logo from "../public/file.svg"
const Navbar = () => {
return (
<div>
<nav>
<div>
<Image src={Logo} alt="logo" height={50} width={50}/>
I had a problem like this. The problem was gone when I replaced the imported .svg file and placed the URL directly in the Image. like this:
const Navbar = () => {
return (
//...
<Image src="/file.svg" alt="logo" height="50px" width="50px"/>
);
}
Notice that with this method, you start your URL with a / from public folder and should not include ../public itslef.

Can a SVGStyleElement be disabled like an HTMLStyleElement can?

Short version:
Unlike the HTMLStyleElement the SVGStyleElement has no disabled property (according to the MDN docs)
Question: can a whole SVGStyleElement be disabled/toggled?
Longer playground version:
I can inject HTML in a DIV and the HTMLStyleElement remains a HTMLStyleElement and respects the disabled state.
(there is a FOUC because unload=this.disabled=true kicks in after parsing)
The disabled Toggle Button works as it should.
Injected in an SVG the HTMLStyleElement becomes a SVGStyleElement
The onload is ignored (the exact behaviour I want for my Web Component)
FireFox shows the state undefined on first load because there is no disabled property
but!! the disabled Toggle button works in Chromium, not in FireFox (and Safari??)
Question: Which Browsers are doing what right?
<template id=TEMPLATE>
<style onload="this.disabled=true">
body { background: red; }
circle { fill:green; }
</style>
<circle cx="50%" cy="50%" r="45%"></circle>
</template>
<div id=DIV></div>
<svg id=SVG width="40" height="40"></svg>
<script>
let STYLE;// global
function inject(id) {
DIV.innerHTML=``;SVG.innerHTML=``; // wipe all
INNER.innerHTML=``;DISABLEDSTATE.innerHTML=``;
id.innerHTML = TEMPLATE.innerHTML; // set innerHTML in DIV or SVG
INNER.append(TEMPLATE.innerHTML); // show template HTML as text
setTimeout(() => { // wait till DOM is fully updated
STYLE = id.querySelector("style"); // get style tag in DIV or SVG
DISABLEDSTATE.append(STYLE.constructor.name,' - disabled state: ',STYLE.disabled);
})
}
function toggle(){
STYLE.disabled = !STYLE.disabled;
DISABLEDSTATE.innerHTML =``;
DISABLEDSTATE.append(STYLE.constructor.name,' - disabled state: ',STYLE.disabled);
}
</script>
<hr>
<button onclick="inject(DIV)">inject Template in DIV</button>
<button onclick="toggle()">toggle STYLE disabled state</button>
<button onclick="inject(SVG)">inject Template in SVG</button>
<hr><b>Injected HTML:</b><div id=INNER></div>
<b>Style disabled state:</b><div id=DISABLEDSTATE></div>
Both elements support the media property, so you can use the media="not all" hack found here: https://stackoverflow.com/a/59572781/1869660
Edit: Looks like SVGStyleElement.media is read-only in Chrome, so you need to start with a normal (enabled) style sheet and use both techniques:
<template id=TEMPLATE>
<style>
body { background: red; }
circle { fill:green; }
</style>
<circle cx="50%" cy="50%" r="45%"></circle>
</template>
function toggle() {
if ((STYLE.media === 'all') || !STYLE.media) {
STYLE.media = 'not all'; //Firefox
STYLE.disabled = true; //Chrome
}
else {
STYLE.media = 'all'; //Firefox
STYLE.disabled = false; //Chrome
}
DISABLEDSTATE.innerHTML = ``;
DISABLEDSTATE.append(STYLE.constructor.name, ' state: ', STYLE.media);
}
https://jsfiddle.net/mvb9fkay/
Chrome has implemented disabled on SVGStyleElement for some time now.
The SVG 2 specification says this about the style element
SVG 2 ‘style’ element shall be aligned with the HTML5 ‘style’ element.
In recognition of that the SVGStyleElement has recently had a disabled property added to it.
Additionally I've landed a patch to Firefox that implements disabled on SVGStyleElements. That should be available from Firefox 104 but you can already try it out in nightlies if you wish.

svg convert to canvas - can't generate multi pages pdf

I have 12 graphs and I want to generate pdf with 2 pages each page has 6 graphs.
However, when I convert svg to canvas, then the jspdf can only see part of both sub-dives.
$('#downloadx2').click(function() {
var svgElements = $("#body_id").find('svg');
//replace all svgs with a temp canvas
svgElements.each(function() {
var canvas, xml;
// canvg doesn't cope very well with em font sizes so find the calculated size in pixels and replace it in the element.
$.each($(this).find('[style*=em]'), function(index, el) {
$(this).css('font-size', getStylex(el, 'font-size'));
});
canvas = document.createElement("canvas");
canvas.className = "screenShotTempCanvas";
//convert SVG into a XML string
xml = (new XMLSerializer()).serializeToString(this);
// Removing the name space as IE throws an error
xml = xml.replace(/xmlns=\"http:\/\/www\.w3\.org\/2000\/svg\"/, '');
//draw the SVG onto a canvas
canvg(canvas, xml);
$(canvas).insertAfter(this);
//hide the SVG element
////this.className = "tempHide";
$(this).attr('class', 'tempHide');
$(this).hide();
});
var doc = new jsPDF("p", "mm");
var width = doc.internal.pageSize.width;
var height = doc.internal.pageSize.height;
html2canvas($("#div_pdf1"), {
onrendered: function(canvas) {
var imgData = canvas.toDataURL(
'image/png', 0.1);
doc.addImage(imgData, 'PNG', 5, 0, width, height/2,'','FAST');
doc.addPage();
}
});
html2canvas($("#div_pdf2"), {
onrendered: function(canvas2) {
var imgData2 = canvas2.toDataURL(
'image/png', 0.1);
doc.addImage(imgData2, 'PNG', 5, 0, width, height/2,'','FAST');
doc.save('.pdf');
}
});
});
<body id="body_id">
<div id="div_pdf1" >
<svg></svg>
<svg></svg>
<svg></svg>
</div>
<div id="div_pdf1" >
<svg></svg>
<svg></svg>
<svg></svg>
</div>
</body>
When I run this code, the generated pdf will view two pages with same canvas the first one (div_pdf1) div. So how to get both of them appearing in pdf as two pages.
You seem to be trying to run 2 parts in sequence but that's not how javascript works and actually runs your code.
No big deal, just a small misunderstanding between your mental model and the engine that executes the code.
A quick temporary debugging tool to see what's going on and verify that there is a discrepancy is to add console.log to key points and check the sequence of their printout once you run the code.
console.log('[1] just before: svgElements.each');
svgElements.each(function() {
console.log('[2] just after: svgElements.each');
And also around this part of the code:
console.log('[3] just before html2canvas-div_pdf1');
html2canvas($("#div_pdf1"), {
console.log('[4] just after html2canvas-div_pdf1');
Finally around this part of the code:
console.log('[5] just before html2canvas-div_pdf2');
html2canvas($("#div_pdf2"), {
console.log('[6] just after html2canvas-div_pdf2');
I suspect you'll see the code doesn't print the log lines in the order you think they will.
Next, you can try wrapping the 2 calls to html2canvas with one setTimeout function and force a delay in the execution of that code by an arbitrary amount of milliseconds.
Note that this is not the recommended final production quality solution but it will make the code output what you want.

Create an interactive SVG component using React

Let's say I have an SVG element with paths for all US states.
<svg>
<g id="nh">
<title>New Hampshire</title>
<path d="m 880.79902,142.42476 0.869,-1.0765 1.09022,..." id="NH" class="state nh" />
</g>
...
</svg>
The SVG data is saved in a separate file with a .svg extension. Say I want to create a React component of that map, with complete control over it so that I can modify the styling of individual states based on some external input.
Using Webpack, as far as I can tell, I have two options for loading the SVG markup: Insert it as raw markup using the raw-loader and create a component using dangerouslySetInnerHTML:
var InlineSvg = React.createClass({
render() {
var svg = require('./' + this.props.name + '.svg');
return <div dangerouslySetInnerHTML={{__html: svg}}></div>;
}
});
or manually convert the markup to valid JSX:
var NewComponent = React.createClass({
render: function() {
return (
<svg>
<g id="nh">
<title>New Hampshire</title>
<path d="m 880.79902,142.42476 0.869,-1.0765 1.09022,..." id="NH" className="state nh" />
</g>
...
</svg>
);
});
Finally, let's say that in addition to the SVG map, there's a simple HTML list of all the states. Whenever a user hovers over a list item, the corresponding SVG path should shift fill color.
Now, what I can't seem to figure out is how to update the React SVG component to reflect the hovered state. Sure, I can reach out into the DOM, select the SVG state by classname and change its color, but that doesn't seem to be the "react" way to do it. A pointing finger would be much appreciated.
PS. I'm using Redux to handle all communication between components.
You need to do two things:
1) Set an event listener on each list item to inform your app of the highlighted item.
<li
onMouseOver={() => this.handleHover('NH')}
onMouseOut={() => this.handleUnhover()}
>
New Hampshire
</li>
2) Capture the data, and propagate it your SVG component.
This is the more complicated part, and it comes down to how you've structured your app.
If your entire app is a single React component, then handleHover would simply update the component state
If your app is divided into multiple components, then handleHover would trigger a callback passed in as a prop
Let's assume the latter. The component methods might look like this:
handleHover(territory) {
this.props.onHighlight(territory);
}
handleUnhover() {
this.props.onHighlight(null);
}
Assuming you have a parent component, which contains both the SVG map and the list, it might look something like this:
class MapWrapper extends React.Component {
constructor(props) {
super(props);
this.state = {
highlighted: null;
};
}
setHighlight(territory) {
this.setState({
highlighted: territory
});
}
render() {
const highlighted = { this.state };
return (
<div>
<MapDiagram highlighted={highlighted} />
<TerritoryList onHighlight={(terr) => this.setHighlight(terr)} />
</div>
);
}
}
The key here is the highlighted state variable. Every time a new hover event occurs, highlighted changes in value. This change triggers a re-render, and the new value is passed onto MapDiagram which can then determine which part of the SVG to highlight.

SVG + Angular2 code sample does not work with interpolation

My goal is to convert code from Angular 1.3 to Angular 2 (with SVG in both cases).
I tried the following simple test code, which works in case #1 that does not involve interpolation, but does not work in case #2 (which uses interpolation), and AFAICS the only difference in the generated SVG code is the inclusion of an extra attribute in the element: class="ng-binding"
Is there a way to suppress the preceding class attribute, or is there another solution?
Btw I wasn't able to get the formatting quite right (my apologies).
Contents of HTML Web page:
<html>
<head>
<title>SVG and Angular2</title>
<script src="quickstart/dist/es6-shim.js"></script>
</head>
<body>
<!-- The app component created in svg1.es6 -->
<my-svg></my-svg>
<script>
// Rewrite the paths to load the files
System.paths = {
'angular2/*':'/quickstart/angular2/*.js', // Angular
'rtts_assert/*': '/quickstart/rtts_assert/*.js', // Runtime assertions
'svg': 'svg1.es6' // The my-svg component
};
System.import('svg');
</script>
</body>
</html>
Contents of the JS file:
import {Component, Template, bootstrap} from 'angular2/angular2';
#Component({
selector: 'my-svg'
})
#Template({
//case 1 works:
inline: '<svg><ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse></svg>'
//case 2 does not work:
//inline: "<svg>{{graphics}}</svg>"
})
class MyAppComponent {
constructor() {
this.graphics = this.getGraphics();
}
getGraphics() {
// return an array of SVG elements (eventually...)
var ell1 =
'<ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse>';
return ell1;
}
}
bootstrap(MyAppComponent);
SVG elements do not use the same namespace as HTML elements. When you insert SVG elements into the DOM, they need to be inserted with the correct SVG namespace.
Case 1 works because you are inserting the whole SVG, including the <svg> tags, into the HTML. The browser will automatically use the right namespace because it sees the <svg> tag and knows what to do.
Case 2 doesn't work because you are just inserting an <ellipse> tag and the browser doesn't realise it is supposed be created with the svg namespace.
If you inspect both SVGs with the browser's DOM inspector, and look at the <ellipse> tag's namespace property, you should see the difference.
You can use outerHtml of an HTML element like:
#Component({
selector: 'my-app',
template: `
<!--
<svg xmlns='http://www.w3.org/2000/svg'><ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse></svg>
-->
<span [outerHTML]="graphics"></span>`
})
export class App {
constructor() {
this.graphics = this.getGraphics();
}
getGraphics() {
// return an array of SVG elements (eventually...)
var ell1 =
'<svg><ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse></svg>';
return ell1;
}
}
note that the added string has to contain the <svg>...</svg>
See also How can I add a SVG graphic dynamically using javascript or jquery?

Resources