Related
Trying to replicate an SVG gradient with canvas drawing in javascript i came across this use case:
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="760"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 380 380">
<defs>
<linearGradient id="three_stops_4" gradientTransform="rotate(90)">
<stop offset="0%" style="stop-color: #ffcc00;"/>
<stop offset="33.3%" style="stop-color: #cc6699; stop-opacity: 1"/>
<stop offset="100%" style="stop-color: #66cc99; stop-opacity: 1"/>
</linearGradient>
<polygon id="base" points="5,5 35,5 35,40 5,40 5,35 30,35 30,10 5,10" />
<rect width="35" height="80" id="base2" />
</defs>
<polygon transform="translate(0, 45)" points="5,5 80,5 80,40 5,40 5,35 75,35 75,10 5,10" style="fill: url(#three_stops_4); stroke: black;"/>
</svg>
That render like this:
Now those are the specs:
https://www.w3.org/TR/SVG11/pservers.html#LinearGradientElementGradientUnitsAttribute
What i do not understand is if:
1) the object bounding box transformation is applied to the gradient coordinates and then the gradient transform is applied.
OR
2) the gradientTransform is applied to the object bounding box and the the gradient coordinates are transformed.
The specs seems to say to take in the consideration the first option, but the point is that while the gradient should stretch from the whole width of the object, i clearly see it in the rendering that being rotated it stretches for the height of the object.
There is an additional stretching ( compression ) that seems to come from the object bounding box aspect ratio, applied after the rotation.
Does anyone know how exactly the renderer should behave?
It says in the spec (under gradientTransform):
This additional transformation matrix is post-multiplied to (i.e., inserted to the right of) any previously defined transformations, including the implicit transformation necessary to convert from object bounding box units to user space.
So depending on how you like to think about matrix multiplication, both answers are correct. In my way of thinking about it (transforms are applied right to left) #2 is correct. The gradient transform is applied first. Then the objectBoundingBox transform is applied. In pseudo code:
ctx.transform(gradientTransform)
ctx.transform(objectBoundingBoxTransform)
But it also depends on what 2D rendering library you are using, and whether you are using pre- or post- multiplication. Some rendering libraries let you set a transform matrix on the gradient object. For example, in Android you can do:
LinearGradient grad = new LinearGradient(...);
grad.setLocalMatrix(gradientTransformMatrix);
The accepted answer is the one that guided me to the solution.
The problem was translating some SVG peculiarities to JS canvas rendering.
That is hard and sometimes not possible at all with simple gradients.
For gradients that get applied to a filled shape, and that are boundingbox units and also have a gradientTrasnfrom attribute, you should:
var t = myGradientTransform;
MakeAPathForMyShape(ctx);
ctx.transform(width, 0, 0, height, minX, minY);
ctx.transform(t[0], t[1], t[2], t[3], t[4], t[5], t[6]);
ctx.fill();
This works most of the time if you are using ctx.fill, does not work if you are using fillRect or fillText, because you cannot put transformations in the middle of the path traced and the fill operation.
Now let's talk of the stroke.
If you use the code above with stroke, you are going to apply the gradient transform to your strokeWidth, thing that maybe you do not want to.
If the scaling is not uniform between X and Y, you cannot compensate with changing ctx.strokeWidth;
A solution i had to fallback to, is to create a pattern and apply that.
So i calculate a bounding box for my shape, and i create a canvas with the same size.
Then make a path as big as the canvas, using its perimeter, and i use the above code on that context.
At this point i have a rect, as big as my shape, with the transformed gradient applied over.
Now i create a pattern from it:
ctx.strokeStyle = ctx.createPattern(tmpCanvas, 'no-repeat');
ctx.stroke();
in this way i can apply the transform to the gradient and not to the stroke size.
In the research i also evaluated the idea of transforming the normal of the gradient and find back again the points that would generate a transformed gradient in a non transformed space. This can work for linear gradients, but not for radial gradients, that get an ellipse shape and do need a classic trasnform to work.
I'd like to do an animation to an element when hovering it.
As I do use svg-elements for both situations (standard and hover-state) I guess I must somehow manipulate the first svg-element when hovering it by editing the svg-code inline.
I basically'd need a starting point there:
How would I "redraw" in an animated manner the hover-image and not just swap it?
Do I need a 3rd party library (which)?
If I had multiple of these situations, how would I keep my code clean by not having 10 svg-codes inline within my html-source?
Thanks for your answer(s)!
The code for the svg-image(s) is here
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 800">
<defs><style>.cls-1,.cls-2{fill:none;stroke:#000;}.cls-1{stroke-miterlimit:10;stroke-width:7px;}.cls-2{stroke-linejoin:bevel;stroke-width:5px;}</style></defs>
<title>arrows_demoZeichenfläche 1</title>
<line class="cls-1" x1="325.5" y1="333" x2="325.5" y2="539"/>
<polyline class="cls-2" points="242 455.67 325.75 539.42 409.42 455.75"/><path class="cls-1" d="M670.5,135.79c0,11.62-8,11.73-8,23.35s8,11.68,8,23.3-8,11.65-8,23.28,8,11.64,8,23.26-8,11.63-8,23.25,8,11.63,8,23.25-8,11.63-8,23.25,8,11.63,8,23.25-8,11.62-8,23.25,8,11.62,8,23.25-8,11.63-8,23.25,8,11.63,8,23.25-8,11.62-8,23.25,8,11.62,8,23.25-8,11.63-8,23.25,8,11.63,8,23.25v31"/>
<polyline class="cls-2" points="587 455.67 670.75 539.42 754.42 455.75"/></svg>
You could use JavaScript to manipulate the value of the points attribute, but such changes would be sudden, so the change would look like a stop motion film.
Like this Codepen, what you could do is give a path element a stroke-dasharray that is equal to the getTotalLength of the path in order to "erase" the straight line (of the arrow) off the page, then quickly switch the value of the d attribute, and then "redraw" the line back onto the page?
However, I don't believe that's what you're looking for. I believe HTML5 Canvas, with my limited knowledge about it, would be the more feasible option for what you're trying to accomplish.
Actually, I guess it might be possible using a CSS3 3D transform, like so. The problem, however, is that the line doesn't have any depth, so when you initially set rotateX(90deg) on the path in CSS the line becomes invisible instead of appearing as a straight line...
Unsure what's going on with my SVG icons since updating to the latest Chrome. They still appear to display correctly in Safari and Firefox the only other browsers that I am supporting. Was working perfectly in prior versions of Chrome I've been testing with.
Appears that there are two or more path elements inside the clipPath element in the examples where the icons are not rendering as expected. According to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath I can place any number of allowed elements inside the clipPath so I believe that I am doing this right... so lost as to why it suddenly has broken down on me.
So basically if you look at the images you will see the kill cockroaches and the eye ball look different than expected on the left to how they are now rendering on the right hand side...
http://codepen.io/dapinitial/pen/Kflpv
<svg>
<defs>
<clipPath id="diff-path">
<path d="M17.19,7.349 C18.753,7.349 20.019,8.626 20.019,10.202 C20.019,11.777 18.752,13.056 17.19,13.056 C15.628,13.056 14.361,11.778 14.361,10.202 C14.361,8.625 15.627,7.349 17.19,7.349 L17.19,7.349 L17.19,7.349 Z" />
<path d="M0.19,10.201 C0.19,10.201 6.833,20.152 17.19,20.152 C27.547,20.152 34.19,10.201 34.19,10.201 C34.19,10.201 27.414,0.249 17.19,0.249 C6.966,0.249 0.19,10.201 0.19,10.201 L0.19,10.201 Z M10.125,10.201 C10.125,6.269 13.288,3.082 17.19,3.082 C21.091,3.082 24.254,6.269 24.254,10.201 C24.254,14.132 21.091,17.32 17.19,17.32 C13.288,17.32 10.125,14.132 10.125,10.201 L10.125,10.201 Z"></path>
</clipPath>
</defs>
</svg>
You need to specify a different "clip-rule" attribute for your clip-path. If I add clip-rule="evenodd" to your clip-path element it fixes things.
I understand that fill is applied after transform, but I don't understand why. I've looked in the spec and cannot find information about why this might be.
I'd like to know if there is a way to have the fill applied to a group of elements, some using transform: rotate in a consistent manner. To illustrate the problem see the example code below:
<svg width='38' height='18' viewBox='0 0 38 18' xmlns='http://www.w3.org/2000/svg'>
<defs>
<linearGradient id='gradientFill' x2='0' y2='100%' gradientUnits='userSpaceOnUse'>
<stop stop-color='hsl(0, 0%, 0%)' />
<stop offset='100%' stop-color='hsl(0, 0%, 100%)' />
</linearGradient>
<path id='shape' d='M0,0m9,0c-3.92,0,-7.24,2.51,-8.48,6h3.29c0.83,-1.92,2.8,-3.56,5.19,-3c2.52,0.59,3,3.55,3,6c0,3.6,-0.6,6,-3,6s-3,-2.4,-3,-6h-6c0,4.97,4.03,9,9,9c4.97,0,9,-4.03,9,-9s-4.03,-9,-9,-9z'/>
<g id='test'>
<use xlink:href='#shape'></use>
<use transform='translate(20,0) rotate(180,9,9)' xlink:href='#shape'></use>
</g>
</defs>
<use class='logo' xlink:href='#test' fill="url(#gradientFill)"></use>
</svg>
As you see, on the non-rotated element the gradient runs top to bottom, as expected. However on the rotated element the gradient is also rotated so that it runs from bottom to top. This seems inconsistent to me, as the fill is applied to a group and not the individual objects.
My ultimate desire is to have the linear gradient flow from top to bottom on both shapes, but I'd like to continue using the use and transform method, if possible.
Can someone point me in the right direction in regards to the spec, and also suggest a solution to accomplish my goal?
Attributes like "fill" are not commands that operate on an object, they are properties that are used by the object when rendering and are inherited by an object's children. Just the same as CSS rules.
To achieve the effect you want, use Michael's solution, or create a second gradient that has it's x,y and x2,y2 flipped to match the 180 degree rotation.
That you apply transforms first is stated in the SVG specification for gradients when it talks about gradientUnits...
If gradientUnits="userSpaceOnUse", ‘x1’, ‘y1’, ‘x2’ and ‘y2’ represent values in the coordinate system that results from taking the current user coordinate system in place at the time when the gradient element is referenced (i.e., the user coordinate system for the element referencing the gradient element via a ‘fill’ or ‘stroke’ property) and then applying the transform specified by attribute ‘gradientTransform’.
If you apply a transform to the element it changes the element's co-ordinate system and that therefore affects the gradient too.
You should define your shape as a clip-path and then apply it to a rect that is filled with the gradient you want. You could also do it with a filter, although that's a little more complicated.
I want to put two filters on my circle. (Maybe more)
I've tried doing:
filter="url(#f1);url(#f2)"
And:
filter="url(#f1,#f2)"
And:
filter="url(#f1 #f2)"
But none of them work. How can I approach this problem?
You could add multiple effects in one filter, however if you want to stack the filters up, first group the object and then apply the other filter to your object.
<g filter="url(#f2)">
<rect width="90" height="90" stroke="green" stroke-width="3" fill="yellow" filter="url(#f1)"/>
</g>
I know this is a very old question, but for people referencing this, you can also just separate your filter functions with spaces - filter="url(#f1) url(#f2)" should work
See: https://css-tricks.com/almanac/properties/f/filter/
Start with an identity feColorMatrix and name the result currentGraphic for example. Use that as 'in' for each filter element bundle/transaction start and as 'result' for the final operation of the bundle. The next element bundle picks it up at 'in'='currentGraphic', etc.