How to rotate SVG inside mask? - javascript

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>

Related

How to create a staggered square tiling pattern in svg?

I want to fill a rectangle space with a repeating image pattern. The images are arranged in a staggered square tiling. Two adjacent rows are staggered in the x-direction by half the width of the side of the square.
How to use the pattern element in svg to achieve this?
Here is the diagram of the result I want
I tried to achieve this shift but it did not work.
<svg width=1000 height=1000>
<pattern id="bar" width="200" height="200" patternUnits="userSpaceOnUse" patternTransform="rotate(0)">
<rect class="checker" x="0" width="100" height="100" y="0" fill="blue" />
<rect class="checker" x="100" width="100" height="100" y="0" fill="yellow" />
<rect class="checker" x="50" y="100" width="100" height="100" y="0" fill="red" stroke="red" />
<rect class="checker" x="150" y="100" width="100" height="100" y="0" fill="green" />
</pattern>
<rect width="100%" height="100%" fill=url(#bar)>
</svg>
The difficulty is that each of my squares is an image and it cannot be split in half. So I can't find the smallest element of this pattern.
Thank you in advance.
Something like this... You can replace the paths with images.
The trick is to draw one of the pieces in two separate halves allowing the pattern rect to clip each half.
<svg>
<pattern id="p" patternUnits="userSpaceOnUse" width="80" height="80">
<path transform="translate(8,8)" d="M17.37 17.07H6.57L4.24 24H3.01l8.23-24h1.52l8.23 24h-1.3zm-.39-1.13l-5-14.96-5.03 14.98h10.03Z"/>
<path transform="translate(48,8)" d="M17.37 17.07H6.57L4.24 24H3.01l8.23-24h1.52l8.23 24h-1.3zm-.39-1.13l-5-14.96-5.03 14.98h10.03Z"/>
<path transform="translate(28,48)" d="M17.37 17.07H6.57L4.24 24H3.01l8.23-24h1.52l8.23 24h-1.3zm-.39-1.13l-5-14.96-5.03 14.98h10.03Z"/>
<path transform="translate(68,48)" d="M17.37 17.07H6.57L4.24 24H3.01l8.23-24h1.52l8.23 24h-1.3zm-.39-1.13l-5-14.96-5.03 14.98h10.03Z"/>
<path transform="translate(-12,48)" d="M17.37 17.07H6.57L4.24 24H3.01l8.23-24h1.52l8.23 24h-1.3zm-.39-1.13l-5-14.96-5.03 14.98h10.03Z"/>
</pattern>
<rect width="200" height="200" fill="url(#p)"/>
</svg>

<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>

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

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.

Scale overriding SVG width and height attributes

I have an SVG element with a defined width and height, like <svg width="100px" height="100px"></svg>, filled with various elements.
I want to have a kind of "zoom" feature, where a particular region of the SVG is zoomed in on to fill the whole SVG element.
I planned to do this with the scale and translate attributes, i.e. by applying scale(x) to the SVG element and then calculating what I need to translate by in order to have the desired region remain visible.
I expected this would keep the SVG at 100x100px and simply hide any element outside this region. However, this doesn't happen; the whole SVG element just gets bigger instead, even though the dimensions are explicitly defined as attributes.
Clearly I'm misunderstanding the way that scaling and SVG dimensions work, does anyone know how I can achieve what I'm trying to do here?
You can warp the svg with a div element and use overflow: hidden.
<div style="width: 300px; height: 300px; overflow: hidden">
<svg width="100" height="100" style="transform: scale(4);">
<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
</div>
Do you mean something like this?
function setViewBox(vbx){
svg.setAttribute("viewBox",vbx)
}
<svg viewBox="0 0 100 100" width="200px" height="200px" id="svg">
<rect x="0" y="0" width="100" height="100" stroke="black" fill="white" onclick="setViewBox('0 0 100 100')"/>
<circle cx="25" cy="25" r="25" fill="red" onclick="setViewBox('0 0 50 50')"/>
<rect x="60" y="10" width="30" height="30" fill="green" onclick="setViewBox('50 0 50 50')"/>
<rect x="10" y="60" width="30" height="30" fill="blue" transform="rotate(45,25,75)" onclick="setViewBox('0 50 50 50')"/>
<path d="M50 100L75 50L100 100z" fill="yellow" onclick="setViewBox('50 50 50 50')"/>
</svg>
or more like that?
var last=null
function setTransform(evt,trs){
reset()
svg.appendChild(evt.target)
evt.target.setAttribute("transform","scale(2 2) translate("+trs+")")
last=evt.target
}
function reset(){
if(last) last.removeAttribute("transform")
}
<svg viewBox="0 0 100 100" width="200px" height="200px" id="svg">
<rect x="0" y="0" width="100" height="100" stroke="black" fill="white" onclick="reset()"/>
<circle cx="25" cy="25" r="25" fill="red" onclick="setTransform(event,'0 0')"/>
<rect x="60" y="10" width="30" height="30" fill="green" onclick="setTransform(event,'-50 0')"/>
<rect x="10" y="60" width="30" height="30" fill="blue" transform="rotate(45,25,75)" onclick="setTransform(event,'0 -50')"/>
<path d="M50 100L75 50L100 100z" fill="yellow" onclick="setTransform(event,'-50 -50')"/>
</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