Incorrect path rendering in react-native-svg - javascript

Using Inkscape I created an svg file from png, then I tried to use its path's d (which was generated by Inkscape) in react-native-svg, but I failed. This is my code, but it renders only rectanagle with given fill color:
import React from 'react';
import styled from 'styled-components/native';
import { withTheme } from 'styled-components';
import Svg, { Path } from 'react-native-svg';
const StyledPart = styled.View`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.5;
`;
const StyledWaveBackground = styled.View`
width: 100%;
height: 100%;
position: relative;
`;
const Part = ({ width, height, fill, d }) => (
<StyledPart>
<Svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
<Path d={d} fill={fill} />
</Svg>
</StyledPart>
);
const WaveBackground = ({ width, height, theme }) => (
<StyledWaveBackground>
<Part
width={width}
height={height}
fill={theme.color.primary}
d="M 203.75,239.89934 C 142.14982,233.15571 74.42389,213.31632 19.88109,186.03745 L 0,176.09419 0,88.047087 0,0 256.25,0 512.5,0 l 0,87.952377 0,87.952383 -8.212,0.84135 c -19.97561,2.04655 -50.95386,8.64648 -74.52537,15.87764 -8.7243,2.6764 -30.4419,10.7209 -48.26133,17.87666 -49.04169,19.6937 -70.93005,25.94725 -103.01454,29.43144 -13.91836,1.51146 -60.81961,1.49106 -74.73676,-0.0325 z"
/>
<Part
width={width}
height={height}
fill={theme.color.primary}
d="M 120,254.17154 C 81.92867,250.44385 47.75997,241.17995 15.9375,225.95786 L 0,218.33426 0,109.16713 0,0 256.875,0 513.75,0 l 0,106.73949 0,106.73949 -2.82013,0.70781 c -1.55107,0.38928 -11.53545,1.66099 -22.1875,2.82599 -28.98642,3.17021 -64.58588,2.85335 -96.24237,-0.85663 -21.29589,-2.49576 -31.64353,-3.00549 -61.25,-3.01714 -32.18921,-0.0126 -36.98349,0.26131 -49.71091,2.84071 -14.16663,2.87109 -33.00602,9.07774 -66.53909,21.92134 -23.25836,8.90826 -36.6669,12.75144 -52.89669,15.16133 -11.77822,1.7489 -30.52554,2.24276 -42.10331,1.10915 z"
/>
</StyledWaveBackground>
);
export default withTheme(WaveBackground);
This is what it renders as a result (just colored recatngle)
current render result
But it should be like this (waved background)
correct render result
PS: I'm testing on real device(not emulator) Meizu M3E, if it is important

the most important part - the values for width and height you use and the resulting viewBox - are missing. This looks ok:
<svg width="200" height="100" viewBox="0 0 600 300">
<path fill="green"
d="M 203.75,239.89934 C 142.14982,233.15571 74.42389,213.31632 19.88109,186.03745 L 0,176.09419 0,88.047087 0,0 256.25,0 512.5,0 l 0,87.952377 0,87.952383 -8.212,0.84135 c -19.97561,2.04655 -50.95386,8.64648 -74.52537,15.87764 -8.7243,2.6764 -30.4419,10.7209 -48.26133,17.87666 -49.04169,19.6937 -70.93005,25.94725 -103.01454,29.43144 -13.91836,1.51146 -60.81961,1.49106 -74.73676,-0.0325 z"
/>
</svg>

Related

How do I get SVG textPath to work with animated path?

