SVG patterns scaling the way pattern is not cut off - svg

I'm trying to discover the way to scale svg patterns so that the pattern is not cut by the bounds of the element using it. And yet so that pattern is beeing repeated to cover that element.
Let's say i have a 20px radius circle as a pattern.
Applying it to 80px width element:
Now the element is beeing scaled to 90px, the pattern should stretch horizontally (no need to keep aspect ratio):
And at last, scale up above 90px adds another circle occurance (95px width in this example - aspect ratio is still ignored):
I can add some scripting to achieve that effect. But i'm curious about if i can get that behavior with pure SVG.
<svg style='display: block; height: 60px; width: 95px; '>
<defs>
<pattern patternUnits='userSpaceOnUse' viewBox='0 0 20 20' width='20' height='20' id='the_pattern'>
<circle cx='10' cy='10' r='10' stroke-width='0' fill='black'></circle>
</pattern>
</defs>
<rect x='0' y='0' width='80' height='20' fill='url(#the_pattern)'></rect>
<rect x='0' y='20' width='90' height='20' fill='url(#the_pattern)'></rect>
<rect x='0' y='40' width='95' height='20' fill='url(#the_pattern)'></rect>
</svg>

The answer is no. You cannot achieve that with pure SVG. Unless by "pure SVG" you include SVGs that have embedded Javascript (ie <script> elements).
You could do it with CSS border-image though.
https://developer.mozilla.org/en-US/docs/Web/CSS/border-image
Here's an example:
Ignore the fact that I've used a diamond image here. Just replace it with a circle version of the border image file. :)
.circle-border {
border-top: 30px solid;
border-image: url('https://interactive-examples.mdn.mozilla.net/media/examples/border-diamonds.png') 30 / 20px 0 0 0;
border-image-repeat: round;
}
div {
width: 80px;
height: 0px;
margin-bottom: 50px;
}
div:nth-child(2) {
width: 90px;
}
div:nth-child(3) {
width: 100px;
}
<div class="circle-border"></div>
<div class="circle-border"></div>
<div class="circle-border"></div>

