I have an SVG element of dynamic size. I want to scale it and its contents to a particular pixel size (not by pixels) on demand.
This is invalid:
transform: scale(100px);
My knowledge of SVG is middling so maybe there's a better way, but setting the height/width of the SVG element after the contents are drawn simply causes its contents to runeth over, as they are "absolute" and not "relative" paths.
With JS you can just get the relative sizes:
const scaleX = targetWidth / svg.offsetWidth;
const scaleY = targetHeight / svg.offsetHeight;
svg.style.scale = `${scaleX}px ${scaleY}px`; //untested but you get the idea
My hope is there is a sort of "scaleTo" somewhere in CSS3 I'm unaware of, or neat trick to accomplish this. An authoritative "no" is an acceptable answer.
If you have access to the html for the svg, you can remove the svg element's width and height attributes and replace them with a viewBox attribute of the with the x/y positions set to 0, and the width/height pair set to the values you deleted:
<svg width="300" height="200">
<!-- change to: -->
<svg viewBox="0 0 300 200">
You can then place the svg element inside a sized div and set the css width and height of the svg to 100%:
.svgContainer svg {
width: 100%;
height: 100%;
}
See working snippet to compare effects, I've squeezed the same svg into a smaller div, with and without the viewBox set.
Note for a dynamic resize, the div container has to resize dynamically, the viewBox version of the svg set to 100% width and height of the container will take care of itself. If the container Div had been sized by % instead of pixels, it will grow and shrink with the viewport of the browser.
If you can't access the html markup, you could achieve the same by retrieving the width and height attributes of the svg using javascript and set a new attribute for the viewBox.
More about viewBox: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
.svgContainer {
width: 100px;
}
.svgContainer svg {
margin: 0;
width: 100%;
height: 100%;
}
<p> 300x200 svg rendered outside of a container:</p>
<svg width="300px" height="200px">
<rect x="0" y="0" width="100%" height="100%" fill="red" stroke="blue"/>
<rect x="100" y="50" width="100" height="50" fill="yellow"/>
<rect x="30" y="20" width="20" height="35" fill="blue"/>
</svg>
<p> same 300x200 svg rendered inside sized div:</p>
<div class="svgContainer">
<svg width="300px" height="200px">
<rect x="0" y="0" width="100%" height="100%" fill="red" stroke="blue"/>
<rect x="100" y="50" width="100" height="50" fill="yellow"/>
<rect x="30" y="20" width="20" height="35" fill="blue"/>
</svg>
</div>
<p>svg modified to use viewbox attribute values, inside sized div</p>
<div class="svgContainer">
<svg viewBox="0 0 300 200">
<rect x="0" y="0" width="100%" height="100%" fill="red" stroke="blue"/>
<rect x="100" y="50" width="100" height="50" fill="yellow"/>
<rect x="30" y="20" width="20" height="35" fill="blue"/>
</svg>
</div>
Related
I'm trying to build an SVG image with content that is 100% the width of the container, minus 60px for some text.
If I was using HTML, or SVG with javascript, I would have no problem doing this. But I feel like there should be a way to do this using SVG (and CSS if needed).
I want the equivalent of this (Codepen here):
<svg width="100%" height="100%">
<rect fill="#ccc" x="0" y="0" width="100%" height="100%"></rect>
<text x="100%" y="50%" stroke="black" text-anchor="end">Y-axis</text>
<svg width="100%" height="100%">
<!-- This rect represents the entirety of the contents of the graph -->
<rect x="0" y="0" style="width: calc(100% - 60px)" height="100%" fill="#c88"></rect>
</svg>
</svg>
In the above snippet, the inner <rect> resizes to be 100% - 60px the width of the container element. However, this trick only works for a single element - if you replace that <rect> with a complex SVG structure it no longer works.
Things I've tried:
Doing a transform: scale() via CSS on the <rect> - I can't figure out what to put into the scale() to make it behave like 100% - 60px.
Changing the width of the nested <svg> element
<svg width="calc(100% - 60px)"> doesn't work - can't do calc() inside the width attribute
<svg width="100%" style="width: calc(100% - 60px);"> (with or without the width attribute) - doesn't work - the CSS "width" property is ignored whether or not the width attribute is present.
I'm starting to think what I want to do isn't possible right now with SVG, but it doesn't seem like an uncommon use case. Is there any way to do this?
As discussed in the comments, you might have some luck achieving the same by making your graph area 100% of the viewBox, but place the SVG in a container with 60px of padding on the right to account for the text space.
Moving your text (and background rect) to x="100%" with its text-anchor="start", in addition to letting the SVG overflow, you can get a pretty close result without needing to transform your graphic, since you have a fixed 60px value you can consistently rely on:
div {
padding-right: 60px;
}
svg {
overflow: visible;
}
<div>
<svg width="100%" height="100%">
<rect fill="#ccc" x="100%" y="0" width="60px" height="100%"></rect>
<text x="100%" y="50%" stroke="black" text-anchor="start">Y-axis</text>
<rect x="0" y="0" width="100%" height="100%" fill="#c88"></rect>
</svg>
</div>
PS: Maybe you would prefer your text to have text-anchor="middle", and transform it in CSS with transform: translateX(30px) to place it in the centre of the "text" area — might look cleaner that way:
div {
padding-right: 60px;
}
svg {
overflow: visible;
}
text {
transform: translateX(30px);
}
<div>
<svg width="100%" height="100%">
<rect fill="#ccc" x="100%" y="0" width="60px" height="100%"></rect>
<text x="100%" y="50%" stroke="black" text-anchor="middle">Y-axis</text>
<rect x="0" y="0" width="100%" height="100%" fill="#c88"></rect>
</svg>
</div>
I'm a newbie in SVG so it's probably an easy question. I'm trying to make an SVG Mask with a simple triangle shape inside a rectangle. What I want to achieve is to get the rectangle responsive with his width but the triangle should
- get a fixed size
- be always at the center of the viewport
You'll understand better with my snippet:
.header-arrow {
height: 70px;
}
svg {
height: inherit;
}
#arrow-down-alpha {
transform: translateX(calc(50vw - 130px/2));
}
<div class="header-arrow">
<svg width="100%">
<defs>
<mask id="myMask" x="0" y="0" width="100%" height="100%">
<rect fill="white" x="0" y="0" width="100%" height="100%" />
<polygon id="arrow-down-alpha" fill="black" x="00" y="0" width="165px" height="100%" points="55.91 37.8 111.81 0 0 0 55.91 37.8" />
</mask>
</defs>
<rect id="base-mask" mask="url(#myMask)" x="0" y="0" width="100%" height="100%" />
</svg>
</div>
It's workning right now in chrome, but the translateX (or translate) is not working in firefox and edge. I've tried to use the transform SVG attribute but it seems that I can't use percentages values.
I'm not realy familiar with the viewbox but I'm not sure it will help in this case.
Thanks anyway for any kind of help !
Here's one way to achieve what you want without relying on new units or calc(). It should be cross-browser compatible also.
How it works:
We wrap the triangle in a nested SVG. We use an SVG because it has an x attribute which can take percentages.
We position this nested SVG at x="50%". It is now centred in the mask (roughly, see next step).
We move the triangle shape so it is centred at x=0. That's so that it is not offset from the centre of the mask.
We set overflow="visible" on the nested SVG so the part of the triangle that is now off the left of the SVG (ie. x < 0) are not clipped.
.header-arrow {
height: 70px;
}
svg {
height: inherit;
}
<div class="header-arrow">
<svg width="100%">
<defs>
<mask id="myMask" x="0" y="0" width="100%" height="100%">
<rect fill="white" x="0" y="0" width="100%" height="100%" />
<svg x="50%" overflow="visible">
<polygon fill="black" points="0 38 56 0 -56 0" />
</svg>
</mask>
</defs>
<rect id="base-mask" mask="url(#myMask)" x="0" y="0" width="100%" height="100%" />
</svg>
</div>
DEMO
Objective: I am trying to create a triangle shaped proportion less image in HTML.
My Approach: I have decided to use SVG to achieve this by creating a polygon triangle that can stretch & shrink to fit any dimension and used it as a mask on image that is suppose to fit in any dimension without loosing its own proportion.
Issue: Although the shape is working as I want but the background image stretches with the shape, is there any way I can make the image behave to something similar like css background-size: cover property.
Code:
HTML
<div id="svg-container">
<svg width='100%' height='100%' viewBox="0 0 100 100" preserveAspectRatio="none" style='background-color: whitesmoke'>
<defs>
<polygon id="mask" points="0,0 0,100 0,100 100,0" />
<pattern id="image" patternUnits="userSpaceOnUse" width="100" height="100" x="0" y="0">
<image xlink:href="http://lorempixel.com/500/500" x="0" y="0" width="100%" height="100%" preserveAspectRatio="xMinYMin slice"/>
</pattern>
</defs>
<use xlink:href="#mask" fill="url(#image)"></use>
</svg>
</div>
CSS:
#svg-container {
width: 100%;
height: 100px;
}
To check same issue using SVG image tag here.
I generate a SVG in a GWT application and I want to modify the first rect background-color inside my SVG in CSS.
Here the code:
<svg id="chart8" width="720" height="350" style="overflow: hidden;">
<defs id="defs">...</defs>
<rect x="0" y="0" width="720" height="350" stroke="none" stroke-width="0" fill="#ffffff">
<g>...</g>
<g><rect .../></g>
<g>...</g>
</svg>
So i want to modify in CSS the fill value only for the first rect element for #chart8.
I tested this but it's not working:
#chart8 rect:first-of-type {
fill: #F1F1F1;*/
}
Have you an idea?
Thanks!
I am trying to create an SVG graph with a fixed width to the left for the x axis labels and then a variable width to fill the remaining space for the actual graph results. The image below shows what I am trying to achieve. Thus far I have been unable to work out how to create the fixed width and variable width area.
Any help with this would be much appreciated!
Many thanks.
I would nest a couple of SVG elements inside your main like so:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500px" height="500px" >
<svg width="100">
<rect x="0" y="0" width="100%" height="100%" fill="red" />
</svg>
<svg x="100" >
<rect x="0" y="0" width="100%" height="100%" fill="blue" opacity="0.5"/>
</svg>
</svg>
NOTE I made the blue SVG element translucent so you can see that none of the red SVG was behind it.
I would also recommend using viewBox to give you more control over your drawing...
EDIT:
OK then I need to ask you a question about aspect ratios. If you take a square (width = height) and chop off a fixed portion from ONE side you no longer have a square and you have to think about what that means to your graph.
I believe this SVG will demonstrate more or less what you want:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="600px" height="500px" viewBox="0 0 1200 1000">
<svg width="200">
<rect x="0" y="0" width="100%" height="100%" fill="red" opacity="0.5"/>
</svg>
<svg x="200" width="1000" height="1000" viewBox="0 0 100 100">
<rect x="0" y="0" width="100%" height="100%" fill="blue" opacity="0.5"/>
<rect x="80%" y="10%" width="10%" height="50%" fill="green"/>
<rect x="10" y="10" width="70" height="40" fill="gray"/>
</svg>
NOTE the aspect ratio (AR) of the outermost SVG's dimensions MUST match the outermost viewBox's AR but can have different values. Likewise for the second inner SVG, but now you are dealing with a slice of the total that is a true square and not a rectangle. You can vary the width and height of the outer most SVG and so long as you maintain the same AR all the code on the inside will not have to change - it will all scale automajically :)
Also note the different scales in use and the different value types I used for co-ordinates. Because my second inner SVG's viewBox set the user co-ordinates to 100 X 100, 10% and 10 amount to the same thing...
You could also set the preserveAspectRatio="none" or some other value to achieve different effects but for a graph I kinda think lining things up is important so I wouldn't.
One final note - you could (and in your case should) omit the viewBox on the inner SVG. That way the scale is consistent on all parts of your graph. I was just showing off the power of viewBox :)
It just occurred to me that you may prefer a rectangle to a square so here is a code sample for that:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="400px" viewBox="0 0 1600 800" >
<svg width="200">
<rect x="0" y="0" width="100%" height="100%" fill="red" opacity="0.5"/>
</svg>
<svg x="200" width="1400" height="800" viewBox="0 0 175 100" >
<rect x="0" y="0" width="100%" height="100%" fill="blue" opacity="0.5"/>
<rect x="80%" y="10%" width="10%" height="50%" fill="green"/>
<rect x="10" y="10" width="70" height="40" fill="gray"/>
</svg>
NOTE the width of the inner SVG is set to 175 so that the aspect ratio of 1400/800 is maintained.