Please view the demo here: https://codepen.io/bsoutendijk/pen/gOvLMaq
Expected behavior: After clicking "update path" button, the path will change, and the text will appear along the path by the end of the animation.
Actual behavior: The text appears correctly on the path upon load, but when clicking update path, the text does not appear in the correct place.
code:
const path = document.getElementById('myPath');
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('clicked!');
path.setAttribute('d', getRandomCurve());
})
function getRandomCurve() {
const curveSize = Math.round(Math.random() * 300);
return `M 100 350 q ${curveSize} -300 300 0`
}
#myPath {
transition: all ease-out 2s;
}
<div>
<button id="myButton">Update Path</button>
<svg height="400" width="450">
<path id="myPath" d="M 100 350 q 150 -300 300 0" stroke="blue" stroke-width="5" fill="none" />
<text style="font-size: 2em;">
<textPath startOffset="30%" text-anchor="middle" href="#myPath">
Hello World
</textPath>
</text>
</svg>
</div>
How do I use textPath in a way that it can work with animated paths?
const path = document.getElementById("myPath2");
function getRandomCurve() {
const curveSize = Math.round(Math.random() * 300);
return `M 100 350 q ${curveSize} -300 300 0`;
}
setInterval(() => {
const pathDef = getRandomCurve();
path.setAttribute("d", pathDef);
}, 1000)
.anim {
transition: 1s;
}
<div>
<svg height="400" width="450">
<path id="myPath2" class="anim" d="M 100 350 q 150 -300 300 0" stroke="blue" stroke-width="5" fill="none" />
<text x="0" y="0" style="font-size: 2em;">
<textPath startOffset="30%" href="#myPath2" class="anim">
Hello World
<animateTransform attributeName="transform" dur="2s" repeatCount="indefinite"/>
</textPath>
</text>
</svg>
</div>
Oh, i can do it :)
"animateTransform" can fix your problem.
"repeatCount" - important attribute, try to remove it and your animation will stop after "dur"

React Native SVG Image not loading

