How to stroke SVG elements together, as a single element [duplicate] - javascript

I have two shapes: circle and rectangle. Want to convert them into one figure. Are there any ways to do that in SVG code?
<svg width="400" height="400">
<defs>
<g id="shape" fill="none" stroke="red">
<rect x="40" y="50" width="40" height="70" />
<circle cx="50" cy="50" r="50" />
</g>
</defs>
<use xlink:href="#shape" x="50" y="50" />
<use xlink:href="#shape" x="200" y="50" />
</svg>
Like this:

For anyone looking for the answer to the actual question of how to combine two outlined shapes into a single outlined shape (rather than putting a drop shadow on the combined shape), here is a possible solution:
<svg width="400" height="400">
<defs>
<rect id="canvas" width="100%" height="100%" fill="white" />
<rect id="shape1" x="40" y="50" width="40" height="70" />
<circle id="shape2" cx="50" cy="50" r="50" />
<mask id="shape1-cutout">
<use href="#canvas" />
<use href="#shape1" />
</mask>
<mask id="shape2-cutout">
<use href="#canvas" />
<use href="#shape2" />
</mask>
</defs>
<use href="#shape1" stroke="red" fill="none" mask="url(#shape2-cutout)" />
<use href="#shape2" stroke="red" fill="none" mask="url(#shape1-cutout)" />
</svg>
This essentially draws the circle with the rectangle shape cut out of it and draws the rectangle with the circle cut out of it. When you place these "punched out" shapes one on top of the other, you get what appears to be a single outlined shape.
Here's what the SVG actually does:
It defines a white rectangle called "canvas" that is the same size as the SVG.
It defines the two shapes that are to be combined ("shape1" and "shape2").
It defines a mask for each shape that combines the canvas (which has a fill of white) with the shape (which has a fill of black by default). Note that when you apply a mask to a shape, the part of the shape that corresponds to the white area of the mask is shown, while the part that corresponds with black part is hidden.
It draws each shape with the the mask of the other shape applied.

