I have a SVG based app that makes heavy use of transformation such as translates, rotates and scales. While I have no issue in Firefox, in Chrome, the transform-origin property is not taken in account. It seems to apply the user-agent default value 0px 0px 0.
Here is an example (JSFiddle):
<svg width="400" height="400">
<defs>
<rect id="shape" width="200" height="200"/>
</defs>
<g transform="translate(100,100)">
<use xlink:href="#shape" style="stroke: lightgray; fill: transparent;"/>
<ellipse cx="100" cy="100" rx="3" ry="3" style="fill: black;"/>
<g transform="translate(0,0) scale(0.5) rotate(45)" style="transform-origin: 100px 100px;">
<use xlink:href="#shape" style="stroke: black; fill: transparent;"/>
</g>
</g>
</svg>
As you can see Chrome applies all transformation from top left corner of the shape regardless of the defined origin while Firefox respects the defined origin.
Am I missing something about how transform-origin works with SVG?
Does anyone actually found a way to fix this without compensating with translates?
I am answering to my own question in order to clarify entirely what is going on with transform-origin properties on the SVG 1.1 transform functions and how to overcome this issue in Chrome 48.
First of all, transform-origin is a pure CSS 3 property, it is not related to SVG 1.1 at all. Despite the fact that transform sounds a lot like transform-origin, they apply to different systems. transform exists in both CSS 3 and SVG 1.1 but have separate implementations. transform-origin only exists in CSS 3 and therefore it is not supposed to influence SVG 1.1. The fact that transform-origin has no influence on SVG in Chrome 48 is expected.
So why transform-origin does apply to SVG in Firefox 44? Well the reason is not exactly clear, but it seems that it is part of the ongoing effort from Mozilla to slowly bring support for SVG 2 in Firefox. Indeed with SVG 2, everything will become a CSS 3 transform (no separate implementation) and SVG will therefore get support for transform-origin. I found out about this in the excellent article about the SVG coordinate systems from Sara Soueidan.
Now how can that be overcome in Chrome 48. It is fairly simple but if you want to apply translate(), scale() and rotate() all the same time, you will still need to calculate the offset induced by the scaling and compensate it in your translation.
As Bobby Orndorff mentioned in his answer, it is actually possible to provide the center of rotation to the rotate() function by providing extra x and y parameters. This is already a great improvement. But unfortunately the scale() function does not support such a thing and will always scale from the top left corner of its parent. Therefore you will still have to correct your translation in order to simulate a scale around a center.
Here is the final solution that works on Chrome 48 and Firefox 44:
<svg width="400" height="400">
<defs>
<rect id="shape" width="200" height="200"/>
</defs>
<g transform="translate(100,100)">
<use xlink:href="#shape" style="stroke: lightgray; fill: transparent;"/>
<ellipse cx="100" cy="100" rx="3" ry="3" style="fill: black;"/>
<g transform="translate(50,50) scale(0.5) rotate(45, 100, 100)">
<use xlink:href="#shape" style="stroke: black; fill: transparent;"/>
</g>
</g>
</svg>
The example is mixed CSS transform-origin with a SVG transform. While CSS transform and SVG transform are similar, there are differences. For example, CSS transform can be 2D and 3D while SVG transform is only 2D. CSS transform rotate function accepts the angle as a number combined with an unit (e.g. degs, grad, rad, turn) while SVG transforms accepts the angle as a number (with implied unit of degrees) along with optional second and third parameters (x, y) representing the origin of rotation.
To get the example to work in FireFox and Chrome, you could use a CSS transform instead of a SVG transform. For example...
<svg width="400" height="400">
<defs>
<rect id="shape" width="200" height="200"/>
</defs>
<g transform="translate(100,100)">
<use xlink:href="#shape" style="stroke: lightgray; fill: transparent;"/>
<ellipse cx="100" cy="100" rx="3" ry="3" style="fill: black;"/>
<g style="transform: translate(0,0) scale(0.5) rotate(45deg); transform-origin: 100px 100px;">
<use xlink:href="#shape" style="stroke: black; fill: transparent;"/>
</g>
</g>
</svg>
To get the example to work in FireFox, Chrome, and IE, you could use the SVG transform rotate function with the optional second and third parameters instead of a CSS transform-origin. For example...
<svg width="400" height="400">
<defs>
<rect id="shape" width="200" height="200"/>
</defs>
<g transform="translate(100,100)">
<use xlink:href="#shape" style="stroke: lightgray; fill: transparent;"/>
<ellipse cx="100" cy="100" rx="3" ry="3" style="fill: black;"/>
<g transform="translate(0,0) scale(0.5) rotate(45,200,200)">
<use xlink:href="#shape" style="stroke: black; fill: transparent;"/>
</g>
</g>
</svg>
Related
I am working a project by use SVG viewBox, this is a html snippet.
<body>
<svg width="960" height="960" viewBox="18897158,-480,960,960">
<g>
<circle cx="18897482" cy="-94" r="3" fill="#18ff00"></circle>
<circle cx="18897837" cy="-102" r="3" fill="#18ff00"></circle>
<line x1="18897482" y1="-94.00" x2="18897837" y2="-102" stroke-width="1" stroke="#18ff00">
</line>
<text x="18897660" y="-98" fill="#18ff00" dominant-baseline="middle"
text-anchor="middle">0.00094mm</text>
</g>
</svg>
</body>
When this html in Chrome, the line element does not display correctly, but it works in Edge. I want to know is this a bug in Chrome?
I have animated the SVG, to give a handwriting effect, I want to make the animation more seamless, as the letter "W" appears in the animation, it makes the other part of the letter appear as well, and I can't decrease the stroke-width as the rest of the part of the letter will not appear completely, please guide me along on what can be done.
As I cant upload the code here its SVG and exceeding more than 35,000 characters, so please do check the CodePen.
HTML Code
<defs>
<clipPath id="clip-path" transform="translate(0 0)">
<path id="w" d="M47.205,44.721c-.1.062-.191.119-.262.169a1.172,1.172,0,0,0-.192.17,1.237,1.237,0,0,0-.155.208,1.71,1.71,0,0,0-.13.285L42.677,55.6q-.354-.956-.708-1.9-.307-.8-.655-1.694t-.623-1.6L38.8,55.6Q37.7,52.9,36.825,50.73q-.37-.925-.739-1.818c-.247-.6-.467-1.134-.662-1.618s-.357-.883-.485-1.2-.2-.508-.223-.57a1.835,1.835,0,0,0-.293-.47,1.326,1.326,0,0,0-.539-.332H36.7a.519.519,0,0,0-.355.4.832.832,0,0,0,.093.539l2.836,7.18,1.077-3.3q-.215-.57-.493-1.247t-.532-1.279c-.17-.4-.313-.747-.432-1.04s-.187-.464-.208-.516a1.529,1.529,0,0,0-.223-.409,1.143,1.143,0,0,0-.532-.332h2.711a.5.5,0,0,0-.315.4,1.016,1.016,0,0,0,.07.539l.817,2.033.8-2.2a.886.886,0,0,0,.068-.479q-.039-.2-.347-.293h1.633a2.841,2.841,0,0,0-.254.17,1.007,1.007,0,0,0-.169.161,1.148,1.148,0,0,0-.124.185,2.582,2.582,0,0,0-.116.254l-1.155,3.034,1.556,4.313L45.652,45.6a1.01,1.01,0,0,0,.062-.533.513.513,0,0,0-.355-.346h1.849Z" style="fill: none" />
</clipPath>
</defs>
<g id="w-grp">
<g style="clip-path: url(#clip-path)">
<polyline class="logo-path m-1" id="w-path" points="35.084 43.989 39.663 55.599 38.796 55.599 38.609 55.136 42.916 43.989 38.968 43.989 43.484 55.599 42.677 55.597 42.474 55.101 46.67 43.989" style="fill: none;stroke: #191717;stroke-miterlimit: 10;stroke-width: 3px" />
</g>
</g>
CodePen: https://codepen.io/ToxifiedM/pen/MWKeERr
Linked Question 1: A JQuery Function For SVG, To Execute 2nd Animation As Soon As, 1st Animation Completes?
Linked Question 2: To Control SVG CSS Based Animation Using Jquery?
Linked Question 3: To Control The Speed Of Multiple SVG Elements Using Jquery?
This is how I would do it:
I'm using a polyline element that I am clipping with a V like path. In order to make the W I'm using the clipped polyline twice. I am animating the stroke-dashoffset of the use elements, the second element with a 1s delay
svg{width:300px;border:solid}
use{
stroke-dasharray: 255;
stroke-dashoffset: 255;
animation: dash 1s linear forwards;
}
use:nth-of-type(2){
animation-delay:1s
}
#keyframes dash {
to {
stroke-dashoffset: 0;
}
}
<svg viewBox="30 80 250 150">
<defs>
<clipPath id="clip">
<path id="V" xmlns="http://www.w3.org/2000/svg" d="M52,90L103,210 110,210 160,90 152,90 110,190 66,90 52,90" stroke="black" fill="none" />
</clipPath>
<polyline id="poly" points="58,85 107,203 156,85" stroke="red" fill="none" stroke-width="19" clip-path="url(#clip)" />
</defs>
<use xlink:href="#poly" x="0" />
<use xlink:href="#poly" x="50" />
</svg>
I am trying to create a SVG circle with an image inside of it. The image is supposed to be inside of the SVG circle with a radius that is slightly below the radius of the outer circle. To clip the image I am using the <clipPath> element on the <image> element. But is won't clip the right path. Here is a Codepen to give an example:
https://codepen.io/Martin36/pen/BdpMbX
As you can see in the example the <clipPath> clips the upper left corner of the image even though the clipping <circle> is placed directly above the <image> element. Here is the code:
<svg width="900" height="300" >
<g class="hotel" transform="translate(150,150)">
<circle r="120" style="fill: rgb(56, 255, 0); opacity: 0.6;" class=""></circle>
<clipPath id="clipCircle">
<circle r="100"></circle>
</clipPath>
<image xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="http://res.cloudinary.com/martin36/image/upload/v1502102349/hotel_yg1fg1.jpg"
clip-path="url(#clipCircle)"
width="250" height="250"
transform="translate(-125,-125)">
</image>
</g>
</svg>
Does anyone know why this happens and how to fix it?
I solved the problem by changing the cx and cy properties of the <circle> element inside the <clipPath> tag to the widthof the image divided by two. The codepen is updated with the correct code. But I will post it here also:
<svg width="900" height="300" >
<g class="hotel" transform="translate(150,150)">
<circle r="120" style="fill: rgb(56, 255, 0); opacity: 0.6;"></circle>
<clipPath id="clipCircle">
<circle r="100" cx="125" cy="125"> </circle>
</clipPath>
<image xmlns:xlink="http://www.w3.org/1999/xlink"
xlink:href="http://res.cloudinary.com/martin36/image/upload/v1502102349/hotel_yg1fg1.jpg"
clip-path="url(#clipCircle)"
width="350" height="250"
transform="translate(-125,-125)">
</image>
</g>
</svg>
I have the following simple example. It is stored in image.svg:
<svg>
<defs>
<g id="shape">
<circle cx="100" cy="100" r="100" />
</g>
</defs>
</svg>
However, putting this code in a HTML file doesn't load anything. Why is that?
<svg>
<use xlink:href="#shape" x="10" y="10" />
</svg>
What am I doing wrong? I can't seem to make it work.
If you are using elements from another document, you have to specify the document!
<use xlink:href="#shape" x="10" y="10" />
This means "use the #shape element from the current document".
To import from another document, you need to put the reference to the SVG file in the xlink:href attribute:
<use xlink:href="image.svg#shape" x="10" y="10" />
Obviously you need to check the path is correct here. Note that this is not supported in any version of Internet Explorer, though polyfills are available.
For external svg files you need the namespace ... and I have added a fill to render the circle otherwise it will be transparent:
<svg xmlns="http://www.w3.org/2000/svg" >
<symbol id="shape" width="200" height="200" viewbox="0 0 200 200">
<circle cx="100" cy="100" r="100" fill="currentColor" />
</symbol>
<text y="20">Symbol above will not render unless referenced by use element</text>
</svg>
Then when you reference it you need to use the correct namespace for xlink:
svg.defs-only {
display:block; position: absolute;
height:0; width:0; margin: 0; padding: 0;
border: none; overflow: hidden;
}
svg {
color: orange;
stroke: red;
}
.purple {
color: purple;
stroke: black;
}
<svg class="defs-only" xmlns="http://www.w3.org/2000/svg" >
<symbol id="shape" width="50" height="50" viewbox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="currentColor" stroke="inherit" />
</symbol>
</svg>
<svg xmlns:xlink="http://www.w3.org/1999/xlink">
<use xlink:href="#shape" x="10" y="10" />
<use xlink:href="#shape" x="80" y="10" class="purple" />
</svg>
If you are referencing an external file, you need to put the filename before the # e.g. image.svg#shape making sure you get the path correct of course.
Note, not all browsers support fragment identifiers - notably IE and Edge - you need to use a javascript polyfill like svg4everybody for those browsers.
Workaround - use svg inline only
You need to have the use-tag inside the SVG with the shape you want to use:
<svg>
<defs>
<g id="shape">
<circle cx="100" cy="100" r="100" />
</g>
</defs>
<use xlink:href="#shape" x="10" y="10" />
</svg>
SVG 2 (when implemented in browsers) will allow to reference another SVG file without any fragment identifier:
New in SVG 2: An href without a fragment allows an entire SVG document to be referenced without having to ensure that it has an ID on its root element.
Before (SVG 1.1):
<!-- my-vector.svg -->
<svg id="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle r="10" cx="12" cy="12" />
</svg>
<use href="my-vector.svg#icon"></use>
After (there will be no need to define id="..." on the svg):
<!-- my-vector.svg -->
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<circle r="10" cx="12" cy="12" />
</svg>
<use href="my-vector.svg"></use>
SVG 2 seems to be in the process of development in major browsers (see this Chrome feature and specifically this Chromium issue: Issue 366545: [SVG2] Allow to reference entire files).
Hello, I'm using slick slider and I want to clip-path the container but the clip-path doesn't work well..
this is my svg path
<svg width="0" height="0">
<defs>
<clipPath id="mask1">
<path id="curve" d="m242.6,393.7c-82.2,-4.7 -138.1,-15.4 -191.2,-36.6 -19.3,-7.7 -36.4,-16.1 -47.7,-23.5l-3.2,-2.1 -0,-71.5c-0,-39.3 -0.1,-113.9 -0.3,-165.8l-0.3,-94.2 371.1,0 371.1,0 0,152 0,152 -5.8,3.7c-7.8,5 -14.5,8.8 -23.4,13.4 -70.4,36.3 -187.2,62.5 -317.4,71.2 -28.6,1.9 -31.6,2 -91.2,1.9 -31,-0 -58.8,-0.2 -61.9,-0.4z">
</path>
</clipPath>
</defs>
</svg>
the slider works but the clip-path doesn't fit the 100% window..
Thanks.. and sorry for my English..
You should convert your clipPath to one using bounding box units:
<clipPath clipPathUnits="objectBoundingBox" ... >
When you use bounding box units, all your clip path coordinates should be defined in the range 0..1.
http://www.w3.org/TR/SVG/masking.html#EstablishingANewClippingPath
If you do this, the clipping path will be given the same size as the the element you apply it to.
img {
width: 100%;
clip-path: url(#mask1);
-webkit-clip-path: url(#mask1);
}
<svg width="0" height="0">
<defs>
<clipPath id="mask1" clipPathUnits="objectBoundingBox">
<path id="curve"
transform="scale(0.00135, 0.00254)"
d="m242.6,393.7c-82.2,-4.7 -138.1,-15.4 -191.2,-36.6 -19.3,-7.7 -36.4,-16.1 -47.7,-23.5l-3.2,-2.1 -0,-71.5c-0,-39.3 -0.1,-113.9 -0.3,-165.8l-0.3,-94.2 371.1,0 371.1,0 0,152 0,152 -5.8,3.7c-7.8,5 -14.5,8.8 -23.4,13.4 -70.4,36.3 -187.2,62.5 -317.4,71.2 -28.6,1.9 -31.6,2 -91.2,1.9 -31,-0 -58.8,-0.2 -61.9,-0.4z">
</path>
</clipPath>
</defs>
</svg>
<img src="//placekitten.com/400/150"/>
What I have done in the example above is to use a transform to scale the clip path to size 1x1. It's easier than recreating the shape with new coordinates.