I created an svg. And want to re-create it in React-Native. I use this npm package for it (https://www.npmjs.com/package/react-native-svg#image).
Here is the SVG
What i created is this
<TestApp
imageWidth={2300}
imageHeight={1438}
width={width}
url={'./url/to/picture'}
scale1={0.00100794}
scale2={0.000666667}
/>
const TestApp = ({
width,
overlayColor = 'black',
overlayOpacity = '0.3',
translate1,
translate2,
scale1,
scale2,
imageWidth,
imageHeight,
url,
}) => {
const height = (254 / 202) * width;
const translate = translate1
? `translate(${translate1} ${translate2 ? translate2 : ''})`
: '';
const scale = scale1 ? `scale(${scale1} ${scale2 ? scale2 : ''})` : '';
return (
<Svg
width={width}
height={height}
viewBox={`0 0 ${202} ${254}`}
fill="none"
>
<Mask
id="breathMask"
mask-type="alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width={width}
height={height}
>
<Path
d="M0 0H201.374V228.761C201.374 228.761 158.683 254 100.687 254C42.6913 254 0 228.761 0 228.761V0Z"
fill="#C4C4C4"
/>
</Mask>
<G mask="url(#breathMask)">
<Rect
x="1"
width={width}
height={(303 / 254) * height}
fill="url(#pattern0)"
/>
<Path
d="M0 0H201.374V228.761C201.374 228.761 158.683 254 100.687 254C42.6913 254 0 228.761 0 228.761V0Z"
fill={overlayColor}
fillOpacity={overlayOpacity}
/>
</G>
<Defs>
<Pattern
id="pattern0"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<Use href="#image0" transform={`${translate} ${scale}`} />
</Pattern>
<Image
id="image0"
widht={imageWidth}
height={imageHeight}
href="../path/to/url"
/>
</Defs>
</Svg>
);
};
Basically I copied the svg as it is.
The problem, the picture is not loading, even If I put a public url, such as this in it.
The svg with a grey background is not rendering. The picture will render in webview, though but not in ios or android.
You got any ideas how to fix it? Or a complete other way to achieve something like this?
Probably easiest way would be to just create a curved picture in photoshop, but thats not really dynamic.
Have you tried the using other parameters?
<Image
style={{ width: imageWidth, height: imageHeight }}
source={{
uri: '../path/to/url',
}}
/>

What should I use to show live icons in React OR Javascript? Like how much coffee is filled in a mug?

I want to create dashboard with lots of live icons. Live icons means icons content will change dynamically.
For example:- Let say i have an icon of a coffee cup, based upon number of coffee sold the coffee level in cup goes up.
I tried solving it using SVG & CSS. Below is the live code-sandbox DEMO example. (same code I am posting below)
https://codesandbox.io/s/fill-coffee-to-svg-1sfb0
what exactly I am doing is on top of SVG component i using div node and applying css on the div tag.
Issue is that, It is very difficult to manage path/points dynamically in SVG, also for different screen version it will be difficult.
Can anyone suggest any better approach for this.
import ReactDOM from "react-dom";
import React, { Component } from "react";
class Todo extends Component {
state = { height: 4 };
setHeight = (e, fillType) => {
e.preventDefault();
console.log(this.state.height);
if (this.state.height < 100) {
if (fillType === "fill") {
this.setState({
height: this.state.height + 2
});
}
}
if (this.state.height >= 2) {
if (fillType === "drink") {
this.setState({
height: this.state.height - 2
});
}
}
};
render() {
return (
<div>
<svg width="255" height="224" xmlns="http://www.w3.org/2000/svg">
<g>
<rect
fill="#fff"
id="canvas_background"
height="226"
width="257"
y="-1"
x="-1"
/>
<g
display="none"
overflow="visible"
y="0"
x="0"
height="100%"
width="100%"
id="canvasGrid"
>
<rect
fill="url(#gridpattern)"
stroke-width="0"
y="0"
x="0"
height="100%"
width="100%"
/>
</g>
</g>
<g>
<title>Layer 1</title>
<path
stroke="#000"
id="svg_1"
d="m78.82963,176.75921l97.93778,0c14.11708,-0.03733 23.74788,-17.00704 23.70086,-34.46505l0,-11.73873c27.94999,-0.03136 48.22814,-30.02253 48.21769,-64.99381c0.01045,-35.59398 -19.86965,-64.83701 -43.00946,-64.81162l-150.95938,0l0,141.54714c0.02194,19.22158 11.60543,34.42772 24.1125,34.46207zm121.63551,-149.38391l0,0l5.20823,0c19.14875,-0.04331 25.29102,25.83983 25.19908,38.38045c0.01881,20.24897 -10.47393,39.66916 -30.40731,37.78463l0,-76.16508zm-199.71514,158.00316c0.01776,26.16387 13.9729,38.29683 25.20535,38.37149l202.59351,0c13.39827,-0.07466 25.14161,-15.13147 25.20117,-38.37149l-253.00002,0z"
stroke-width="1.5"
fill="#fff"
/>
</g>
</svg>
<div
style={{
position: "absolute",
left: "63px",
top: "9px",
height: "175px",
width: "145px",
borderBottomLeftRadius: "25px",
borderBottomRightRadius: "25px",
overflow: "auto"
}}
>
<div
style={{
height: this.state.height + "%",
width: "100%",
position: "absolute",
bottom: "0",
zIndex: 1000,
background: "green",
transition: "all .4s"
}}
/>
</div>
<button onClick={e => this.setHeight(e, "fill")}>
fill more cofee +
</button>
<button onClick={e => this.setHeight(e, "drink")}>drink cofee -</button>
</div>
);
}
}
ReactDOM.render(<Todo />, document.getElementById("root"));
Have you tried using the react-spring library?
In the basics section of react-spring documentation (can be found here - https://www.react-spring.io/docs/hooks/basics) this code snippet is given:
const props = useSpring({ x: 100, from: { x: 0 } })
return (
<animated.svg strokeDashoffset={props.x}>
<path d="..." />
</animated.svg>
)
Using your example of your coffee mug, if you would render your mug separate to the coffee filling the mug, with the coffee stroke being upwards, then you could use this technique to animate the coffee height.
Edit:
As you are using a class component you will want to use the render props API for this - https://www.react-spring.io/docs/props/spring
<Spring
from={{ x: 100 }}
to={{ x: 0 }}>
{props => (
<svg strokeDashoffset={props.x}>
<path d="..." />
</svg>
)}
</Spring>

How do I get an internal SVG element's position relative to the viewport of an outer SVG element?

Say I have an SVG element containing some stuff:
<div style="margin-left:50px; width: 100%; min-height: 400px;">
<svg>
<g transform="translate(34.34,47.5) scale(0.345)" height="100%" width="100%">
<svg x="20" y ="50" style="overflow: visible">
<circle cx="0" cy="0" r="35" stroke="red" fill="blue">
<text>a bunch of text</text>
</svg>
<line />
</g>
<svg>
<div>
I'm trying to find the center position of the <g> relative to the viewport of the outer <svg> element, so that I can translate the <g> to be centered within the outer <svg>, and scale it to fit.
I was able to get it working using getBoundingClientRect() and adjusting for the transform scale, but this does not work in Firefox because the <svg> elements inside the <g> container are not constrained to the bounding box of the displayed section of their contents (rather it's the same size as the outer <svg>, with some scaling).
There is probably a solution using createSVGPoint() and getScreenCTM() or getCTM() but frankly I'm not sure what I should be using.
An SVG without a viewBox attribute has a width of 300px and a height of 150px. I've added a viewBox="0 0 300 150". You can remove it.
Also I've added a rectangle to be able to see the position and the size of the <g> element. You can remove it as well.
How I would center the <g> element: Since the <g> element is transformed the easiest way to get it's size and position would be wrapping the <g> element in another one, in this case <g id="wrap"> Next I can get the bounding box of the wrap: wrap.getBBox()
In order to center the wrap I need to know the center of the main svg canvas: x = 300/2; y=150/2. Now I can translate the wrap into the center
let c = {x:150,y:75}//the center of the main svg element
let bb = wrap.getBBox()//the bounding box of the wrap
let transformation = `translate(${c.x - bb.x - bb.width/2},
${c.y - bb.y - bb.height/2})`
wrap.setAttributeNS(null,"transform",transformation)
svg{border:1px solid;width:100vh;}
text{fill:black;}
path{fill:none;stroke:black}
<div style="margin-left:50px; width: 100%; min-height: 400px;">
<svg id="main" viewBox="0 0 300 150" >
<g id="wrap">
<rect x="29.165" y="47.5" width="45.03" height="29.325" fill="gold" fill-opacity=".5" />
<g transform="translate(34.34,47.5) scale(0.345)" height="100%" width="100%">
<svg x="20" y ="50" style="overflow: visible">
<circle cx="0" cy="0" r="35" stroke="red" fill="blue"/>
<text>a bunch of text</text>
</svg>
<line />
</g>
</g>
<path d="M0,0L300,150M0,150L300,0" />
<svg>
<div>
I hope this is what you were asking.
I managed to figure out a solution using one of the d3.zoom transform methods (we're using d3.zoom to manage the translate/scale transform), and SVGElement.getBBox(). I originally was using this method but had messed up the calculation; this way it works though.
const selection = d3.select(group);
const zoomBehavior = d3.zoom().on('zoom', () => {
selectionTransform = d3.event.transform;
});
selection.call(zoomBehavior);
const scaleAndTransformTo = () => {
selection.call(zoomBehavior.translateBy, Math.random() * 100, Math.random() * 150);
group.setAttribute("transform", selectionTransform.toString());
}
scaleAndTransformTo();
reset.addEventListener('click', scaleAndTransformTo);
run.addEventListener('click', () => {
const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect();
const containerCenter = [containerWidth / 2, containerHeight / 2];
const { height, width, x, y } = group.getBBox();
const nodeBBoxCenter = [x + (width / 2), y + (height / 2)];
// Apply the current interpolated translate/scale to the BBox center to get the actual position
const groupCenterCoords = selectionTransform.apply(nodeBBoxCenter);
const translationOffset = [
(containerCenter[0] - groupCenterCoords[0]) / selectionTransform.k,
(containerCenter[1] - groupCenterCoords[1]) / selectionTransform.k,
];
selection.call(zoomBehavior.translateBy, ...translationOffset);
group.setAttribute("transform", selectionTransform.toString());
});
#page {
display: flex;
flex-direction: column;
position: relative;
align-items: stretch;
margin-left: 100px;
}
#container {
background-color: grey;
flex-grow: 1;
flex-shrink: 0;
min-height: 500px;
border: 1px solid red;
}
#group > svg {
overflow: visible;
}
#group > svg > circle {
overflow: visible;
}
text {
fill: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="page">
<div>
<button id="run">Run</button>
<button id="reset">Reset</button>
</div>
<svg id="container">
<g x="0" y="0" id="group" width="100%" height="100%">
<line x1="20" y1="50" x2="150" y2="150" stroke="brown" />
<svg x="20" y ="50">
<circle cx="0" cy="0" r="35" stroke="red" fill="blue">
<text x="35" y="0" height="100%" width="100%">a bunch of text</text>
</svg>
<line x1="100" y1="350" x2="160" y2="340" stroke="brown" />
<svg x="100" y ="350">
<circle cx="0" cy="0" r="35" stroke="red" fill="blue">
<text x="35" y="0" height="100%" width="100%">a bunch of text 3</text>
</svg>
</g>
<svg>
<div>

How can I animate the tracing a SVG rectangle using JavaScript?

For a game I'm working on,
I would like to be able to draw a SVG rectangle; using a percentage value (50% would draw half the rectangle stroke).
I need to do this in Javascript since I'll update the value quite often.
<svg id="rectangle-timer" style="width:100%;height:100%;">
<rect width="100%" height="100%"/>
</svg>
I saw quite nice JS libraries like drawSVG or Vivus, but it seems that they work with paths, not with basic shapes like rectangles.
Can anyone help ?
Thanks.
The reason most libraries will use path elements is because of their inheritance from the SVGGeometryElement prototype, which gives you handy functions for computing the path length. So if we swap out this rectangle for a path like this:
<path d="M 0 0 L 1 0 L 1 1 L 0 1 z" />
We get exactly the same output, but its much more controllable. After that, we can just adjust the strokeDasharray value in the style to extend and remove some stroke. For this property we just need two values: initial dash size and initial empty space. So when our progress is 0, we want the first value to be 0 and the second to be the path length , and as we approach 1 we want the second value to 0 and the first one to increase to the path length.
function update( amount ){
const total = rect.getTotalLength();
const filled = total * amount;
const none = total - filled;
rect.style.strokeDasharray = `${filled} ${none}`;
}
const rect = document.getElementById( 'rectangle' );
const input = document.getElementById( 'input' );
input.addEventListener( 'mousemove', event => update( input.value ));
update( input.value );
<svg width="200px" height="200px" viewBox="0 0 200 200">
<path d="M 20 20 L 180 20 L 180 180 L 20 180 z" id="rectangle" fill="none" stroke="black" stroke-width="10" />
</svg>
<input id="input" type="range" min="0" max="1" step=".01" />
If you insist on using a rect, you could get a rectangle's path length by taking its width and height twice, which would look something like this:
function update( amount ){
const total = rect.width.baseVal.value * 2 + rect.height.baseVal.value * 2;
const filled = total * amount;
const none = total - filled;
rect.style.strokeDasharray = `${filled} ${none}`;
}
const rect = document.getElementById( 'rectangle' );
const input = document.getElementById( 'input' );
input.addEventListener( 'mousemove', event => update( input.value ));
update( input.value );
<svg width="200px" height="200px" viewBox="0 0 200 200">
<rect x="20" y="20" width="160" height="160" id="rectangle" fill="none" stroke="black" stroke-width="10" />
</svg>
<input id="input" type="range" min="0" max="1" step=".01" />
In the long run, however, this would mean less versatility, so I would suggest switching to path.
This is my solution: The SVG has preserveAspectRatio ="none" style="width:100%;height:100vh;" The total length of the path is 2*window.innerWidth + 2*window.innerHeight; Both stroke-dasharray and stroke-dashoffset are igual to the total length of the path.
I'm using an input type="range" to animate the stroke-dashoffset.
In order to preserve the stroke width and avoid stretching I'm using vector-effect="non-scaling-stroke"
I hope this is what you need.
function timer(){
let totalLength = 2*window.innerWidth + 2*window.innerHeight;
thePath.setAttributeNS(null, "style", `stroke-dashoffset:${totalLength * (1-range.value)}`)
}
timer()
range.addEventListener("input",timer);
setTimeout(function() {
timer()
addEventListener('resize', timer, false);
}, 15);
*{margin:0; padding:0;}
#thePath {
stroke-dasharray: calc(2 * 100vw + 2* 100vh);
stroke-dashoffset: calc(2 * 100vw + 2* 100vh);
}
#rectangle-timer{background:#dfdfdf}
[type="range"] {
position: absolute;
display: block;
width: 200px;
height: 20px;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
<svg id="rectangle-timer" viewBox="0 0 100 100" preserveAspectRatio ="none" style="width:100%;height:100vh;">
<path id="thePath" d="M0,0L100,0 100,100 0,100 0,0" fill="none" stroke="skyBlue" stroke-width="25" vector-effect="non-scaling-stroke" />
</svg>
<input type="range" id="range" value=".5" min="0" max="1" step=".01" />

Categories

Resources