GSAP: SVG not centering as expected - svg

2 questions about this CodePen
Why isn't the red balloon ending up centered on the crosshairs, given that I've set transformOrigin:"50% 50%"?
Why does the green balloon seem to have its origin set to "left top" when, according to this doc, it should default to "50% 50%"?
Relevant code (I think)
HTML
<svg class="container" fill="#f0c0c0" style="background: linear-gradient(to top, #ddfdff, #6dd5fa, #2980b9);
;" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200">
<g id="green-balloon">
<path … />
<path … />
<text …>🕊</text>
</g>
<g id="red-balloon">
<path …/>
<path …/>
<text …>⚡️</text>
</g>
<line x1="0" y1="100" x2="200" y2="100" stroke="white" stroke-width=".5px"/>
<line x1="100" y1="0" x2="100" y2="200" stroke="white" stroke-width=".5px" />
<defs>…</defs>
</svg>
JS
var redBalloon = $("#red-balloon");
var greenBalloon = $("#green-balloon");
var tl = new TimelineLite({onUpdate:updateSlider});
tl.set(greenBalloon, {x:100, y:200})
.set(redBalloon, {transformOrigin:"50% 50%", x:100,y:200})
.to(greenBalloon, 1, {scale:2, y:100})
.to(redBalloon, 1, {scale:2, y:100})
CSS
Not applicable.

Per OUSblake's answer on the GreenSock forums:
transformOrigin/svgOrigin affect scale, rotation, and skew. And svgOrigin uses the <svg> element's coordinate system. So svgOrigin: 50% 50% means everything is going to transform around 100,150 in the svg. transformOrigin: 50% 50% would be the center of the balloon. To center your element, use xPercent: -50 and yPercent: -50.
After providing a demo on Codepen, he continues:
It sounds like you were expecting transformOrigin to behave like in Adobe products, where changing the registration point causes the element to move. It doesn't, but that's what xPercent/yPercent are for. 😃
Just use those with a transformOrigin: 50% 50%, and everything should be pretty easy with curves.
He even went so far as to demonstrate motion along a path. Super helpful!

Related

Reversing the flip and rotation of an image fill in a path that is flipped and rotated