You can make a <mask> or a <clipPath> from the two shapes and then use that to mask a third shape. You can then apply your drop shadow to that.
<svg width="400" height="400">
<defs>
<clipPath id="shape">
<rect x="40" y="50" width="40" height="70" />
<circle cx="50" cy="50" r="50" />
</clipPath>
<filter id="shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="3" dy="3"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g filter="url(#shadow)">
<rect width="100%" height="100%" fill="red"
clip-path="url(#shape)"/>
</g>
</svg>
Note: if you are wondering why we are applying the drop shadow to a parent <g> here, it is because if we applied it directly to the <rect>, the drop shadow would be subject to the clip also.

What's wrong with just a dropshadow on a group around the shapes?
<svg width="400" height="400">
<defs>
<filter id="shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="3" dy="3"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g filter="url(#shadow)">
<rect x="40" y="50" width="40" height="70" fill="red"/>
<circle cx="50" cy="50" r="50" fill="red"/>
</g>
</svg>

Note: Be aware that this solution is only giving you the outer part of the contour.
A little variation of #devuxer's answer. A shadow can be created by a mask that can then be applied to a black shape. In https://stackoverflow.com/a/50884092/275195 two masks are created with set theory (A-B)+(B-A). However in the rendering engine a pixel raster is used for masks and antialiasing is applied. This leads to half-transparent pixels in the mask at the boundary of A and B. This can be avoided by applying the shaped to one mask like this (basically outline of A+B minus A+B):
<svg>
<!-- Masks seem to be pre-filled with black -->
<mask id="union">
<!-- Whatever is white in the mask will be visible of the shape the mask is applied to. -->
<rect id="A" x="20" y="20" width="50" height="100" stroke="white" fill="none"></rect>
<!-- Putting a second (, third, ...) shape into the mask will draw the shape over any existing shapes. -->
<circle id="B" cx="80" cy="60" r="30" stroke="white" fill="none"></circle>
<!-- Now we delete the inner part of the shapes by drawing black over them. -->
<rect id="Ainner" x="20" y="20" width="50" height="100" fill="black"></rect>
<circle id="Binner" cx="80" cy="60" r="30" fill="black"></circle>
</mask>
<!-- Applied to a full-screen red rectangle it cuts out the strokes from the mask. -->
<rect id="A+B" x="0" y="0" width="100%" height="100%" fill="red" mask="url(#union)"></rect>
</svg>

For everyone who wants both fill and border, here is a combination of devuxers and pascals posts
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<rect id="canvas" width="100%" height="100%" fill="white" />
<rect id="rect" x="50" y="60" width="40" height="70" />
<circle id="circle" cx="50" cy="50" r="40" />
<clipPath id="shape">
<use href="#rect" />
<use href="#circle" />
</clipPath>
<mask id="maskRect">
<use href="#canvas" />
<use href="#rect" />
</mask>
<mask id="maskCircle">
<use href="#canvas" />
<use href="#circle" />
</mask>
</defs>
<rect width="100%" height="100%" fill="red" clip-path="url(#shape)" />
<use href="#rect" stroke="green" stroke-width="2" fill="none" mask="url(#maskCircle)" />
<use href="#circle" stroke="green" stroke-width="2" fill="none" mask="url(#maskRect)" />
</svg>

With the feMorphology filter you can grow and shrink objects (MDN). Combining them, you can create a simply reusable outline: make an increased copy of the original, paint it, and place it under the shrunk original.
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">
<defs>
<filter id="outline">
<!-- Make a copy of the original,
grow it by half the stroke width,
call it outer -->
<feMorphology
operator="dilate"
radius="1"
result="outer"
/>
<!-- Make another copy of the original,
shrink it by half the stroke width,
call it inner -->
<feMorphology
in="SourceGraphic"
operator="erode"
radius="1"
result="inner"
/>
<!-- Create a color layer... -->
<feFlood
flood-color="black"
flood-opacity="1"
result="outlineColor"
/>
<!-- ...and crop it by outer -->
<feComposite
operator="in"
in="outlineColor"
in2="outer"
result="coloredOuter"
/>
<!-- Place the shrunk original over it -->
<feComposite
operator="over"
in="inner"
in2="coloredOuter"
/>
</filter>
</defs>
<g filter="url(#outline)">
<rect x="50" y="60" width="40" height="70" fill="red" />
<circle cx="60" cy="60" r="50" fill="red" />
</g>
</svg>
If you want to have it transparent, replace the operator in the last feComposite with xor:
<!-- Knock off the shrunk one -->
<feComposite
operator="xor"
in="inner"
in2="coloredOuter"
/>
The only caveat: fatter strokes - ie larger dilate and erode radii - on curves may look distorted, with 1px + 1px it is still fine.

Related

<use> - SVG: Stroke of Circle overriding stroke of use element

Stroke of both <use> element ignored here. The stroke color of <circle> is set blue which is appearing on both <use> element too. Why?
I want different strock color of all of 3 these element. but it is not working.
<svg width="300" class="svg-elem" viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">  
<circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
 
<use class="circle1" href="#myCircle" x="10" stroke="grey" fill="blue"/>
 
<use href="#myCircle" x="20" fill="white" stroke="red"/>
</svg>
Because circle still overriding the <use>.
You can consider using CSS variable to control stroke color like below.
<svg width="300" class="svg-elem" viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
<circle id="myCircle" cx="5" cy="5" r="4" style="stroke:var(--stroke, blue)"/>
<use class="circle1" href="#myCircle" x="10" style="--stroke:gray;" fill="blue"/>
<use href="#myCircle" x="20" fill="white" style="--stroke:red;"/>
</svg>

Move shape along parts of path

I am looking for a method to move an element along a path in stages kind of like a subway map. This is to illustrate position within a process. Any pointers toward libraries or techniques much appreciated.
To animate the movement of the metro train along the path, the animateMotion command will be used.
To create a motion path, you must load the motion diagram into the vector editor
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="50%" height="50%" viewBox="0 0 1000 945" preserveAspectRatio="xMinYMin meet" >
<image xlink:href="https://i.stack.imgur.com/0c7Aq.jpg " width="100%" height="100%" />
</svg>
Then in the vector editor you need to add node points
Then save the file in the * .svg format and copy thepath formula to another file
<!-- Train track -->
<path id="track" d="M55.2 119.6H911.7V397.1H294.5v460.1H911.7V626.5" style="fill:none;stroke-width:8;stroke:gray"/>
Next, we write the animation formula
<animateMotion
xlink:href="#train"
begin="svg1.click"
dur="14s"
fill="freeze">
<mpath xlink:href="#track" />
</animateMotion>
Below is the full code
Animation starts after a click.
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="50%" height="50%" viewBox="0 55 1000 945" preserveAspectRatio="xMinYMin meet" Style="border:1px solid gray;">
<style>
.s1 {
fill:white;
stroke:gray;
stroke-width:8;
}
</style>
<!-- Train track -->
<path id="track" d="M55.2 119.6H911.7V397.1H294.5v460.1H911.7V626.5" style="fill:none;stroke-width:8;stroke:gray"/>
<!-- Metro stations -->
<ellipse cx="58" cy="119" rx="27" ry="27" class="s1"/>
<ellipse cy="118" cx="911" ry="27" rx="27" class="s1" />
<ellipse cy="398" cx="910.3" ry="26.2" rx="26.9" class="s1" />
<ellipse cy="397.8" cx="294.5" ry="26.2" rx="26.9" class="s1" />
<ellipse cy="858" cx="294.5" ry="26.2" rx="26.9" class="s1" />
<ellipse cy="858" cx="911.7" ry="26.2" rx="26.9" class="s1" />
<ellipse cx="910" cy="628" rx="45" ry="45" fill="yellowgreen" stroke="gray" stroke-width="8" />
<g id="train_start " >
<circle cx="58" cy="119" r="55" stroke="dodgerblue" fill="yellowgreen" />
<circle cx="58" cy="119" r="27" stroke="dodgerblue" fill="white" />
</g>
<g id="train" >
<circle cx="0" cy="0" r="55" stroke="dodgerblue" fill="dodgerblue" />
<circle cx="0" cy="0" r="27" stroke="dodgerblue" fill="white" />
</g>
<animateMotion
xlink:href="#train"
begin="svg1.click"
dur="8s"
fill="freeze"
repeatCount="indefinite">
<mpath xlink:href="#track" />
</animateMotion>
</svg>

SVG - Center image in circle without image fill

I have created a circle in SVG and trying to display image in circle and when the circle size is increased the image is also increasing but I wanted to have image same as it is, changing the circle size without changing the image size, currently its filling in the circle:
Here is the HTML code, can somebody please help
<svg id="graph" width="100%" height="400px">
<!-- pattern -->
<defs>
<pattern id="image" x="0%" y="0%" height="100%" width="100%"
viewBox="0 0 512 512">
<image x="0%" y="0%" width="512" height="512" xlink:href="https://ppcaxis.com/cat-150.jpg"></image>
</pattern>
</defs>
<circle id="sd" class="medium" cx="5%" cy="40%" r="5%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
<circle id="sd2" class="medium" cx="25%" cy="50%" r="10%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
<circle id="sd3" class="medium" cx="25%" cy="80%" r="15%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
</svg>
Only challange for me here to NOT resize and fill image in circle.
The general answer to this question is that you need to use patternUnits="userSpaceOnUse" to make patterns be relative to the current coordinate system, rather than being relative to the object it is applied to.
For example, in the SVG below, I have set the pattern to be 200x200. Note that it is the same size no matter what the radius of each circle is. Also, it repeats independently of the siza and position of the circle also.
<svg id="graph" width="100%" height="400px">
<!-- pattern -->
<defs>
<pattern id="image" patternUnits="userSpaceOnUse" x="0" y="0" height="200" width="200"
viewBox="0 0 512 512">
<image x="0%" y="0%" width="512" height="512" xlink:href="https://ppcaxis.com/cat-150.jpg"></image>
</pattern>
</defs>
<circle id="sd" class="medium" cx="5%" cy="40%" r="5%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
<circle id="sd2" class="medium" cx="25%" cy="50%" r="10%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
<circle id="sd3" class="medium" cx="25%" cy="80%" r="15%" fill="url(#image)" stroke="lightblue" stroke-width="0.5%" />
</svg>
So if you wanted each circle to have an independently positioned cat pattern, you would need to define a pattern specifically for each circle.

How to rotate SVG inside mask?

I have an SVG mask that I would like to rotate the elements inside. However, when I attempted it, it looks like the entire mask is being rotated.
I tried at this SO post, but the fiddle wasn't working (the image wasn't showing anymore).
This is what it originally looks like
What happened
What I wanted to happen
I wanted only the "liquid" element to be rotated, but somehow the liquid element is being rotated the wrong way.
This is the SVG (with mask element):
<svg width="200px" height="200px" viewBox="0 0 200 200">
<defs>
<rect id="path-1" x="75" y="65" width="50" height="100" rx="5"></rect>
</defs>
<g id="bottle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="bottle-lid" stroke="#916548" stroke-width="5" x="82.5" y="52.5" width="35" height="15" rx="5"></rect>
<rect id="bottle-holder" stroke="#916548" stroke-width="5" x="117.5" y="55.5" width="30" height="7" rx="3.5"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Mask"></g>
<rect id="bottle-liquid" fill="#3498DB" mask="url(#mask-2)" x="40" y="80" width="120" height="120"></rect>
<rect id="bottle-outline" stroke="#916548" stroke-width="5" mask="url(#mask-2)" x="77.5" y="67.5" width="45" height="95" rx="5"></rect>
</g>
</svg>
And this is the JS (I am using a GSAP library)
const liquid = document.querySelector("#bottle-liquid")
const tl = new TimelineMax()
tl.to(liquid, 1, {
rotation: 20
})
How can I rotate just the a certain element inside a masked SVG?
I've made a few changes to your SVG, the most important: I've putted the bottle-liquid inside a g element and I'm applying the mask to the g element and the transform to the bottle-liquid. I hope it helps.
<svg width="200px" height="200px" viewBox="0 0 200 200">
<defs>
<rect id="path-1" x="75" y="65" width="50" height="100" rx="5"></rect>
<mask id="mask-2" fill="white">
<use xlink:href="#bottle-outline"></use>
</mask>
</defs>
<g id="bottle" stroke-width="5" stroke="#916548" fill="none" fill-rule="evenodd">
<rect id="bottle-lid" x="82.5" y="52.5" width="35" height="15" rx="5"></rect>
<rect id="bottle-holder" x="117.5" y="55.5" width="30" height="7" rx="3.5"></rect>
<g mask="url(#mask-2)">
<rect id="bottle-liquid" stroke="none" fill="#3498DB" x="40" y="80" width="120" height="120" transform="rotate(20 100 140)"></rect>
</g>
<rect id="bottle-outline" x="77.5" y="67.5" width="45" height="95" rx="5"></rect>
</g>
</svg>

SVG element not visible on screen, can see element added in console

I have the following SVG code. The SVG with id "nestedsvg" is being appended in the HTML, I can view it on the console. But it's not visible on the screen. I tried assigning it a z-index of 99 but still it's invisible. Where am I going wrong?
<svg data="BusinessRoleFigure" x="144" y="95"
width="128" height="66" id="outer" style="position: relative;">
<rect x="0" y="0" width="100%" height="100%"
stroke="rgb(178,178,126)" stroke-width="1" fill="rgb(255,255,181)"
style="position: relative;"></rect>
<svg id="nestedsvg" x="100%" height="100" width="50">
<rect x="-50" rx="5" ry="5" width="20" height="10" stroke="black"
stroke-width="1" fill="black" z-index="99"></rect>
</svg>
<circle cx="118" cy="13" r="5" fill="none"
stroke-linejoin="round" stroke="black"
z-index="1" stroke-width="1"></circle>
</svg>
Jsfiddle: http://jsfiddle.net/MxHPq/145/
This is because the rectangle you are drawing is outside of the nested SVG viewport.
That SVG has a width and height of 100x50, and you are drawing a 20x10 rectangle at (-50,0). Meaning the rectangle covers the area from (-50,0) to (-30,10). So it is not visible. By default, objects outside a nested SVG viewport are not visible.
There are two ways to fix this:
Make objects outside the viewport visible. You can do this by setting overflow="visible" on the nested SVG.
<svg data="BusinessRoleFigure" x="144" y="95" width="128" height="66" id="outer">
<rect x="0" y="0" width="100%" height="100%" stroke="rgb(178,178,126)" stroke-width="1" fill="rgb(255,255,181)"></rect>
<svg id="nestedsvg" x="100%" height="100" width="50" overflow="visible">
<rect x="-50" rx="5" ry="5" width="20" height="10" stroke="black" stroke-width="1" fill="black"></rect>
</svg>
<circle cx="118" cy="13" r="5" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></circle>
</svg>
Move the rectangle inside the SVG viewport and reposition the SVG so that the rectangle ends up in the same place.
I don't know why you wanted the nested SVG to be at x="100%", but you would need to change that if you go with this solution.
<svg data="BusinessRoleFigure" width="128" height="66" id="outer">
<rect x="0" y="0" width="100%" height="100%" stroke="rgb(178,178,126)" stroke-width="1" fill="rgb(255,255,181)"></rect>
<svg id="nestedsvg" x="78" height="100" width="50">
<rect x="0" rx="5" ry="5" width="20" height="10" stroke="black" stroke-width="1" fill="black"></rect>
</svg>
<circle cx="118" cy="13" r="5" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></circle>
</svg>
A few other notes about your original SVG:
x and y coordinates on the root <svg> element have no effect.
z-index currently has no meaning in SVGs. Although this may change for the upcoming SVG2 standard.
position: relative has no meaning in SVGs.
I've removed these things from my modified examples.

Categories

Resources