In my Angular application, the user can insert an image into an SVG shape and apply a transform to the image interactively. By default, the image fills the shape.
It works well, except for one very annoying problem which occurs when:
The shape has no stroke
One side of the image is much darker than the opposite side
When these conditions are met, a very thin line appears on the side of the shape where the image is lighter. For example, if the bottom of the image is much darker than the top, the thin line will appear at the top of the shape. That line comes from the opposite side of the image, as if the pattern is trying to repeat. I know that there is nothing like a no-repeat attribute for image patterns, so I set the following pattern attributes to avoid repeating:
<pattern patternUnits="objectBoundingBox" x="0" y="0" width="1" height="1" ...>
It works but not perfectly. The thin line is not always visible on the screen, even when zooming, but it is most of the time. It causes us problems since these SVG shapes are part of book pages, and the thin line often ends up being visible in the final print.
Is there a good way to completely avoid pattern repeating? I tried extending the pattern so that it overflows the shape, but the extra size has to be somewhat significant to be effective:
<pattern x="-0.01" y="-0.01" width="1.02" height="1.02" ...>
When I run the code snippet below in my Chrome browser, the unwanted thin blue line is visible at the top of the shape. The problem is not specific to Chrome however; I see it on Firefox as well.
<div style="padding: 20px; width: 180px; height: 150px; background-color: yellow;">
<svg height="100%" width="100%" x="0%" y="0%" viewBox="0 0 1600 1200">
<defs>
<pattern id="pattern1" patternUnits="objectBoundingBox"
preserveAspectRatio="xMidYMid slice"
width="1" height="1" x="0" y="0" viewBox="0 0 1600 1200">
<image width="1600" height="1200" x="0" y="0"
xlink:href="https://i.ibb.co/vZ9spGH/1600x1200-1.png"></image>
</pattern>
</defs>
<rect fill="url('#pattern1')" height="100%" width="100%" x="0%" y="0%"></rect>
</svg>
</div>
Shape fills are fairly straightforward using a filter:
<div style="padding: 20px; width: 180px; height: 150px; background-color: yellow;">
<svg height="100%" width="100%" x="0%" y="0%" viewBox="0 0 1600 1200">
<defs>
<filter id="simple-image-fill" primitiveUnits="userSpaceOnUse">
<feImage width="1600" height="1200" x="0" y="0" preserveAspectRatio="xMidYMid slice"
xlink:href="https://i.ibb.co/vZ9spGH/1600x1200-1.png" result="image-res"/>
<feComposite operator="in" in2="SourceGraphic" in="image-res"/>
</filter>
</defs>
<rect filter="url(#simple-image-fill)" height="100%" width="100%" x="0%" y="0%"/>
</svg>
</div>
Related
I'm trying to understand the SVG viewport. Why are these three examples so different?
svg {
border: 2px solid red;
}
<div>
<svg>
<rect x="0" y="0" width="100" height="100" fill="blue" />
</svg>
<svg width="200" height="200">
<rect x="0" y="0" width="100" height="100" fill="blue" />
</svg>
<svg viewBox="0 0 200 200">
<rect x="0" y="0" width="100" height="100" fill="blue" />
</svg>
</div>
The first example is missing a width and height. The CSS specification says that replaced elements missing width and height values fallback to 300px x 150px so you'd see the top left 300 x 150 px of whatever you're drawing onto the canvas.
The second example has width and height so you'd see that part of the canvas.
The third example also has no width/height but oddly we're now going to use the 100% x 100% lacuna values for the width/height because we have a usable aspect ratio from the viewBox. The viewBox also scales the 200 x 200 internal co-ordinate system to fix into that canvas so everything looks bigger.
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>
I need to make text automatically stretch in both dimensions, to fill a container. It will distort.
This shows the the container space in red
This shows what a long name would normally resize to put in that space and maintaining aspect ratio
.
This shows what my client wants to happen
.
I would prefer to use SVG but I will work with what works.
I have searched for a solution to the best of my abilities but all seem to either refer to maintaining aspect ratio or stretching text when the page or viewbox changes dimensions.
That's quite a broad question, but yes you can do it with svg, I'll let you implement it though since you didn't provided anything to chew on.
The key point is to set your svg's preserveAspectRatio to "none":
svg{
height: 100vh;
width: 50vw;
}
body{
margin:0;
}
<div>
<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 40" preserveAspectRatio="none">
<text x="0" y="35" font-family="Verdana" font-size="35">
Hello, out there
</text>
</svg>
</div>
If your text is already part of an SVG (as it appears in your example), you will probably need to use a nested <svg> element.
<svg width="400" height="400">
<rect width="400" height="400" fill="rebeccapurple"/>
<!-- rect representing area that our text has to squeeze into -->
<rect x="20" y="50" width="200" height="50" fill="white"/>
<!-- x y width height match above rect -->
<!-- viewBox values need to match text bounds -->
<svg x="20" y="50" width="200" height="50"
viewBox="0 8 244 28" preserveAspectRatio="none">
<text x="0" y="35" font-family="Verdana" font-size="35">
HELLO THERE
</text>
</svg>
</svg>
The hardest part is workoing out the correct values for viewBox. It needs to match the bounds of the (normal unsqueezed) text.
I have the following SVG graphic that is currently scaling when the window is resized, but the aspect ratio is maintained. How could I get this to only scale on the X axis, and keep the Y at 80px?
<svg width="100%" viewBox="0 0 300 80">
<rect x="0" y="0" fill="yellow" height="80" width="100"/>
<rect x="100" y="0" fill="blue" height="80" width="100"/>
<rect x="200" y="0" fill="red" height="80" width="100"/>
</svg>
Thank you,
You have a couple of options. First, you could simply specify the height of the graphic, e.g. using CSS.
svg {
height: 80px;
width: 100%;
}
If that's not the effect you want, you can get more sophisticated with the preserveAspectRatio attribute. It's hard to say what value would work for you since it's not completely clear what you want (assuming the CSS approach above doesn't do it), but maybe something like:
<svg viewBox="0 0 300 80" preserveAspectRatio="none">
Check out the reference link for more details.
Based on this example: https://bl.ocks.org/jfsiii/7772281
I am trying to fill an SVG shape with a masked pattern.
I have two boxes. The first takes just the dot pattern. This works, but the color of the dots cannot be changed in css.
I have a second box, which has a fill and is then masked with the same pattern. This ought to result in blue dots.
But it's not masking properly. Or at least, it's doing so inconsistently. The first box will show in Chrome, but the second one will not, at least not until I go to inspect it. Then it decides to turn on. :/. Both show up in Firefox correctly.
Since the example I borrowed from works fine in Chrome, I assume I am doing something wrong.
<style>
.mask-dots {
mask: url(#mask-dots);
}
.pattern-dots {
fill: url(#pattern-dots)
}
.blue {
fill: blue;
</style>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="393px"
height="528px" viewBox="0 0 393 528" enable-background="new 0 0 393 528" xml:space="preserve">
<g id="underskirt-4">
<rect class="pattern-dots" width="100" height="100"/>
<rect x="110" class="blue mask-dots" width="100" height="100"/>
</g>
</svg>
<svg><defs><pattern id="pattern-dots" width="50" height="50"
patternUnits="userSpaceOnUse">
<path fill="white" d="M15.946,12.651c0,1.905-1.544,3.45-3.45,3.45c-0.953,0-1.815-0.386-2.439-1.011
c-0.624-0.624-1.01-1.486-1.01-2.439c0-1.905,1.544-3.45,3.45-3.45S15.946,10.746,15.946,12.651z"/>
</pattern>
<mask id="mask-dots">
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern-dots)" />
</mask>
</defs>
</svg>
Jsfiddle, with second box not displaying (at least not in Chrome.)
https://jsfiddle.net/qux8yt9g/