I am needing some help understanding how to "unflip/unrotate" and image fill in SVG for a path. When I fill a path with an image and then rotate and fill the path with an image, the image also flips and rotates. But I'd like to keep the image upright and non-flipped, regardless of the rotation and flipping. The size of the picture is the bounding box of the rotated shape.
So, for example, say I have this path and this picture:
If the path is only rotated (in this case, 315 degrees), it's easy to unrotate the image by just reversing the angle in the pattern that is used for a fill (i.e. 45 degrees).
<svg name="rotate only" x="0" y="0" width="100.08" height="200" overflow="visible" fill="url(#fillImages0sp15)" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25">
<defs>
<image id="bgImage" preserveAspectRatio="none" width="159.113" height="159.113" xlink:href="THE IMAGE URL"></image>
<pattern id="fillImages0sp15" x="-38.362" y="11.598" width="159.113" height="159.113" patternTransform="rotate(45,50.04,100)" patternUnits="userSpaceOnUse">
<use xlink:href="#bgImages0sp15"></use>
</pattern>
</defs>
<path d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z " transform="rotate(315,50.04,100)"></path>
</svg>
But if there any kind of flip on the path (horizontal, vertical, or both), it doesn't work by just reversing the transformation on the pattern used for the image fill. For example, if the image is rotated 315 degrees and flipped vertical, the path has transform="rotate(45,50.04,100) translate(0,200), scale(1,-1)" for flipping vertically. That works. But the image fill needs to get reset back to be upright and not flipped. So the patternTransform should just be the same transformation. But this isn't working. This is the result I get.
<svg name="flipV" x="0" y="0" width="100.08" height="200" overflow="visible" fill="url(#fillImages0sp14)" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25">
<defs>
<image id="bgImages" preserveAspectRatio="none" width="159.113" height="159.113" xlink:href="THE IMAGE URL"></image>
<pattern id="fillImages0sp14" x="-20.671" y="-370.711" width="159.113" height="159.113" patternTransform="rotate(45,50.04,100) translate(0,200) scale(1,-1)" patternUnits="userSpaceOnUse">
<use xlink:href="#bgImages"></use>
</pattern>
</defs>
<path d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z " transform="rotate(45,50.04,100) translate(0,200) scale(1,-1)"></path>
</svg>
Notice the path has transform="rotate(45,50.04,100) translate(0,200) scale(1,-1) and the fill pattern with the image has patternTransform="rotate(45,50.04,100) translate(0,200) scale(1,-1). This produces the wrong results.
In fact, here's all of it. This is what I'm hoping to achieve:
Does anyone know how to set the patternTransform so that it can "unflip/unrotate" the filled image? Is it that the translate in the patternTransform needs to be calculated differently?
Rather than filling the arrow, <svg ... fill="url(#fillImages0sp14)", transforming it and then trying to somehow separate it from its fill, untransform that, and then refill it, I'd just display the image, but masked by the transformed arrow.
I don't understand why the orange border still shows up. I've made the black rectangle overly large (which helped), and I've changed the overflow="...", but neither made it disappear.
Edit: Your global stroke attributes were messing up the mask. Moving them to the displayed arrow (the only thing using that stroke) fixed the orange border issue.
P.S. xlink is deprecated. Just use href.
P.P.S. I had to add a final translate to center the transformed arrow over the image. It's easier and more accurate to move the center of the arrow to the center of the image first, and then do your transformations about that center.
<svg name="transformed" x="0" y="0" width="159.113" height="159.113" overflow="visible" xmlns="http://www.w3.org/2000/svg">
<defs>
<path id="Arrow" d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z" />
<use id="TransformedArrow" href="#Arrow" transform="translate(38.3625,-29.2893) rotate(45,50.04,100) translate(0,200) scale(1,-1)" />
<mask id="ArrowMask">
<!-- Everything under black will be invisible -->
<rect x="0" y="0" width="100%" height="100%" fill="black" />
<!-- Everything under white will be visible -->
<use href="#TransformedArrow" fill="white" />
</mask>
</defs>
<image width="159.113" height="159.113" mask="url(#ArrowMask)" href="https://i.stack.imgur.com/yDcGi.png" />
<use href="#TransformedArrow" fill="none" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25" />
</svg>

Positioning rotated letters inside viewBox in svg

I have code which generates svg (by means of producing the XML DOM). It takes input text and randomly scatters it's letters on page as shown below.
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<!-- ... styles omitted -->
  <text x="20" y="150" rotate="45">M</text>
