I am trying to implement barcode scanner viewFinder and I want to use svg icon to make it look nice, but I have a problem with forcing the path element inside the svg to take up the full svg width and height. I am using react native and to generate icon i use SVGR https://react-svgr.com/playground/?native=true&typescript=true in the scan handler I set the dimensions of the svg like so:
const handleBarCodeScanned = ({ type, data, bounds }: BarCodeEvent) => {
if (!bounds) return;
const { origin, size } = bounds;
setX(origin.x);
setY(origin.y);
setWidth(size.width);
setHeight(size.height);
};
and the I ise them inside the svg which looks like so
import * as React from "react";
import Svg, { SvgProps, Path } from "react-native-svg";
export const ViewFinder = (props: SvgProps & { top: number; left: number }) => {
const { width, height, top, left } = props;
return (
<Svg
width={width}
height={height}
style={{
borderColor: "green",
borderWidth: 2,
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
}}
fill="none"
stroke="green"
preserveAspectRatio="none"
viewBox={`0 0 ${width} ${height}`}
>
<Path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></Path>
<Path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></Path>
</Svg>
);
};
original icon is a featerIcons crop icon https://feathericons.com/ and the original code of the icon is:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-crop"><path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></path><path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></path></svg>
as you can see I set the border color and borderWidth on the svg itself, and it scales to fit the container so here everything seems to be ok. I have viewBox and preserveAspectRatio set up its just the inner path not scaling with the svg, and it is not just this icon I have tries several and the issue is still this same so there must be something wrong with my understanding of svg.
Thanks a lot for any help.
Normally a viewBox would be 4 fixed numbers, i.e. unrelated to width and height. That should give you the result you want.
Your content doesn't change in size so your viewBox shouldn't change either.
Related
Helo I have a problem. I am using npm package which provides pack of weather icons. The problem is the SVG box is big and it breaks positioning that is a lot of white space on website, it does not look nice. Is negative margin the way>
import {wiCloudy} from 'weather-icons-react'
const WeatherDataBox = () => {
return (
icon=<WiCloudy size={256} />
)}
<svg stroke="currentColor" fill="currentColor"
stroke-width="0" viewBox="0 0 30 30"
attr="[object Object]" size="256" height="256" width="256">
<path>*long code*</path>
</svg>
I changed viewbox and it reduced empty background
let ICON_SIZE = 256
let VIEWBOX = "5 5 20 20"
<WiCloudy viewBox={VIEWBOX} size={ICON_SIZE} />
I've got a "link" icon that I am using to explore SVG animation using Framer.
I'm animating the path from 0 initially, to 1 once animated, which does what I want it to; it animates from the start to the end and then from the end back to the start.
Next, I want to try and see what it would look like if I then animated the icon disappearing from the start to the end. This would work in a similar fashion to how it currently works, except it would make the path disappear from the start rather than the end.
So far, I have this:
export default function App() {
const transition = {
duration: 2,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut"
};
return (
<svg
height="256"
width="256"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<motion.path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={transition}
/>
</svg>
);
}
And a CodeSandbox link; https://codesandbox.io/s/boring-burnell-kc6gt
I'm having trouble figuring out what I need to change to get the path to animate out from the start to the end.
You can animate pathOffset on your motion path from 0 to 1 to animate out from the start to the end.
But you'll need some way to switch between the two animations. So you'd need to orchestrate the animations with Animation Controls, or switch the animation prop based on some other event in your code.
Here's an example that animates in as you have it, then animates out with pathOffset when the icon is clicked:
https://codesandbox.io/s/interesting-wu-lbocl?file=/src/App.js
I have an SVG exported from Adobe Illustrator with several paths like this, which produces a small polygon I intend to use as a text box
<svg viewbox="387 390 74 20">
<g>
<path class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0 C460,404.42,456.42,408,452,408z" />
</g>
</svg>
I'd like to dynamically add text to it. I've seen many similar questions here, but most of them involed specifying a x and y property for a text element based on the x and y property the path element. My path, however, does not have such properties.
I've tried to use a textPath element with xlink:href pointing to my path. I gets attached to the path, but the text wraps my path element instead of being inside it.
So, is there a way to achieve this? I'm open to different solutions here. My dream would be to use javascript to get the path element and add the text from there. But I could also edit the base SVG file to add a text or any other relevant element and attributes to make this work, as long as I can change the text dynamically from javascript later. And since this SVG is produced by Illustrator, I could also try different export options there in order to get a proper output for my goal.
Here's some sample code that takes a label path and adds a <text> element after it with whatever text you choose.
let label1 = document.querySelector("#label1");
addLabelText(label1, "Something");
function addLabelText(bgPath, labelText)
{
let bbox = bgPath.getBBox();
let x = bbox.x + bbox.width / 2;
let y = bbox.y + bbox.height / 2;
// Create a <text> element
let textElem = document.createElementNS(bgPath.namespaceURI, "text");
textElem.setAttribute("x", x);
textElem.setAttribute("y", y);
// Centre text horizontally at x,y
textElem.setAttribute("text-anchor", "middle");
// Give it a class that will determine the text size, colour, etc
textElem.classList.add("label-text");
// Set the text
textElem.textContent = labelText;
// Add this text element directly after the label background path
bgPath.after(textElem);
}
.st37 {
fill: linen;
}
.label-text {
font-size: 10px;
fill: rebeccapurple;
transform: translate(0, 3px); /* adjust vertical position to centre text */
}
<svg viewbox="387 390 74 20">
<g>
<path id="label1" class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0 C460,404.42,456.42,408,452,408z" />
</g>
</svg>
Since you can edit your base SVG align
create a proper SVG to work with
Your path is a background label starting (red circle) at a large offset x=452 y=408
I would recreate it,
starting at the green circle, (editor: https://yqnn.github.io/svg-path-editor/)
in a viewBox="0 0 80 20"
To get single coordinates for both backgroundlabel and (blue) textPath line
after that use JavaScript to add text dynamically.
No need for text x,y calculations, pathLength and startoffset do the work
Or if you go fancy you can create the blue line dynamically from getBBox()
That will also work with your current path; just more calculations required
It is all about coordinates,
and positioning the blue line (with stroke="transparent" then):
playground:
<svg viewbox="387 390 74 20">
<path fill="lightgreen" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0C460,404.42,456.42,408,452,408z" />
<circle cx="452" cy="408" r="2" fill="red"/>
<circle cx="388" cy="400" r="2" fill="green"/>
<path id="P" pathLength="100" d="M388 400h72" stroke="blue"/>
<text>
<textPath href="#P" startoffset="50" text-anchor="middle" dominant-baseline="middle"
fill="green" font-size="14px">My Text</textPath>
</text>
</svg>
Thanks for the answers! I end up using a tweaked version of Paul LeBeau's function to take into account the structure suggested by Danny '365CSI' Engelman so I don't have to use translate to center the text vertically.
let label = document.querySelector("#mylabel");
addLabelTextPath(label, "Something");
function addLabelTextPath(bgPath, labelText) {
let bbox = bgPath.getBBox();
let x = bbox.x + bbox.width / 2;
let y = bbox.y + bbox.height / 2;
// Create the path line
let pathElem = document.createElementNS(bgPath.namespaceURI, "path");
pathElem.setAttribute("pathLength", 100);
pathElem.setAttribute("d", `M${bbox.x} ${y}h${bbox.width}`);
pathElem.id = `baseline-${bgPath.id}`;
// Create a <text> element
let textElem = document.createElementNS(bgPath.namespaceURI, "text");
// Create a <textPath> element
let textPath = document.createElementNS(bgPath.namespaceURI, "textPath");
textPath.setAttribute("href", `#${pathElem.id}`);
//Center text
textPath.setAttribute("dominant-baseline", "Middle");
textPath.setAttribute("startOffset", 50);
textPath.setAttribute("text-anchor", "middle");
// Give it a class that will determine the text size, colour, etc
textPath.classList.add("label-text");
// Set the text
textPath.textContent = labelText;
// Add the elements directly after the label background path
bgPath.after(pathElem);
pathElem.after(textElem);
textElem.appendChild(textPath);
}
.st37 {
fill: lightblue;
}
.label-text {
font-size: 10px;
fill: darkblue;
}
<svg viewbox="387 390 74 20">
<g>
<path id="mylabel" class="st37" d="M452,408h-56c-4.42,0-8-3.58-8-8l0,0c0-4.42,3.58-8,8-8h56c4.42,0,8,3.58,8,8l0,0 C460,404.42,456.42,408,452,408z" />
</g>
</svg>
I'm trying to place a text onto a path with svg.js
Here is my fiddle:
https://jsfiddle.net/Byteschmiede/ytz67egn/1/
var draw = SVG().addTo('body').size(500, 500)
draw.svg(`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="100%" height="100%" viewBox="0 0 4725 2363" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path id="text1326" d="M2362.2,952.109C2548.36,773.615 2727.44,622.128 2899.46,497.647C3071.47,370.823 3216.39,286.272 3334.21,243.993C3452.02,201.721 3580.45,180.583 3719.47,180.58C4011.66,180.583 4247.3,278.051 4426.39,472.986C4607.82,665.577 4698.54,899.267 4698.55,1174.06C4698.54,1361.95 4658.48,1535.75 4578.37,1695.46C4498.25,1855.16 4383.97,1976.12 4235.52,2058.32C4089.42,2140.53 3920.94,2181.63 3730.08,2181.62C3482.66,2181.63 3265.87,2128.78 3079.72,2023.09C2895.92,1917.4 2656.75,1717.77 2362.2,1424.19C2055.87,1727.16 1811.99,1929.15 1630.55,2030.14C1449.11,2131.13 1238.21,2181.63 997.866,2181.62C691.537,2181.63 452.365,2086.51 280.351,1896.27C110.692,1706.03 25.862,1465.29 25.863,1174.06C25.862,901.616 115.404,667.926 294.489,472.986C475.929,278.051 712.744,180.583 1004.94,180.58C1146.32,180.583 1275.92,201.721 1393.74,243.993C1511.55,286.272 1655.29,370.823 1824.95,497.647C1996.97,622.128 2176.05,773.615 2362.2,952.109M2591.95,1170.53C2841.72,1417.14 3046.73,1585.07 3206.96,1674.32C3369.55,1761.22 3530.96,1804.67 3691.2,1804.67C3891.49,1804.67 4048.18,1745.95 4161.29,1628.52C4274.4,1508.74 4330.95,1364.3 4330.95,1195.19C4330.95,1009.65 4274.4,856.992 4161.29,737.208C4050.54,615.082 3903.27,554.017 3719.47,554.014C3615.79,554.017 3515.64,572.806 3419.04,610.382C3322.42,645.614 3206.96,709.027 3072.65,800.622C2938.33,889.873 2778.1,1013.18 2591.95,1170.53M2132.46,1170.53C1962.8,1027.27 1810.81,909.836 1676.5,818.237C1542.19,724.294 1424.37,657.357 1323.05,617.428C1221.72,577.503 1110.97,557.54 990.797,557.537C818.78,557.54 676.22,617.43 563.115,737.208C450.009,856.992 393.456,1009.65 393.457,1195.19C393.456,1324.37 422.911,1433.58 481.82,1522.83C540.729,1612.08 612.598,1681.37 697.428,1730.68C784.613,1780.01 893.006,1804.67 1022.61,1804.67C1192.27,1804.67 1357.21,1760.05 1517.45,1670.8C1677.68,1581.55 1882.68,1414.79 2132.46,1170.53" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:1px;"/>
<path id="textCurve3" d="M294.489,472.986C475.929,278.051 712.744,180.583 1004.94,180.58C1146.32,180.583 1275.92,201.721 1393.74,243.993C1511.55,286.272 1655.29,370.823 1824.95,497.647" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:1px;"/>
<path id="textCurve2" d="M2899.46,497.647C3071.47,370.823 3216.39,286.272 3334.21,243.993C3452.02,201.721 3580.45,180.583 3719.47,180.58C4011.66,180.583 4247.3,278.051 4426.39,472.986" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:1px;"/>
<path id="textCurve1" d="M564.216,1310.73C609.732,1475.4 770.154,1619.22 909.116,1635.49C993.881,1645.42 1152.78,1644.6 1262.96,1605.82C1479.72,1529.53 1626.78,1408.31 1802.89,1258.49" style="fill:none;fill-rule:nonzero;stroke:rgb(177,0,52);stroke-width:1px;"/>
</svg>
`)
let textCurve1 = draw.find("#textCurve1")[0]
let textPath = textCurve1.text("Forever").font({
size: textCurve1.height()
})
.attr({
startOffset: '50%',
'text-anchor': 'middle'
})
.fill({
opacity: 0
}).stroke({
color: "#000",
width: 1
})
The red line is the target path.
The Text needs to follow the path.
As you can see, the text curve is correct but the placement is way to low on the y axis.
I'm not sure how to solve this, since there aren't much opportunities.
I tried attributes like dominant-baseline and baseline-alignment but nothing workend
Here is a picture of the svg
You can use a tspan with a dx attribute to move specific text up or down.
const text = draw.text((text) => {
text.tspan('Some Text').dx(10)
}).path(textCurve1)
First of all I looked at all possible related answers here but none of them seem to bring the answer I need so here I am.
Given a svg text path:
<svg viewBox="0 0 900 900"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="mysvg"
>
<defs>
<path id="myPath" d="M70 110 C 70 140, 110 140, 110 110" stroke="black" fill="transparent"/>
</defs>
<use xlink:href="#myPath" fill="none" stroke="red" />
<text id="names" font-family="Verdana" font-size="10" text-anchor="middle" >
<textPath xlink:href="#myPath" startOffset="50%">
My text is going to exceed at some point…
</textPath>
</text>
</svg>
At this point the text exceeds the textpath
I can't find a way to check for possible overflow through jquery. This command won't actually return undefined:
alert($("text#names").attr("textLength") );
I am trying to check for overflows in order to fit the text to the maximum length or so.
I had the same problem when adjusting font size so that the given text will be drawn with the largest possible font size without overflow. Its quite simple using plain JS.
1) Determine the np. of characters in the text element with a minimum font size:
textElement.css('font-size', 1);
var allCharCount = textElement[0].getNumberOfChars();
2) Then set font size to any value and determine the length again
var hasOverflow = allCharCount != textElement[0].getNumberOfChars();
getNumberOfChars() will only return the no. of chars drawn. If there is an overflow this number will be smaller then from the original whole string.
It looks like text.getNumberOfChars() has changed since the other answer was written, and now returns the total number of characters in the string, regardless of if they're rendered or not.
My approach to this problem is to:
Change the <textPath> element to draw on a much longer path, then calculate the text length using text.getComputedLength()
Change the <textPath> back to the original path and calculate length again
If the length on the original path is shorter than the length on the longer path, you know there's an overflow.
const textPath = document.querySelector('textPath');
const checkClipped = () => {
textPath.setAttribute('xlink:href', '#fullWidthPath');
const fullLength = textPath.getComputedTextLength();
textPath.setAttribute('xlink:href', '#myPath');
const curvedLength = textPath.getComputedTextLength();
return fullLength > curvedLength;
}
const findLongestString = () => {
const text = textPath.innerHTML;
if (checkClipped()) {
const newText = text.substring(0, text.length - 1);
textPath.innerHTML = newText;
return findLongestString(newText);
} else {
return text;
}
}
console.log(findLongestString())
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="mysvg">
<defs>
<path id="myPath" d="M70 110 C 70 140, 110 140, 110 110" stroke="black" fill="transparent"/>
<path id="fullWidthPath" d="M 0 0 L 0 10000" />
</defs>
<use xlink:href="#myPath" fill="none" stroke="red" />
<text id="names" font-family="Verdana" font-size="10" text-anchor="middle" >
<textPath xlink:href="#myPath" startOffset="50%">
My text is going to exceed at some point…
</textPath>
</text>
</svg>