I have a huge svg 3200*1800. I only want to show a part of that image something like 400*1000, ensuring that the width is the dominant attribute and having a scroll bar for the height but when I set viewbox it increase the width to display the added height.
viewBox="900 550 400 1000"
Is their a way to stop this happening?
I worked it out you need to increase the height relative to the viewbox for example I ended up with something like this:
width="1400"
height="4000"
viewBox="966 555 350 1000"
Compared to what I used to have:
width="350"
height="1000"
viewBox="966 555 350 1000"
You just set 'preserveAspectRatio' to "none" along with your 'viewBox' attribute, then your problem is solved.
This answer builds on Shane's answer (which does not cater to variable window sizes)...
To have width-dominant overflows:
Let the 'viewbox' define the portion of the graphic to display (any known aspect ratio)
Let the svg element have default width and height (100%)
With javascript, dynamically set the height of the svg element every time the window resizes
The code below works for my learning project and is NOT production code.
In the head element:
<script type="application/javascript">
var svgRatio = ${viewboxRatio}; // ratio must be known
// From http://stackoverflow.com/a/13651455
if(window.attachEvent) {
window.attachEvent('onresize', resizeSvg);
}
else if(window.addEventListener) {
window.addEventListener('resize', resizeSvg, true);
}
else {
//The browser does not support Javascript event binding
}
function resizeSvg() {
var height = window.innerWidth * svgRatio;
var svg = document.getElementsByTagName('svg')[0];
svg.setAttribute("height", height.toString());
}
</script>
At the end of the body:
<script type="application/javascript">
resizeSvg();
</script>
Related
I'm trying to create an SVG element with a width defined by a percentage of the parent and a fixed value, say 50% + 20px. For normal html elements, in the CSS you can use calc(50% + 20px). Is there an equivalent way to do this for embedded SVGs? Specifically, I'm using snap.svg, though I'm not sure if this capability exists with SVGs in general.
EDIT:
Tried setting <svg> width with percentages and px, which I couldn't get to work. I tried both:
<svg width='calc(50% + 20px)'>...</svg>
<svg width='50% + 20px'>...</svg>
I also tried setting it in CSS:
svg {
width: calc(50% + 20px);
}
It should be possible with the upcoming SVG2 as width etc. become geometry properties and then you can style them with calc
I want to put a rectangle around a text in SVG.
The height of the text is known to me (the font-size attribute of the text element). But the width is dependent on the actual content. Using getBBox() or getComputedTextLength() should work. But this only works after rendering.
Is there a way to specify that in an other way? For example defining the x and width attributes relative to other values? I didn't find anything like that in the SVG Spec.
Figuring where text ends presumably requires roughly the same underlying code path as the rendering itself implements - going through the width of each character based on the font and style, etc... As I am not aware the SVG standards define a method for directly getting this information without doing the actual full rendering, till such methods emerge or are reported here by others, the approach should be to render invisibly before doing the actual rendering.
You can do that in a hidden layer (z-index, opacity and stuff) or outside the visible viewport, whichever works best in experimentation. You just need to get the browser do the rendering to find out, so you render invisibly for that sake, then use getComputedTextLength()
I know this is old, but a few ideas:
If you can choose a mono-spaced font, you know your width by a simple constant multiplication with glyph count
If you are bound to proportional fonts, you can find an average glyph size, do the math as with mono-space, and leave enough padding. Alternatively you can fill the padding with text element textLength attribute. If the constant is chosen carefully, the results are not very displeasing.
EDIT: As matanster found it to be hacky
Predetermine glyph widths with getComputedTextLength() and build a lookup table. Downside is that it does not account for kerning, but if your cache size is not a problem, you can append glyph-pair widths to this lookup.
Going beyond that is to find some way to do server side rendering: Is there a way to perform server side rendering of SVG graphics using React?
It is possible using canvas with measureText():
// Get text width before rendering
const getTextWidth = (text, font) => {
const element = document.createElement('canvas');
const context = element.getContext('2d');
context.font = font;
return context.measureText(text).width;
}
// Demo
const font = '16px serif';
const text = 'My svg text';
const textWidth = getTextWidth(text, font);
document.body.innerHTML = `
<svg>
<text x="0" y="20" font="${font}">${text}</text>
<rect x="0" y="30" width="${textWidth}" height="4" fill="red" />
</svg>
`;
Adapted from https://stackoverflow.com/a/31305410/1657101
I am trying to place an SVG Text-element according to the width and height of the text by getting the bounding box using the getBBox() method.
If the text is using a websafe font, this works reasonably well across different browsers, but if the text is styled using #font-face and a custom webfont, then the width of the text is returned incorrectly in Firefox (Mac) and Safari (iOS).
It works perfectly in both Safari (Mac) and Chrome (Mac).
If the gray box has the same width as the text, then it works in that browser.
Does anybody have an idea on how to get the correct width of the text bounding box in all browsers?
The browser is calculating the bounding box before it has finished loading/applying #font-face, assuming you don't need IE, you can wrap your BBox calculation function inside a document.fonts.ready promise...
document.fonts.ready.then(() => const bbox = textEl.getBBox());
Here is an example at work that exhibits the problem and the fix:
const xmlns = "http://www.w3.org/2000/svg";
const correct = document.getElementById("correct");
const incorrect = document.getElementById("incorrect");
visualizeBBox(incorrect);
document.fonts.ready.then(()=> visualizeBBox(correct));
function visualizeBBox(el){
const bbox = el.getBBox();
const rect = document.createElementNS(xmlns, "rect");
for (prop in bbox) rect.setAttribute(prop, bbox[prop]);
document.querySelector("svg").appendChild(rect);
}
svg text {
font-family: 'Diplomata SC', serif;
}
svg rect {
stroke: red;
fill: none;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Diplomata+SC&display=swap" rel="stylesheet">
<svg xmlns="https://www.w3.org/2000/svg" width="600" height="400">
<text x="0" y="40" font-size="24" id="correct">Correct dimensions</text>
<text y="100" font-size="24" id="incorrect">Incorrect dimensions</text>
<svg>
Today I ran into similar issue. Duopixel is right that getBBox() might return momental metric which may be unexpected because external font hasn't been loaded yet and some standard font is used instead.
The problem in WebKit (tested in Chrome 24.0.1312.52 and 26.0.1389.0 canary) is that the browser defers external font loading until it is first effectively used anywhere on the page. So even if you wait for onreadystatechange to become "complete" you are not guaranteed to have font metrics ready when calling getBBox() - you may still be the first one rendering a text styled with external font, inserting it into the document and immediately calling getBBox() on it (my case).
My workaround instead of calling mySVGInitCode() directly I do:
$("body").append(
$("<div/>")
.attr("class", "force-external-font-loading")
.attr("style", "font-family: \"xkcd\";visibility:hidden;position:absolute")
.text("x")
);
setTimeout(function(){ mySVGInitCode() }, 100); // 100ms is just arbitrary waiting time which should be sufficient for fetching the external font on a fast network, anyone has a better solution?
As you can see I dynamically insert absolutely positioned styled piece of text to force external font loading (visibility:hidden is important here instead of display:none). Then I wait some time before I execute my SVG code which could potentially render something and then immediately ask for metrics.
I'm using DOJO's ContentPane module. I have a div element in one of the panes and I need to give it a certain height - 100 pixels less than the height of the ContentPane so that the div changes its height dynamically when you change the ContentPane size by dragging the splitters. I'm new to Dojo and would be happy if somebody could help me with this.
Thanks.
I think the best solution is via nested BorderContainers with properly set splitters, because that way dijit/layout will take care of resizing and you won't need to write any JavaScript code and your layout will be based solely on CSS.
It's kinda cumbersome to explain, so I created a working example for you at jsFiddle: http://jsfiddle.net/phusick/Ayg8F/ + a diagram:
NB: Do not forget to set height: 100% for html, body and the top BorderContainer.
The drawback of this solution is you will have to replace plain divs with ContentPanes. If you do not want to or can't you can use dojo/aspect and connect to BorderContainer or ContentPane resize method and resize your divs manually whenever the size changes:
require([
"dojo/ready",
"dojo/aspect",
"dijit/registry",
"dijit/layout/ContentPane",
"dijit/layout/BorderContainer"
], function(
ready,
aspect,
registry
) {
ready(function() {
var bc = registry.byId("borderContainer1");
aspect.after(bc, "resize", function() {
// calculate and set <div> size here
console.log("resize divs");
});
});
});
I get an XML or JSON with paths only, and I need to recreate the SVG image.
I create an empty
<svg xmlns='http://www.w3.org/2000/svg' version='1.1'></svg>,
I add a <g transform="scale(1 -1)" fill='#aaa' stroke='black' stroke-width='5' ></g> in it, and then in this element I add all of the paths in it (e.g. <path d= ... />).
In the end I get a SVG image, but because I haven't set the viewBox attribute in the SVG element the image isn't properly displayed - when I open it in browser, a part of it is displayed full size.
Can the viewBox be calculated from the values from the paths?
Thank you!
Similar to Martin Spa's answer, but a better way to do get the max viewport area is using the getBBox function:
var clientrect = path.getBBox();
var viewBox = clientrect.x+' '+clientrect.y+' '+clientrect.width+' '+clientrect.height;
You can then set the viewbox to these co-ordinates.
n.b. i think you can change the viewbox of an svg after it's rendered so you may have to re-render the svg.
OK so I solved it the following way:
removed all letters from the paths string and made an array out of it with
var values = pathValue.split('L').join(' ').split('M').join(' ').split('z').join('').split(' ');
found max and min from those values:
var max = Math.max.apply( Math, values );
var min = Math.min.apply( Math, values );
set the viewBox:
viewBox = max min max max
This worked in my case excellent. Hope that it will be helpful to someone else too.