SVG image with a border / stroke - svg

I'm trying to add a border around a svg image. I have tried 2 approaches but both failed...
Attempt 1 : Draws image but no border..
<image id="note-0" xlink:href="http://example.com/example.png" x="185" y="79" height="202" width="150" style="stroke:#ac0d0d; stroke-width:3;"></image>
Attempt 2 : Draws image but no border..
<defs>
<image id="image1352022098990svg" height="202" width="150" xlink:href="http://example.com/example.png"></image>
</defs>
<use xmlns="http://www.w3.org/2000/svg" id="note-0" xlink:href="#image1352022098990svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="185" y="79" height="202" width="150" style="stroke:#ac0d0d; stroke-width:3;"/>
So my question is, is it possible to define a image on a svg element and have a border/stroke around it at the same time?
Futhermore it seems i can position svg elements with translate and with the x/y attribute. Which is preffered and why?

stroke does not apply to <image> or <use>, only shapes and text. If you want to draw a border round it, draw a <rect> after the image with the same x,y,width and height as the image and give that a stroke and a fill of "none".
As to translate vs x/y - it depends on your use case.

If for some reason you cannot change the SVG elements, there is a workaround using the outline CSS property:
#note-0 {
outline: 6px solid white;
}

If you need it to wrap around a circular image (svg shape for example), and you just need some color to outline it, you may find something like this useful:
image {
filter: drop-shadow(0px 0px 1px red);
}

Related

Specify baseline position in SVG

Is there a possibility, in SVG, to determine a vertical position as being the "baseline" for that SVG graphics?
Context: we include a lot of inline SVG in tasks I prepare for students (see the attached screenshot, in which the circled 2 and 3 are actually SVG data). This text with inline SVG is published as HTML and in LaTeX. I'd like to find a way to include in my SVG files some marker such that later I don't have to manually specify a vertical offset for each graphic files to be perfectly aligned.
In this example, for instance, the bottom of the "2" within the circle should be determined as baseline, such that it can be automatically aligned with the bottom the of other characters that have no descender.
No, there is no such marker. The best you can do for a workaround is probably this: Set the bottom of the viewBox such that it represents the baseline. Then, if you globally set overflow: visible for all of your SVG icons, it doesn't matter if you have grafical content outside the viewBox, especially below the baseline.
p {
font-size: 40px;
}
svg {
width: 1em;
overflow: visible;
}
circle {
fill: none;
stroke: black;
}
text {
font-size: 14px;
}
<p>Example<svg viewBox="0 0 20 15">
<circle r="8.5" cx="10" cy="10" />
<text x="6" y="15">1</text>
</svg>text</p>

Implement Zooming with viewbox in nested SVGs