Idea
You set the dimension of the container elements in 2 passes.
Pass 1:
Set the width of the container element to the value such that the desired number of pattern elements would just fit in horizontally. That would be 80 for the first two cases and 100 for the last.
Pass 2:
Scale the container elements horizontally to the actual target widths ( 80, 90, 95 ).
In the code example below, the transform attribute specifies the scaling. Note that the translate verbs first shift the element position to the origin before scaling is applied and revert the translation thereafter (the verbs inside the `translate attribute are evaluated from right to left).
The x scaling factor is the quotient of target width and specified width, y just leaves the height as it is.
SVG Sample
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="8cm" height="4cm" viewBox="0 0 800 500" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="circlePattern" patternUnits="userSpaceOnUse"
x="0" y="0" width="20" height="20"
>
<circle cx="10" cy="10" r="10" fill="black" stroke="black" stroke-width="0"/>
</pattern>
</defs>
<!-- 1. base case 80 px width -->
<rect fill="url(#circlePattern)" stroke="white" stroke-width="0"
x="100" y="200" width="80" height="20"
/>
<!-- 2. scale width to 90px -->
<rect fill="url(#circlePattern)" stroke="white" stroke-width="0"
x="100" y="300" width="80" height="20"
transform="translate(100, 300) scale (1.125, 1.0) translate(-100, -300)"
/>
<!-- 3. scale width to 95px -->
<rect fill="url(#circlePattern)" stroke="white" stroke-width="0"
x="100" y="400" width="100" height="20"
transform="translate(100, 400) scale (0.95, 1.0) translate(-100, -400)"
/>
</svg>
Inline Sample
The SVG parts pertaining to the question is the same as above.
.showcase {
/* background-image: url('#glyph');
background-size:100% 100%;*/
filter: url(#embedded);
}
.showcase:before {
display:block;
content:'';
color:transparent;
}
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="embedded" width="16cm" height="8cm" viewBox="0 0 800 500" version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<pattern id="circlePattern" patternUnits="userSpaceOnUse"
x="0" y="0" width="20" height="20"
>
<circle cx="10" cy="10" r="10" fill="black" stroke="black" stroke-width="0"/>
</pattern>
</defs>
<!-- 1. base case 80 px width -->
<rect fill="url(#circlePattern)" stroke="white" stroke-width="0"
x="100" y="200" width="80" height="20"
/>
<!-- 2. scale width to 90px -->
<rect fill="url(#circlePattern)" stroke="white" stroke-width="0"
x="100" y="300" width="80" height="20"
transform="translate(100, 300) scale (1.125, 1.0) translate(-100, -300)"
/>
<!-- 3. scale width to 95px -->
<rect fill="url(#circlePattern)" stroke="white" stroke-width="0"
x="100" y="400" width="100" height="20"
transform="translate(100, 400) scale (0.95, 1.0) translate(-100, -400)"
/>
</svg>
<div id="showcase"/>

Related

<svg> <use> only displays in positive quadrant?

Failing to understand why only one quadrant is showing red color rather than the entire viewable area of the SVG? The same rect outside of use covers the entire area, what am I missing?
svg {
height: 50vmin;
border: solid 1px black;
transform: scale(1, -1);
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-1 -1 2 2">
<defs>
<symbol id="quadrant">
<rect x="-1" y="-1" width="2" height="2" fill="#f00"/>
</symbol>
</defs>
<rect x="-1" y="-1" width="2" height="2" fill="#0f0"/>
<use xlink:href="#quadrant" />
</svg>
The use element doesn't have x or y attributes so it starts at 0,0 and not -1, -1 as the prior rect does.
The symbol doesn't have a viewBox so the symbol displays from 0, 0 to 100%, 100% i.e. 1, 1 (since that's where the root viewBox says 100% maps to).
svg {
height: 50vmin;
border: solid 1px black;
transform: scale(1, -1);
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-1 -1 2 2">
<defs>
<symbol id="quadrant" viewBox="-1 -1 2 2">
<rect x="-1" y="-1" width="2" height="2" fill="#f00"/>
</symbol>
</defs>
<rect x="-1" y="-1" width="2" height="2" fill="#0f0"/>
<use x="-1" y="-1" xlink:href="#quadrant" />
</svg>
The alternative would be to just make the symbol's overflow: visible but I wouldn't recommend it as overflow: visible has terrible performance.

Why does Webkit/Safari (iOS, macOS) render my SVG transformations in a different order compared to Blink and Gecko?

I have a minimal SVG image of an asterisk icon:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<style>
.a {
fill: red;
transform-box: fill-box;
transform-origin: center;
}
</style>
</defs>
<!-- x = 45 because it's 10px wide and centered around x=50, so x0 == 45, x1 == 55 -->
<rect class="a" x="45" y="0" width="10" height="100"/>
<rect class="a" x="45" y="0" width="10" height="100" transform="rotate(-45)"/>
<rect class="a" x="45" y="0" width="10" height="100" transform="rotate( 45)"/>
<rect class="a" x="45" y="0" width="10" height="100" transform="rotate(-90)"/>
</svg>
This renders correctly in Firefox 75 and Chrome 80, but not Safari on iOS nor macOS:
Firefox 75:
Chrome 80:
Safari on iOS 13
But on Safari, the transform-origin is seemingly ignored, or applied out-of-order compared to the transform="" attribute on each of the <rect> elements:
I'm told it renders the same broken image on macOS Safari (I don't have immediate access to macOS Safari right now so I can't post a screenshot from there, sorry).
I've searched around Google to see if Safari/WebKit is lacking support for transform-box, transform-origin or transform="rotate(deg)" but as far as I can tell they've all been fully supported by Safari for years - so I don't understand why it's applying the transformations out-of-order and causing the broken rendering.
After fiddling with the SVG in Safari, Safari's Web Inspector does seem to recognize the transform-origin SVG style property, but it doesn't actually use it when applying transformations, which is weird (e.g. using transform-origin: center; or transform-origin: 0px 0px or transform-origin: 50px 50px had no effect) - so a fix lies in changing the rotation centre using some other means.
As far as I can tell, macOS Safari and iOS Safari both only use transform-origin for CSS-based transformations using the transform property and not when using SVG attribute-based transformations using transform="" - whereas Blink and Gecko both use transform-origin for both attribute-based and CSS-based transformations.
Using a translate step
One approach is to add a translate step so the centre of rotation is centred in the canvas, then perform the rotation, then undo the translate step:
transform="translate( 50, 50 ) rotate(45) translate( -50, -50 )"
Like so:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<rect fill="red" x="45" y="0" width="10" height="100" />
<rect fill="blue" x="45" y="0" width="10" height="100" transform="translate(50,50) rotate(-45) translate(-50,-50)" />
<rect fill="green" x="45" y="0" width="10" height="100" transform="translate(50,50) rotate( 45) translate(-50,-50)" />
<rect fill="orange" x="45" y="0" width="10" height="100" transform="translate(50,50) rotate(-90) translate(-50,-50)" />
</svg>
Better approach: Using rotate( angle, x, y ):
Rather than applying an initial translate step to the transform chain, I saw that the rotate transformation function supports specifying a centre-of-rotation using additional arguments - so this works in Safari, Chrome (+Edge +Opera), and Firefox:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<rect fill="red" x="45" y="0" width="10" height="100" />
<rect fill="blue" x="45" y="0" width="10" height="100" transform="rotate(-45, 50, 50)" />
<rect fill="green" x="45" y="0" width="10" height="100" transform="rotate( 45, 50, 50)" />
<rect fill="orange" x="45" y="0" width="10" height="100" transform="rotate(-90, 50, 50)" />
</svg>

SVG Use -- Unable to repostion polygon after rotation

I am trying to create a 16 point star inside a circle using SVG and pure CSS -- no JS!
My strategy is to create 16 equilateral triangles (via Defs and Use, to keep it DRY), rotating each Use iteration by 22.5 degrees.
My problem is that when I apply the rotate() transform to the second triangle, SVG changes the center point of the triangle -- which CSS3 does not (it rotates around a fixed axis).
I have tried adding x and y parameters, adding a class and doing a translate() transform, doing that inline... nothing works -- I just cant figure out how to move the triangle back into position (with a rotation) inside the circle (centered at 150, 150 I reckon).
Any help would be appreciated. Here is the SVG line of code that I am having trouble with.
<use xlink:href="#triangle" style="transform: rotate(22.5deg);" />
You can see it in action here.
<style > .toile {
display: flex;
flex-direction: column;
max-width: 400px;
max-height: 800px;
justify-content: center;
align-items: center;
/* centers outer containing element (the circle) horizontally & vertically */
border: 5px #009000;
/* green */
border-style: groove;
background-color: #f9e4b7;
margin: 0 auto;
/* centers surface on a page */
}
<div class="toile">
<svg>
<defs>
<pattern id="grid" width="15" height="15" patternUnits="userSpaceOnUse">
<rect fill="white" x="0" y="0" width="14" height="14"/>
<rect fill="#009000" x="14" y="0" width="1" height="14"/>
<rect fill="#009000" x="0" y="14" width="14" height="5"/>
</pattern>
<g id="triangle">
<svg>
<polygon points="150,18 200,100 100,100"
style="stroke:#009000;stroke-width:1; fill:#afeeee; opacity:.7" />
</svg>
</g>
</defs>
<rect fill="url(#grid)" x="0" y="0" width="100%" height="100%" />
<svg viewBox="0 100 400 400" stroke="#009000" stroke-width=".5" width="300" height="300" class="cercle">
<circle cx="50%" cy="50%" r="75" fill="transparent" /> </svg>
<svg viewBox="0 100 400 400" stroke="#ce2029" stroke-width=".5" width="300" height="300">
<circle cx="50%" cy="50%" r="2" fill="#ce2029" /> </svg>
<use xlink:href="#triangle" />
<use xlink:href="#triangle" style="transform: rotate(22.5deg);" />
</svg>
</div>
Thank you for any solution to this problem; I just can't figure it out! Please no JS solutions!
UPDATE:
I've changed the 16-point gon to a 15 point one, as for some reason a series of 22.5 degree rotations create an unbalanced hexadecagon. I got rid of the red circle center point, and the background grid, and added SVG animation. Here is the (final) working example.
Sorry about the CodePen but I am trying to figure out how to make snippets work for an entire HTML/CSS/SVG program.
This is one way of doing it:
First I simplified your code. Unless you have a good reason to do it like this, it's always better to keep things simple.
I calculated the points tor the triangle around the center of the svg canvas:
<polygon id="triangle" points="200,125 264.95,237.5 135.05,237.5"
I rotate the triangle using svg transforms: transform="rotate(22.5,200,200)"
The first value is the rotation in degs and next you have the x and y of the rotation center.
As it comes out with SVG transforms you don't have IE issues. Please read this article about Transforms on SVG Elements
.toile {
display: flex;
flex-direction: column;
max-width: 400px;
max-height: 800px;
justify-content: center;
align-items: center;
/* centers outer containing element (the circle) horizontally & vertically */
border: 5px #009000;
/* green */
border-style: groove;
background-color: #f9e4b7;
margin: 0 auto;
/* centers surface on a page */
}
<div class="toile">
<svg viewBox="0 0 400 400" stroke="#009000" stroke-width=".5" width="300" height="300" >
<defs>
<pattern id="grid" width="15" height="15" patternUnits="userSpaceOnUse">
<rect fill="white" x="0" y="0" width="14" height="14"/>
<rect fill="#009000" x="14" y="0" width="1" height="14"/>
<rect fill="#009000" x="0" y="14" width="14" height="5"/>
</pattern>
<polygon id="triangle" points="200,125 264.95,237.5 135.05,237.5"
style="stroke:#009000;stroke-width:1; fill:#afeeee; opacity:.7" />
</defs>
<rect fill="url(#grid)" x="0" y="0" width="100%" height="100%" />
<circle class="cercle" cx="50%" cy="50%" r="75" fill="transparent" />
<circle cx="50%" cy="50%" r="2" fill="#ce2029" />
<use xlink:href="#triangle" />
<use xlink:href="#triangle" transform="rotate(22.5,200,200)" />
</svg>
</div>
UPDATE
To calculate the points for the triangle you may use javascript. In the case of a regular polygon like a triangle all 3 vertices are on a circumscribed circle at a 2*Math.PI/3 angle one from each other. I'm starting with an offset of -Math.PI/2 (-90 degs) for the first vertex.
// the center of the SVG canvas calculated from the values of the viewBox attribute. Alternatively you can choose a different point
let c = {x:200,y:200}
let radius = 75;
let points = [];
for(let a = -Math.PI/2; a < 3*Math.PI/2; a+= 2*Math.PI/3){
let x = c.x + radius*Math.cos(a);
let y = c.y + radius*Math.sin(a);
points.push(x);
points.push(y);
}
tri.setAttributeNS(null, "points", points.join());
svg{border:1px solid;height:90vh}
<svg viewBox="0 0 400 400">
<polygon id="tri" />
</svg>

Origin of CSS transform for svg:use

Is there a way to set the origin of CSS animation (transform - rotation) relatively to x,y of the element <use/> with CSS?
There might be several <use/> in a given <svg/> at different places:
absolute position of each element might not be hard coded in CSS.
.spin {
transition:4s linear;
-webkit-transition:4s linear;
}
.spin:hover {
transform-origin: 0% 0%;
-webkit-transform-origin: 0% 0%;
transform:rotateZ(360eg);
-webkit-transform:rotateZ(360deg);
}
<body>
<svg width="200" height="200" viewBox="0 0 200 200" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<path id="shape1" d="M0,0 V20 H20 Z" fill="red" stroke="red" stroke-width="0" />
</defs>
<circle cx="0" cy="0" r="80" fill="none" stroke="green"/>
<use xlink:href="#shape1" x="40" y="40" class="spin" />
</svg>
</body>
Animating the transform of a <use> is complicated by the fact that you are using the x and y attributes. The spec says that:
The x and y properties define an additional transformation (translate(x,y), ...
This extra transform component can mess with the transform you are animating, and produce unexpected effects.
The simple fix is to remove the x and y attributes, and use a parent <g> element to set the position of the path.
<g transform="translate(40,40)">
<use xlink:href="#shape1" class="spin" />
</g>
Updated example
.spin {
transition:4s linear;
-webkit-transition:4s linear;
}
.spin:hover {
transform-origin: 0px 0px;
-webkit-transform-origin: 0px 0px;
transform:rotateZ(360eg);
-webkit-transform:rotateZ(360deg);
}
<body>
<svg width="200" height="200" viewBox="0 0 200 200" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" >
<defs>
<path id="shape1" d="M0,0 V20 H20 Z" fill="red" stroke="red" stroke-width="0" />
</defs>
<circle cx="0" cy="0" r="80" fill="none" stroke="green"/>
<g transform="translate(40,40)">
<use xlink:href="#shape1" class="spin" />
</g>
</svg>
</body>

Why does this SVG image have a height of 150px

Why is this SVG image displayed at 150px height inside this 500px container? Why this specific value?
I found this weird behavior in both js bin and Codepen, so I think it is something to do with my code and not with the online editors.
Note: a 700px div container results in the same thing. So the height of the parent doesn't matter.
<div style="padding: 30px; background-color: yellow; height: 500px; width: 500px; ">
<svg>
<pattern id="basicPattern" x="10" y="10" width="40" height="40" patternUnits="userSpaceOnUse" >
<rect x= "0" y="0" width="4" height="4"
stroke = "red"
stroke-width = "1"
fill = "black"/>
</pattern>
<!-- <rect x="0" y="0" width="300" height="151" // why this deletes the bottom line? -->
<!-- <rect x="0" y="0" width="300" height="150" // why this deletes 1 px from the bottom line? -->
<!-- but this height="149" is the bottom limmit for this picture..
what prevent's it bor beeing extended further - we have unthil 500 px as you can see on the div.-->
<rect x="0" y="0" width="300" height="149"
stroke= "red"
stroke-width="2"
fill="url(#basicPattern)" />
</svg>
This is Jsbin and this is CodePen.
You didn't set the SVG width and height, so it goes to the default size of 300px width x 150px height (for some user-agents).
Here is your JSBin with the SVG width and height both set to 500px. Now the rectangle can go beyond 150px of height: https://jsbin.com/yafenemawe/1/edit?html,output

Resources