CSS padding property for svg elements - css

I can't figure out how the CSS padding property is interpreted for svg elements. The following snippet (jsFiddle):
<!DOCTYPE html>
<meta charset="utf-8">
<title>noob d3</title>
<style>
svg{background-color:beige;
padding:0px 0px 50px 50px;}
rect{fill:red;
stroke:none;
shape-rendering:crispEdges;}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
d3.select("body")
.append("svg")
.attr("width", 155)
.attr("height", 105)
.append("g")
.append("rect")
.attr("class", "frame")
.attr("x", 50)
.attr("y", 50)
.attr("width", 50)
.attr("height", 50);
</script>
</body>
... displays significantly differently in Firefox and Chrome. What's worse, neither display really makes sense to me: the size of the displayed svg element (the "beige" rectangle) looks to be significantly bigger than what I expected.
So my question is two-fold: 1) How is the padding property of an svg element supposed to affect where things get drawn within it? 2) Is there a polyfill that will ensure that both Chrome and Firefox both handle padding in the same way?

AFAIK, the SVG standard doesn't specify anything like padding, which is why it's handled inconsistently. Just set the SVG to the size you want (with padding) and maybe add a rect to make it appear like you want it to appear.

From my experience (granted, still very little as I am still learning SVG), I have strayed away from using padding wherever that I could do so. It was suggested to me when I was first learning SVG that I use margin in place of padding, if possible.
This is also because you can use display: block; and margin: 0 auto; to make the left and right sides of an SVG to fit directly into the middle of the screen.

There is no padding or margin, but you can set x and y attributes such that the elements inside or outside get a padding and margin. For example, if an element starts at (0,0), starting at (10, 10) will automatically give a margin of 10.