<text x="100" y="80" rotate="45">W</text>
<text x="90" y="50" rotate="270">X</text>
<!-- etc ... -->
</svg>
I have problem how to fill the page efficiently without having letters extend outside of the view box. Either I limit the random values for the x and y, but then there is lot of space left empty around borders. Or I use wider ranges for x,y but then the letters bleed outside. As show the red letters in the jsfiddle example (https://jsfiddle.net/5zqrugx1/1/).
I tried to adjust the x, y ranges based on rotation, but still it does not help much because of different letter shapes.
I am looking for way to style/position these letters in svg in a way which would force them to be completely inside the view port while being able to fill the space border-to-border (this second condition added later to clarify). Something like giving 0-100% where 0% would mean "touching left border" and 100% would be "touching right border". Is there any way to do it?
Below is example which I hand-edited to achieve more-less desired result.
What this probably amounts to is controling the center of rotation in such a way that it is in the center of the glyph. This way, all you need is a 0.5em padding at each edge.
You can start out with positioning the text control point at the middle both horizontally and vertically:
text {
text-anchor:middle;
dominant-baseline:middle;
}
Unfortunately, using the rotate attribute of the text element does not work as expected (at least in Firefox). But you can get around that by adding a post-rotation via a transform attribute. The best way to formulate it would be to also position the glyph with a translation:
<text transform="translate(40 100) rotate(60)">A</text>
Order is important - translate must come before rotate.
The following example rotates all glyphs around the center of the circles they are sitting in. It turns out the font-defined middle is a bit off, so you have to tweak with a dy attribute. If it shows still a bit wrong on your screen, this is because the font used by your system might define or compute a different middle line. For a system-independent experience, you would need to use a web font for you to have complete control.
circle {
fill: none;
stroke: blue;
}
text {
text-anchor: middle;
dominant-baseline: middle;
font-family: sans-serif;
font-size: 50px;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 100">
<circle r="25" cx="50" cy="50" />
<circle r="25" cx="100" cy="50" />
<circle r="25" cx="150" cy="50" />
<circle r="25" cx="200" cy="50" />
<circle r="25" cx="250" cy="50" />
<circle r="25" cx="300" cy="50" />
<text dy="4" transform="translate(50 50) rotate(60)">A</text>
<text dy="4" transform="translate(100 50) rotate(120)">B</text>
<text dy="4" transform="translate(150 50) rotate(180)">C</text>
<text dy="4" transform="translate(200 50) rotate(240)">D</text>
<text dy="4" transform="translate(250 50) rotate(300)">E</text>
<text dy="4" transform="translate(300 50) rotate(360)">F</text>
</svg>

possible to use calc() in svg line?

I'm not sure if this is the best approach. I'm trying to make an SVG that has parts that scale and parts that are fixed. It looks like this:
When the web page loads, I don't know what the height of the container for it will be but I know the width. I want the joining lines to scale based on the height, but keep the box with the plus centered like this:
I've played around with the line settings for x1, y1, etc., but I can't figure out a way to do it without resorting to javascript. Is SVG not the best option here? Here's what I have so far:
<svg class="s2">
<line x1="50%" y1="0" x2="50%" y2="10%" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- top joining line -->
<g id="square" x="50%" y="50%" width="16px" height="16px">
<line x1="5" y1="8" x2="11" y2="8" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- plus horizontal line -->
<line x1="8" y1="5" x2="8" y2="11" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- plus vertical line -->
<rect x="4" y="4" width="8" height="8" style="fill:transparent;stroke:rgba(0,0,0,.5);"></rect>
</g>
<line x1="50%" y1="90%" x2="50%" y2="100%" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- bottom joining line -->
<line x1="90%" y1="50%" x2="100%" y2="50%" style="stroke:rgba(255,0,0,.9);stroke-width:1px;"></line> <!-- right joining line -->
</svg>
Would javascript be my only option here? I tried using values like
calc(50% - 5px)
for the line positioning but it looks like it's not supported. If it was that would fix the problem.
For the solution, you have to combine two techniques:
masking parts of the lines, and
combine positioning in px with CSS translations in percentage
You start by positioning your rect centered on the coordinate origin, giving sizes in pixels. The joining lines are first simply drawn without interruption from -50% to +50%. Then the parts behind your central rect are masked out, the sizing again in px.
Finally, everything is moved by transform:translate(50%, 50&) to fill the SVG. It is important to note that this is the CSS transform property that can have units, while the SVG transform presentation attribute can only have unitless numbers. It therefore has to be written in a style attribute (or in a stylesheet).
#outermost {
transform:translate(50%, 50%);
}
g line {
stroke:rgba(255,0,0,.9);
stroke-width:1px;
}
g rect {
fill:none;
stroke:rgba(0,0,0,.5);
}
<svg xmlns="http://www.w3.org/2000/svg" class="s2" width="24" height="100">
<mask id="cp">
<rect x="-50%" y="-50%" width="100%" height="100%" fill="white"></rect>
<rect x="-6" y="-6" width="12" height="12" fill="black"></rect>
</mask>
<g id="outermost">
<g mask="url(#cp)">
<line x1="0" y1="-50%" x2="0" y2="50%"></line>
<line x1="0" y1="0" x2="50%" y2="0"></line>
</g>
<line x1="-3" y1="0" x2="3" y2="0"></line>
<line x1="0" y1="-3" x2="0" y2="3"></line>
<rect x="-4" y="-4" width="8" height="8"></rect>
</g>
</svg>

Why does adding a positive viewbox min-x, min-y value have a negative translate effect?

I was just going through THIS fiddle and the code looks like below:
<svg width=200 height=200 viewbox="0 0 225 225" >
<path d="M220, 220
A200, 200, 0, 0, 0, 20, 20
L 20, 220
Z"
fill = "lightskyblue">
</path>
</svg>
Now when i play around with the viewbox and change the value to viewbox="100 100 225 225" it has the effect of doing something like:
transform:translate(-100px, -100px);
Well i believe when i specify 100 as the min-x, min-y the values of viewbox the effect should have been something like
transform:translate(100px, 100px);
But instead the effect is something similar to:
transform:translate(-100px, -100px);
Why so ? can somebody explain ?
By setting minX and minY to 100, what you are doing is telling the SVG renderer that the top left of your SVG starts at (100,100). And that point should be at the top left of the SVG viewport.
It is the same as if you decided your ruler started at the 10cm mark. The 12cm mark would appear to be at 2cm instead of 12cm. In other words 10cm further left (lower).
Have a look at the following sample SVG. I've marked out an area which we will make set the viewport and viewBox to in a later example.
<svg width="600" height="600">
<!-- mark the area that will become the viewport -->
<rect x="100" y="100" width="300" height="200" fill="linen"/>
<!-- add some other content -->
<circle cx="120" cy="120" r="20" fill="red"/>
<circle cx="200" cy="200" r="50" fill="red"/>
<circle cx="380" cy="270" r="50" fill="red" fill-opacity="0.3"/>
</svg>
If we now set the viewBox to the cream coloured area and set the viewport (SVG width and height) correspondingly, you will see what happens.
<svg width="300" height="200" viewBox="100 100 300 200">
<!-- mark the area that will become the viewport -->
<rect x="100" y="100" width="300" height="200" fill="linen"/>
<!-- add some other content -->
<circle cx="120" cy="120" r="20" fill="red"/>
<circle cx="200" cy="200" r="50" fill="red"/>
<circle cx="380" cy="270" r="50" fill="red" fill-opacity="0.3"/>
</svg>
You can see that the small red circle which is roughly at 100,100, is now at the top left of the viewport.
Hope this makes it clearer for you.
Imagine you have a sheet of paper with your drawing on it and you overlay a piece of cellulite (or anything transparent) on top.
Draw a box on the cellulite and colour in everything outside the box.
Move the cellulite to the right.
Your drawing (the part you can still see within the cellulite box) appears to have moved to the left.
the viewBox is the cellulite box in this example.

SVG rendering (auto-bluring)

When i try to render this svg sample, the line is bluring automatically and has 2px height:
<svg xmlns="http://www.w3.org/2000/svg" width="500px" height="500px" >
<line x1="100" x2="400" y1="250" y2="250" stroke="black" />
</svg>
But when Y coordinates becomes 250.5, all is OK - line has 1px height.
It would be a solution (adding 0.5 pixels), but I need to use scale transform on elemets. On transformed elemets the problem again.
How to solve it?
Thanks.
By blurring you mean anti-aliasing. Try the crisp-edges rendering mode, e.g.:
<svg xmlns="http://www.w3.org/2000/svg" width="500px" height="500px" >
<line x1="100" x2="400" y1="250" y2="250" stroke="black" shape-rendering="crispEdges" />
</svg>

Resources