I have been tasked to implement zooming in custom charts based on SVGs. Before i had one <svg> element. I looked into either using the transform or the viewbox approach and decided for the viewbox approach. Since i have to support zooming on just the x-axis, the charts contents will be squashed depending on the zoomFactor and need preserveAspectRatio="none". This does not look pretty for the chart labels that i used foreignObjects for. They get disorted as well and are not readable anymore. I did not find any solutions on how to apply the viewbox to just the actual chart contents, not the scales / labels.
I came up with the solution to split the chart into 3 nested svgs. The structure looks like this:
<svg> // Container SVG
<svg>...</svg> // XAxis
<svg>...</svg> // YAxis
<svg>...</svg> // ChartContent
</svg>
The viewbox will only be applied to the ChartContent svg and the svgs with the actual scales stay untouched and are just simply rerendered if needed with different labels at different positions. The desired outcome is similar to this example: https://jsfiddle.net/19h83ker/1/
Given that i have a chart to display that is as an example 4000 pixels wide and 200 pixels high, the y-axis is 40 pixels wide and 200 pixels high, the x-axis is 40 pixels high and 4000 pixels wide, how should i generally setup the viewports? If i set width="100%" and height="100%" on the ContainerSVG and ChartContent SVG, i have no scrollbar available. If i set width="4040" on the ContainerSVG and width="4000" on the ChartContent SVG, i have a scrollbar but applying the viewbox while zooming out by 100% will simply halve my svg in width and the right 50% are left blank. I dont really understand what the combination of widths / heights is in my structure, that i should be going for. Or am i making a mistake in general? Are there better ways to implement the desired outcome? I have already spent 2 days on this and dont really see any other option than these 3 nested svgs.At the end of the day panning / zooming in the 4000 pixel wide example SVG chart should be possible.
The suggestions you mentioned involve setting width and height, then using the default browser behavior for scroll and zoom. Instead, you need to intercept the appropriate events and modify the viewBox attribute.
The code below demonstrates zooming on mouse wheel events. Panning should be simpler (only the first and second parts of the viewBox need to change) but the implementation depends on what UI controls you want to use.
svg = document.getElementById("s")
var vb = [0,0,300,200]
svg.setAttribute("viewBox",vb) // vb gets converted to the string "0,0,300,40"
function zoom(wheelEvent){
let k=1.005**wheelEvent.deltaY
let ctm = svg.children[0].getScreenCTM()//If the svg is empty, this won't work
// position of the mouse in svg coords
let mx = (wheelEvent.clientX-ctm.e)/ctm.a
let my = (wheelEvent.clientY-ctm.f)/ctm.d
// To center the zoom on the mouse, we need:
// (mx - initialX)/initialWidth == (mx - finalX)/finalWidth
// finalX = mx - (mx - initalX)*(finalWidth/initialWidth)
vb[0] = mx-(mx-vb[0])*k
vb[1] = my-(my-vb[1])*k
vb[2] *= k
vb[3] *= k
svg.setAttribute("viewBox",vb)
wheelEvent.preventDefault() // prevent the page from scrolling
}
svg.addEventListener("wheel",zoom)
html,body,svg{
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
}
#s{
width: 100%;
height: 100%;
background-color: red;
}
<svg id="outer" viewBox="0 0 200 100" preserveAspectRatio="xMinYMin">
<rect x="0" y="0" width="20" height="200" />
<rect x="0" y="0" width="100%" height="20" />
<svg id="s" x="20" y="20" width="180" height="180" viewBox="0,0,300,200">
<rect x="-1000" y="-1000" width="2000" height="2000" fill="lightgray"/>
<text font-size="60" x="10" y="60" fill="blue"> hello </text>
</svg>
</svg>
If you only want scaling of the x-axis, you'll need a different value for the 'preserveAspectRatio' attribute (as well as removing the code which changes vb[1] and vb[3]).

How avoid SVG from scaling as its container shrinks?

I want an SVG to not scale as its container shrinks. On scroll, my container shrinks its height (its width stays the same). That container contains an SVG that I want to not become scaled, but instead have its lower part become invisible/cut off.
I can do it if I (by CSS) use my SVG as a background, but I'd prefer to have the SVG inline in the HTML.
I have tried with various values for the SVG attribute preserveAspectRatio. I thought that the value xMidYMin slice would slice off the bottom part of my SVG (like I want), but it squashes its height instead.
My container is 245x80 px and on scroll is shrinked to 245x40 px.
My svg element has attribute viewBox set to 0 0 245 80 , and has no width or height explicitly defined.
You can use preserveAspectRatio="xMidYMin slice" for the svg element.
Please observe that the svg element has a viewBox and also a width and a height. While the aspect ratio of the viewBox is 1:1 the aspect ratio from width and height is 2:1
xMidYMin - Force uniform scaling.
Align the midpoint X value of the element's viewBox with the midpoint X value of the viewport.
Align the of the element's viewBox with the smallest Y value of the viewport.
slice - Scale the graphic such that:
the aspect ratio is preserved and
the entire viewport is covered by the viewBox
Please read more about preserveAspectRatio
In the next demo use the slider to change the height of the svg element
itr.addEventListener("input",()=>{svg.setAttribute("height",itr.value)})
svg{border:solid}
<p><input id="itr" type="range" min="10" max="200" value="100"/></p>
<svg preserveAspectRatio="xMidYMin slice" viewBox="0 0 100 100" width="200" height="100" id="svg">
<defs>
<path style="fill:gold; stroke:black; stroke-width: 8px;
stroke-linecap: round; stroke-linejoin: round;" id="smiley" d="M50,10 A40,40,1,1,1,50,90 A40,40,1,1,1,50,10 M30,40 Q36,35,42,40 M58,40 Q64,35,70,40 M30,60 Q50,75,70,60 Q50,75,30,60" />
</defs>
<use href="#smiley" />
</svg>

SVG is generating a viewbox even though there isn't one specified