You can apply padding to parent svg elements
The padding as described by the OP actually works – albeit, not as desired.
Outermost <svg> will be rendered with padding (won't work for nested svgs).
But: child elements (e.g the <rect>) won't be re-aligned according to – unlike HTML DOM elements.
svg {
background-color: beige;
max-height:20em;
}
.pdd{
padding: 0px 0px 50px 50px;
}
rect {
fill: red;
stroke: none;
shape-rendering: crispEdges;
}
.borderBox{
box-sizing: border-box;
}
.overflow{
overflow:visible
}
<p>Rendered size: 205 x 155 – padding added to initial dimensions </p>
<svg class="pdd" width="155" height="105">
<g>
<rect class="frame" x="50" y="50" width="50" height="50" />
</g>
</svg>
<p>Rendered size: 155 x 105; cropped</p>
<svg class="pdd borderBox" width="155" height="105">
<g>
<rect class="frame" x="50" y="50" width="50" height="50" />
</g>
</svg>
<p>Rendered size: 155 x 105; cropped; overflow visible</p>
<svg class="pdd borderBox overflow" width="155" height="105">
<g>
<rect class="frame" x="50" y="50" width="50" height="50" />
</g>
</svg>
Usecase: padding for fluid svg layouts
So, padding doesn't work well for fixed widths/heights.
However, it can be handy for flexible/fluid layouts – provided you're using relative (percentage) units for svg child elements.
*{
box-sizing:border-box;
}
svg{
border:1px solid #ccc;
}
svg {
background-color: lightblue;
padding:0 10px;
overflow:visible;
}
.svg2 {
padding:10px;
}
.svg3 {
padding:0px;
}
.resize{
resize:both;
overflow:auto;
padding:1em;
border:1px solid #ccc;
}
<p>resize me :</p>
<div class="resize">
<svg id="svg" width="100%" height="40" xmlns="http://www.w3.org/2000/svg">
<circle cx="0" cy="10" r="5" />
<circle cx="0" cy="30" r="5" />
<circle cx="50%" cy="10" r="5" />
<circle cx="50%" cy="30" r="5" />
<circle cx="100%" cy="10" r="5" />
<circle cx="100%" cy="30" r="5" />
</svg>
</div>
<div class="resize">
<svg class="svg2" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
<!-- align path center to x/y =0 by adding viewBox offset width/2 height/2 -->
<symbol class="icon icon-home" id="iconHome" viewBox="20 20 40 40" overflow="visible">
<path d="M36.4 22.2l-5.2 0l0 13l-3.4 0l0-16.7l-7.7-8.7l-7.7 8.7l0 16.7l-3.4 0l0-13l-5.2 0l16.4-17.4z"></path>
</symbol>
<use x="0" y="0%" href="#iconHome" width="20" height="20" />
<use x="0" y="100%" href="#iconHome" width="20" height="20" />
<use x="50%" y="0%" href="#iconHome" width="20" height="20" />
<use x="50%" y="100%" href="#iconHome" width="20" height="20" />
<use x="100%" y="0%" href="#iconHome" width="20" height="20" />
<use x="100%" y="100%" href="#iconHome" width="20" height="20" />
</svg>
</div>

Based on what I was able to try on firefox and chromium: the specified width and height for an svg include the padding.
In other terms, if you want an image of 20*20px with a padding of 10px on each side, you should set the width to 20+10*2 = 40px (same thing with the height) and the padding to 10px
Note : 20+10*2 : 20 is the width you want, 10 is your padding and you double it because you want it on both sides.

The best solution is open Inkscape (or other SVG editor) and change dimension

Related

Scale element to pixel size

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>

How can i add backgroung image in the following svg?

From this website
https://smooth.ie/blogs/news/svg-wavey-transitions-between-sections
i generated the following svg
<div style="height: 100%; overflow: hidden;" class='parent' ><svg viewBox="0 0 500 150" preserveAspectRatio="none" style="height: 100%; width: 100%;"><path d="M213.19,0.00 C152.69,70.06 270.03,70.06 202.98,150.00 L500.00,150.00 L500.00,0.00 Z" style="stroke: none; fill: #08f;"></path></svg></div>
the problem is that when i try to add background image for example here on the parent class
.parent {
background-image: url('../../../assets/images/calendar.png');
}
then the image is hidden behind the blue color of the svg.How can i 'insert' this image to be on the blue svg color image ?
As name says - it's "background-image" so it always is on the background of selected element.
I suggest that you should make an <img> tag in parent element and style it so that .parent would have attribute position: relative and img should have position: absolute.
Also remember to set top and left/right position for <img>.
it's Answered here https://stackoverflow.com/a/3798797/9017484
You can do it by making the background into a pattern:
<defs>
<pattern id="img1" patternUnits="userSpaceOnUse" width="100" height="100">
<image href="wall.jpg" x="0" y="0" width="100" height="100" />
</pattern>
</defs>
your code should be something like this:
<div style="height: 100%; overflow: hidden;" class='parent' >
<svg viewBox="0 0 500 150" preserveAspectRatio="none" style="height: 100%; width: 100%;">
<defs>
<pattern id="img1" patternUnits="userSpaceOnUse" width="100" height="100">
<image href="ImageFile.jpg" x="0" y="0" width="100" height="100" />
</pattern>
</defs>
<path d="M213.19,0.00 C152.69,70.06 270.03,70.06 202.98,150.00 L500.00,150.00 L500.00,0.00 Z" style="stroke: none; fill: url(#img1);"></path>
</svg>
</div>
change the opacity level so the color is lower opacity. something like this perhaps.
background {
backgroundColor:blue,
opacity: 0.5;
}

overlay svg responsive according to the img

I have to put an icon overlay to the image. Image is 100% in width & height. Icon is the mark on the image. Icons position is changing according to the image responsive. I need to stick the same position of the icon all time. When image is responsive the icon also needs to be change its width left and top.
<div class="outer">
<svg class="mark-icon"><svg>
<img src="Map.jpg" class="map-bg"/>
</div>
.outer{
position:relative;
}
.map-bg{
width:100vw;
height:100vh;
object-fit:cover;
}
.mark-icon{
width:50px;
height:50px;
position:absolute;
left:50px;
top:30px;
}
Sample
https://jsfiddle.net/rkv3sd0q/
Try adding a map image using the svg <image> tag
Add a red pointer icon with <circle>
When both elements are inside the SVG, their position relative to each other will remain constant.
And will not change on any resize of the browser window.
body{
margin:0px;
padding: 0px;
}
.outer-pane {
width:100vw;
height:100vh;
}
.pointer {
fill:red;
cursor:pointer;
}
<div class="outer-pane">
<svg viewBox="0 0 1000 739" preserveAspectRatio="xMin YMin meet" >
<image class="mainProject" xlink:href="https://www.mapsofworld.com/style_2019/images/world-map.png?v:1" width="1000" height="739" />
<circle class="pointer" cx="320" cy="124" r="25" fill="red" />
</svg>
</div>
Update
Map with multiple markers
To do this, add the marker path once using the <symbol> tag
<symbol id="marker" width="18" height="28" viewbox="0 0 9.831 14.782">
<path fill="#00AEEF" d="M9.831 4.916c0 2.714-3.538 8.487-4.913 9.867C3.437 13.307 0 7.631 0 4.916S2.201 0 4.916 0s4.915 2.201 4.915 4.916z"/>
<circle cx="4.912" cy="4.916" r="2.932" fill="#E7EDEF"/>
</symbol>
And add markers to the map many times with the <use> tag
<use xlink:href="#marker" fill="#00AEEF" x="350" y="80" />
<use xlink:href="#marker" fill="red" x="670" y="360" />
body{
margin:0px;
padding: 0px;
}
.outer-pane{
width:100vw;
height:100vh;
}
.pointer {
fill:red;
cursor:pointer;
}
symbol path {
fill:inherit;
}
<div class="outer-pane">
<svg viewBox="0 0 1000 739" preserveAspectRatio="xMin YMin meet" >
<symbol id="marker" width="18" height="28" viewbox="0 0 9.831 14.782">
<path fill="#00AEEF" d="M9.831 4.916c0 2.714-3.538 8.487-4.913 9.867C3.437 13.307 0 7.631 0 4.916S2.201 0 4.916 0s4.915 2.201 4.915 4.916z"/>
<circle cx="4.912" cy="4.916" r="2.932" fill="#E7EDEF"/>
</symbol>
<image class="mainProject" xlink:href="https://www.mapsofworld.com/style_2019/images/world-map.png?v:1" width="1000" height="739" />
<use xlink:href="#marker" fill="#00AEEF" x="350" y="80" />
<use xlink:href="#marker" fill="red" x="670" y="360" />
<use xlink:href="#marker" fill="violet" x="770" y="360" />
<use xlink:href="#marker" fill="purple" x="170" y="210" />
<use xlink:href="#marker" fill="crimson" x="190" y="310" />
</svg>
</div>
What I understood is, Icon must be responsive and it's height and width should decrease with decrease on width of the screen, also It should be on left upper corner of the image.
If that is the case then you have to use #media query on each break point to change the icon size and position for left, top, width and height properties
Also, if you keep object-fit: cover;, then there is possibility that image is not of the same height and width of screen. So there might be possibility that icon is not where should be.
If there is anything else, please put up a example in codepen and update the question again.
You can place your icon dynamically by using, unit as vw and vh, as your image is set as well with that, it will be responsive the same way:
.mark-icon{
position:absolute;
left:5vw;
top:5vh;
}
DEMO:
body{margin:0;}
.outer{
position:relative;
}
.map-bg{
width:100vw;
height:100vh;
object-fit:cover;
}
.mark-icon{
width:50px;
height:50px;
position:absolute;
left:5vw;
top:5vh;
}
<div class="outer">
<svg height="100" width="100" class="mark-icon">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
Sorry, your browser does not support inline SVG.
</svg>
<img src="https://images.freeimages.com/images/large-previews/7e9/ladybird-1367182.jpg" class="map-bg">
</div>

Viewbox placement in referencing SVG symbols and CSS dimensions

I edited my initial cry of despair into something more to the technical point, in order to turn it into a Q&A.
I'm using SVG symbols that I reference in the document with use elements. I'm styling these with CSS. I don't want to set both height and width in the CSS, I want to set only one of them with the other one scaling accordingly.
I do set a viewBox attribute on the symbol. But the graphic does not scale correctly.
<!DOCTYPE html>
<html>
<head>
<title>SVG Symbols</title>
<style>
body { margin: 20px; }
.svg-large { width: 500px; fill: yellow;}
</style>
</head>
<body>
<svg style="display:none;">
<symbol id="scary-smiley" viewBox="0 0 20 20">
<circle cx="10" cy="10" r="9.5" stroke-width="1"
stroke="black" />
<circle cx="6" cy="7" r="1.5" fill="black"/>
<circle cx="14" cy="7" r="1.5" fill="black"/>
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Teeth_by_David_Shankbone.jpg/320px-Teeth_by_David_Shankbone.jpg"
width="10" height="5.2" x="5" y="11"/>
</symbol>
</svg>
<svg class="svg-large">
<use xlink:href="#scary-smiley"/>
</svg>
</body>
</html>
The code below has been tested in current Firefox, Chrome and a Webkit-based browser named Midori.
For some reason, defining the viewBox on the symbol element does not have the full desired effect in Firefox and Chrome. It does have some effect, though, as it makes the element scaleable. So, if you want set both width and height in CSS, you can do that.
If the viewBox element is specified only on the symbol and you set only one of width or height, then in Firefox and Chrome the other dimension is set according the default object size in HTML 5 whis is 300x150 px. So, in the example in the question, you get a 500x150 px element and the graphic is scaled to fit that rectangle.
If you want to define only one width or height with the other one scaling accordingly, then defining viewBox on the referencing SVG element works:
<!DOCTYPE html>
<html>
<head>
<title>SVG Symbols</title>
<style>
body { margin: 20px; }
.svg-large { width: 500px; fill: yellow;}
</style>
</head>
<body xmlns:xlink="http://www.w3.org/1999/xlink">
<svg style="display:none;">
<symbol id="scary-smiley">
<circle cx="10" cy="10" r="9.5" stroke-width="1"
stroke="black" />
<circle cx="6" cy="7" r="1.5" fill="black"/>
<circle cx="14" cy="7" r="1.5" fill="black"/>
<image xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Teeth_by_David_Shankbone.jpg/320px-Teeth_by_David_Shankbone.jpg"
width="10" height="5.2" x="5" y="11"/>
</symbol>
</svg>
<svg class="svg-large" viewBox="0 0 20 20">
<use xlink:href="#scary-smiley"/>
</svg>
</body>
</html>
Firefox' and Chrome's behaviour is standard compliant, according to the SVG 2 specification, according to which the <svg><use .../></svg> clause establishes a new SVG viewport.

CSS Clip-path positioning issues

I have created a fairly simple shape using an SVG element which is then put into my CSS using clip-path. It should make the corners rounded for me but for some reason only 1 of the corners does the effect perfectly.
This is the shape:
<svg height="500" width="500">
<path fill="#555555" d="M50,0 L450,0 Q500,0 500,50 L500,400 Q500,450 450,450 L200,450 L175,500 L150,450 L50,450 Q0,450 0,400 L0,50 Q0,0 50,0z" />
</svg>
This is what happens when i use it as a clip-path
body {
background: #555;
}
img {
clip-path: url(#svgPath);
-webkit-clip-path: url(#svgPath);
}
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<path fill="#FFFFFF" d="M50,0 L450,0 Q500,0 500,50 L500,400 Q500,450 450,450 L200,450 L175,500 L150,450 L50,450 Q0,450 0,400 L0,50 Q0,0 50,0z" />
</clipPath>
</defs>
</svg>
<img src="https://dummyimage.com/500" />
It seems to work perfectly within FireFox but shows the corners aren't cut correctly in Chrome apart from the bottom right corner.
The default units for the clip-path is userSpaceOnUse and this seems to calculate the coordinates of the path with reference to the root element. This is the reason why the clip-path seems like it is producing an incorrect output. Nullifying the margin and padding on the root element or absolutely positioning the element (like in the below snippet) should solve the issue.
body {
background: #555;
}
img {
position: absolute;
top: 0px;
left: 0px;
clip-path: url(#svgPath);
-webkit-clip-path: url(#svgPath);
}
<svg height="0" width="0">
<defs>
<clipPath id="svgPath">
<path fill="#FFFFFF" d="M50,0 L450,0 Q500,0 500,50 L500,400 Q500,450 450,450 L200,450 L175,500 L150,450 L50,450 Q0,450 0,400 L0,50 Q0,0 50,0z" />
</clipPath>
</defs>
</svg>
<img src="http://lorempixel.com/500/500/" />
However, in a real life scenario the actual element that has to be clipped could be present anywhere within the body and hence I think it is a much better approach to use the objectBoundingBox as the units like in the below snippet:
body {
background: #555;
}
img {
clip-path: url(#svgPath);
-webkit-clip-path: url(#svgPath);
}
<svg height="0" width="0">
<defs>
<clipPath id="svgPath" clipPathUnits="objectBoundingBox">
<path fill="#FFFFFF" d="M0.1,0 L0.9,0 Q1,0 1,0.1 L1,0.8 Q1,0.9 0.9,0.9 L0.4,0.9 L0.35,1 L0.3,0.9 L0.1,0.9 Q0,0.9 0,0.8 L0,0.1 Q0,0 0.1,0z" />
</clipPath>
</defs>
</svg>
<img src="https://dummyimage.com/500" />
As mentioned in the question itself, this behavior is visible only in Chrome and not Firefox for reasons unknown to me. Firefox produces an output similar to the expected one even when (a) extra padding + margin is added to the body and (b) when the image itself is wrapped inside another container which also has padding + margin.
The only case where Firefox's output matches with Chrome is when a padding is added directly to the img tag itself. I believe this happens because padding is part of the element and thus affects the coordinates.

Resources