I'm trying to create an SVG play button that is only the size of the button itself, but it seems that there is some kind of viewbox being auto-generated. This phantom viewbox isn't even the same dimensions as the the play button and seems to be a 2:1 ratio.
#play-button {
border: 1px dashed gray;
}
<svg id="play-button">
<style type="text/css">
.st0{fill:none;stroke:#010101;stroke-miterlimit:10;}
.st1{fill:#010101;}
</style>
<circle id="button-border" class="st0" cx="30" cy="29.9" r="29.3"/>
<polygon id="play-triangle" class="st1" points="21.9,15.7 46.6,29.9 21.9,44.1 "/>
</svg>
How can I size the viewbox to the size of the SVG without specifying a viewBox or a height/width?
The outer SVG element is a replaced element. If you don't provide anything to go on for size you'll get the default width which is 300px and the default height which is 50% of the width so if you've not supplied a width value either, that ends up being 150px.
There are a number of ways to indicate the height and width you want. The most obvious would be height and width attributes or CSS properties of the same name but you can also use a viewBox attribute to define a width and height.
#RobertLongson's answer explains why this is happening. Here are some things you can do about it:
Your circle's r="29.3" does in effect "specify a height/width" in the markup. If you can put a value there, I would imagine you actually can the same value in the svg's width or height instead. Here's a different approach you could take with that in mind. It does require a viewbox but one that doesn't change: 0 0 100 100 just lets us use percentage values for the polygon's points. To calculate them, I did yourpointvalue/yourradius (e.g. 21.9/58.6). Using a border on the svg instead of a circle element makes this lighter weight and makes the markup easier to read. I've specified the width in the CSS, but it could also be inline; you could also only specify the height, or have it relative to a parent, etc etc.
(If you check the svg with your browser's inspector, you'll see it's the same width and height as the circle)
#play-button {
fill: #010101;
border: 1px solid #000;
border-radius: 50%;
display: block;
/* specify a width or a height either here or inline */
width: 58.6px;
}
<svg id="play-button" viewbox="0 0 100 100">
<polygon points="37.3,26.8 79.5,50 37.3,73.2" />
</svg>
If you really can't use viewbox, width, or height, and need to keep all that markup, you can achieve this with javascript. This is adapted from a solution by #Almis (but see #PaulLeBeau's take on the issue). The overflow: visible is necessary because the circle's stroke extends a little beyond the bounds of the svg. (Eventually we may be able to specify where a stroke is drawn, but not yet.)
var playButton = document.getElementById('play-button');
var boundingRect = document.getElementById('button-border').getBoundingClientRect();
playButton.style.height = boundingRect.height + 'px';
playButton.style.width = boundingRect.width + 'px';
#play-button {
border: 1px dashed gray;
overflow: visible; /* added */
}
<svg id="play-button">
<style type="text/css">
.st0 {
fill: none;
stroke: #010101;
stroke-miterlimit: 10;
}
.st1 {
fill: #010101;
}
</style>
<circle id="button-border" class="st0" cx="30" cy="29.9" r="29.3" />
<polygon id="play-triangle" class="st1" points="21.9,15.7 46.6,29.9 21.9,44.1 " />
</svg>
It looks like the answer is that the default size of an SVG is 300x150, which seems bizarre to me.
If you don't want that sizing (and you probably shouldn't rely on that default) you have to specify the size as detailed in this CSS Tricks article:
https://css-tricks.com/scale-svg/

Auto height for a foreignObject in SVG

I have, in my SVG, a foreignObject which contains a p element. I want the height of this structure to be adapted to the height of my text.
The p element is already adapted : I've done nothing for that.
But I have troubles for the foreignObject. If I remove the field height, it doesn't work. height:auto doesn't work either.
Since there is no real use of scaling up and down the foreignObject itself, then you can set both foreignObject height and width to 1, and via CSS set foreignObject { overflow: visible; } to make its content visible whatever it is and do whatever you need to do it with it.
You can set height of the foreignObject element in em units, maybe that could help?
Right now the width and height attributes of a foreignObject are required, and must have values > 0, otherwise the element will not be rendered.
Update: An alternative is to just set the dimensions of the foreignObject to 100%, and use the fact that the foreignObject has a transparent background per default. Since other elements in svg are laid out in an absolute manner anyway they don't depend on the foreignObject size.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
p { position: absolute; top: 50%; left: 50%; width: 100px; }
</style>
<circle cx="50%" cy="50%" r="25%" fill="lightblue"/>
<foreignObject width="100%" height="100%">
<p xmlns="http://www.w3.org/1999/xhtml">
some wrapped text...
some wrapped text...
some wrapped text...
some wrapped text...
</p>
</foreignObject>
</svg>

Resources