I'm struggling with resizing clipped svg on my HTML setup. I looked for similar solutions but I could not find any yet. I'm trying to resize clipped svg according to vertical screen viewport. Here is the codepen example of my setup:
.slider-image {
clip-path: url(#clip);
-webkit-clip-path: url(#clip);
}
<div>
<img class="w-full h-full slider-image object-cover absolute top-0 left-0 z-20" src="https://images.unsplash.com/photo-1558611848-73f7eb4001a1?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1051&q=80" >
<svg width="100%" height="90vh" viewBox="0 0 1159 548" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clip">
<path d="M989 547.5H0V0H1156L1159 9L1157.5 18.5L1147 89.5L1157.5 126L1120 244.5L1088 309L1041 369.5L989 445.5L962 503.5L989 547.5Z" fill="url(#paint0_linear)"/>
<circle cx="350" cy="168" r="80"/>
</clipPath>
</defs>
</svg>
</div>
https://codepen.io/ilkrclm/pen/oNNMQor?editors=1100
In this case the solution is using clipPathUnits="objectBoundingBox"
The clipPathUnits attribute indicates which coordinate system to use for the contents of the <clipPath> element.
objectBoundingBox indicates that all coordinates inside the <clipPath> element are relative to the bounding box of the element the clipping path is applied to. It means that the origin of the coordinate system is the top left corner of the object bounding box and the width and height of the object bounding box are considered to have a length of 1 unit value.
Read about clipPathUnits
Since the object bounding box is considered to have a length of 1 unit you will need to scale the path: transform="scale(0.00086)". The width of the bounding box of the path is 1159. 1/1159 = 0.00086.
I've commented out the circle since in this case is not doing anything.
*{margin:0;padding:0}
.slider-image {
width:100%;
clip-path: url(#clip);
-webkit-clip-path: url(#clip);
}
<div>
<img class="w-full h-full slider-image object-cover absolute top-0 left-0 z-20" src="https://images.unsplash.com/photo-1558611848-73f7eb4001a1?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1051&q=80" >
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="clip" clipPathUnits="objectBoundingBox">
<path transform="scale(0.00086)" d="M989 547.5H0V0H1156L1159 9L1157.5 18.5L1147 89.5L1157.5 126L1120 244.5L1088 309L1041 369.5L989 445.5L962 503.5L989 547.5Z" fill="url(#paint0_linear)"/>
<!--<circle transform="scale(0.00086)" cx="350" cy="168" r="80"/>-->
</clipPath>
</defs>
</svg>
</div>
Related
Please see the yellow rectangle, this is exactly the same as the rectangle clipping the image.
However image is clipped smaller and moved right.
The image is just one element, but imagine few more elements which all need to be clipped to a shape of a yellow rectangle.
I know I can fix this by wrapping the <image> element (and any more elements) inside a <g> element and applying the clipPath to this <g> element.
Is it possible to fix this issue by modifying just the clipPath part, without touching the rest of the svg structure?
<defs>
<clipPath id="clipPath">
<path d="M150-750 L150,750 L-150,750 L-150,-750Z" transform="matrix(1,0,0,1,152.5,770.5)"></path>
</clipPath>
</defs>
<path fill="#ffff00" d="M150-750 L150,750 L-150,750 L-150,-750Z" transform="matrix(1,0,0,1,152.5,770.5)"></path>
<image x="-1632" y="-1224" width="3264" height="2448" preserveAspectRatio="none"
xlink:href="https://cdn.pixabay.com/photo/2017/07/25/01/22/cat-2536662_960_720.jpg"
transform="matrix(0.3529,0,0,0.3529,246.2554,998.5607)"
style="clip-path: url(#clipPath);"></image>
Please see the jsfiddle here.
Everything happens because you transform the path and the image with a different value. I've removed the transforms and changed the viewBox value so that the clipping path falls inside the svg canvas.
Also I've removed the height of the svg element because I wanted to keep the same aspect ratio as the viewBox.
Please take a look. Let me know if this is what you need.
<svg width="305" style="overflow: hidden; position: relative;"
viewBox="-200 -800 750 3964" preserveAspectRatio="xMinYMin">
<defs>
<clipPath id="clipPath">
<path id="test" d="M150-750 L150,750 L-150,750 L-150,-750Z" ></path>
</clipPath>
</defs>
<use xlink:href="#test" fill="#ffff00" ></use>
<image x="-1632" y="-1224" width="3264" height="2448" preserveAspectRatio="none"
xlink:href="https://cdn.pixabay.com/photo/2017/07/25/01/22/cat-2536662_960_720.jpg"
style="clip-path: url(#clipPath);"></image>
</svg>
I am struggling to understand exactly how min-x and min-y on viewBox works, from a technical standpoint (without metaphors).
Two helpful resources I have spent quite a lot of time on:
SVG 1.1 (official specification) - 7.7 The ‘viewBox’ attribute
Understanding SVG Coordinate Systems and Transformations (Part 1) - by Sara Soueidan
According to the SVG 1.1 specification:
The value of the ‘viewBox’ attribute is a list of four numbers
, , and , separated by whitespace and/or
a comma, which specify a rectangle in user space which should be
mapped to the bounds of the viewport established by the given element,
taking into account attribute ‘preserveAspectRatio’.
And:
The effect of the ‘viewBox’ attribute is that the user agent
automatically supplies the appropriate transformation matrix to map
the specified rectangle in user space to the bounds of a designated
region (often, the viewport).
And:
(Note: in some cases the user agent will need to supply a translate
transformation in addition to a scale transformation. For example, on
an outermost svg element, a translate transformation will be needed if
the ‘viewBox’ attributes specifies values other than zero for
or .)
So, my expectation was that defining a viewBox is the same as:
First scaling the viewbox, so it fills the viewport (assuming same aspect ratio on viewport and viewBox)
Then translating the viewBox, so it is placed in the viewport according to min-x and min-y viewBox attributes.
If we look at Sara's two examples, starting here, that is not what seems to be happening.
In her first example (<svg width="800" height="600" viewbox="100 100 200 150">...</svg>), it looks like:
viewBox is placed according to min-x / min-y in viewport
viewBox is scaled to same size as viewport
viewBox origin is translated (moved) to coincide with viewport origin
In her second example however (<svg width="800" height="600" viewbox="-100 -100 400 300">...</svg>), it looks like a completely different order:
viewBox is scaled to same size as viewport
viewBox origin is translated (moved) somehow in the opposite direction of what viewBox min-x min-y indicates. It does not coincide with viewport origin - This is different from the first example
Thus, I recognize that I do not fully understand it, because technically it should work the same way in both cases.
Finally, in Sara's examples, I do not understand why the blue coordinate system (user coordinate system) does not itself move, to (100, 100) or (-100, -100) in viewport coordinate system. I thought viewBox was supposed to translate and scale the user coordinate system?
EDIT:
According to this SO answer, min-x and min-y does indeed follow my first set of steps. The viewBox origin is placed in the viewport according to min-x and min-y, and then translated so its origin is on top of viewport origin. It is then (before or after) scaled to fill viewport.
If that is correct, I have a hard time understanding why the blue user coordinate system in Sara's examples do not always end up with its origin on top of viewport origin. After all, viewBox should modify the user coordinate system.
The offset of the origin of the coordinates viewBox on the x-axis (min-x=70px)
<svg width="400" height="400" viewBox="70px, 0, 400px, 400px">
In the figure, the origin of user coordinates shifts to the right by 70px, thereby shifting the entire rectangular viewing areaviewBox (400 x 400px)to the right along the horizontal axis.
When this happens, the image of the SVG document fragment that is under the viewBox is captured and then the viewBox viewing area with the captured fragment is back aligned with the fixed user viewport area with the origin (0,0) in the upper left corner.
The coordinates of the figures are recalculated with the last shift of 70px to the left. Formally it turns out that in the fixed viewing area of the viewport when applying the viewBox the fragment of the SVG document has shifted to the left.
Live Demo
The offset of the origin of the viewBox along two axes
min-x=70px, min-y="70px"
<svg width="400" height="400" viewBox="70px, 70px, 400px, 400px">
For clarity, add another red rectangle at the bottom of the picture - 6
After transferring the origin to the viewBox, a rectangular 400 × 400 px SVG document fragment with a width and height count from the origin (70.70) gets into the viewBox.
Image capture occurs. Next, the origin of the viewBox (70,70) is combined with the origin of the viewport (0,0). The coordinates of the figures are recalculated.
Therefore, red rectangles 5 and 6 become fully visible. Everything that does not fall into this area is cut off. For example, part of the areas of colored circles 1,2 and 4.
Live Demo
Zoom using viewBox
The scale of the SVG document fragment depends on the aspect ratio: viewport andviewBox
If viewport /viewBox = 1, then the scale will be 1
If viewport /viewBox different from one, the scale will change in the direction of increase or decrease.
How does the increase in scale explains the figure below
One pixel viewBox stretches to two pixelsviewport
Live Demo
Zoom out svg image 1: 2
<svg width="400" height="400" version="1.1"
viewBox="0 0 800 800">
viewport / viewBox = 1/2
The viewBox captures a rectangular fragment 800 x 800 px, that is, the entire scope of the SVG viewport 400 x 400 px and an additional 400px each on the right and bottom of the viewport.
That is two pixels of the viewBox are compressed into one pixel of the viewport. Therefore the SVG image is reduced by half.
Live Demo
In the picture, a gray rectangle is an infinite SVG canvas.
The green rectangle is the viewport that the user sees on its display.
The yellow rectangle is the virtual viewBox area through which the user looks at the viewport.
viewBox can move along the coordinate axes of the infinitesvg canvas as in the positive direction x-min> 0; y-min> 0 and in the negative direction-x-min; -y-min
Image processing svg
Next comes the capture of a fragment of the SVG canvas, located under
the viewBox.
In the next step, the coordinate system of the viewBox is aligned
with the origin of the coordinate system of the viewport. And the
fragment captured by the viewBox image is passed back to the
viewport.
There is a process of negotiation and options are possible here:
If min-x = 0 andmin-y = 0, the width and height of the viewports are equal, respectively, to the width and height ofviewBoxs, then the fragment image does not move or scale.
If the viewBox is shifted to the right - min-x> 0, the image is shifted to the left. It is clear that by capturing an image to the right of the viewport and then combining it with the origin, we thereby shift the image to the left.
If the viewBox is shifted below the viewports - min-y> 0, the image will go up.
Based on this, there are thoughts that you can implement horizontal and vertical parallax without using CSS,JavaScript. To do this, simply move the viewBox along the SVG canvas, as shown in the figure below. Click the Start button.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="360" viewBox="0 0 600 360" >
<title> Explanation horizontal of parallax viewBox </title>
<desc> animate the horizontal parallax by modifying a coordinate of the viewBox </desc>
<defs>
<g id="canvas-svg" stroke-width="2px">
<g id="canvas-frame1">
<rect id="v-port1" x="25" y="200" width="110" height="110" stroke="skyblue" fill="yellowgreen" />
<text id="t-port1" x="75" y="255" style="font-size: 16pt;">1 </text>
<text x="26" y="303" > 0 </text>
</g>
<g id="canvas-frame2">
<rect id="v-port2" x="135" y="200" width="110" height="110" stroke="skyblue" fill="dodgerblue" />
<text id="t-port2" x="185" y="255" style="font-size: 16pt;">2 </text>
<text x="136" y="303" > 1168 </text>
</g>
<g id="canvas-frame3">
<rect id="v-port3" x="245" y="200" width="110" height="110" stroke="skyblue" fill="crimson" />
<text id="t-port3" x="295" y="255" style="font-size: 16pt;">3 </text>
<text x="246" y="303" > 2336 </text>
</g>
<g id="canvas-frame4">
<rect id="v-port4" x="355" y="200" width="110" height="110" stroke="skyblue" fill="orange" />
<text id="t-port4" x="405" y="255" style="font-size: 16pt;">4 </text>
<text x="356" y="303" > 3504 </text>
</g>
<g id="canvas-frame5">
<rect id="v-port5" x="465" y="200" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellow" />
<text id="t-port5" x="515" y="255" style="font-size: 16pt;">5 </text>
<text x="466" y="303" > 4672 </text>
</g>
</g>
</defs>
<g id="first-rect">
<rect x="25" y="25" width="110" height="110" stroke="skyblue" stroke-width="1px" fill="yellowgreen" />
<text x="75" y="85" style="font-size: 16pt;">1 </text>
<text x="26" y="135" > 0 </text>
</g>
<desc>The SVG canvas is infinite in size. In our example, user a viewport of SVG is in the leftmost position.</desc>
<use xlink:href ="#canvas-svg" x="0" y="0"> </use>
<desc> viewBox is moved along canvas SVG</desc>
<g id="viewBox1">
<rect id="v-box" x="25" y="200" width="110" height="110" stroke="skyblue" stroke-width="5px" fill="none" />
<text id="t-port1" x="45" y="225" style="font-size: 16pt; fill:blue;">viewBox </text>
<animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 0" to="440 0" repeatCount="indefinite" restart="whenNotActive" fill="freeze"/>
</g>
<desc> The image moves to the left viewport</desc>
<use xlink:href ="#canvas-svg" x="0" y="0">
<animateTransform attributeName="transform" type="translate" begin="startButton.click+0.5s" end="stopButton.click" dur="20s" from="0 -170" to="-440 -170" repeatCount="indefinite" restart="whenNotActive" fill="freeze" />
</use>
<desc> Grey background image of the canvas SVG</desc>
<g fill="#E5E5E5" stroke="#E5E5E5">
<rect x="135" y="0" width="465" height="195" />
<rect x="0" y="0" width="25" height="195" />
<rect x="0" y="0" width="135" height="30" />
<rect x="25" y="135" width="135" height="60" />
<rect x="0" y="315" width="600" height="85" />
<rect x="0" y="195" width="25" height="120" />
<rect x="575" y="195" width="25" height="120" />
</g>
<g stroke-width="1px" stroke-dasharray = "5 5">
<line x1="25" y1="140" x2="25" y2="195" stroke="blue" />
<line x1="135" y1="140" x2="135" y2="195" stroke="blue" stroke-width="1px" />
</g>
<g style="font-size: 16pt; fill:blue;">
<text x="45" y="170" > viewport </text>
<text x="15" y="20" style="font-size: 14pt;"> display the user's </text>
<text x="230" y="90" style="font-size: 40pt; fill:#1E90FF"> canvas SVG </text>
</g>
<g id="startButton">
<rect x="520" y="325" rx="8" ry="8" width="60" height="20" fill="#58AE2A" />
<text x="550" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle"
fill="white" >Start</text>
</g>
<g id="stopButton">
<rect x="450" y="325" rx="8" ry="8" width="60" height="20" fill="#1E90FF" />
<text x="480" y="340" font-size="16" font-weight="bold" font-family="Arial" text-anchor="middle"
fill="white" >Stop</text>
</g>
</svg>
I always mix up viewBox and viewport. So I'll try to avoid it where possible. And I don't fully understand if you want to setup the transformation matrix for the browser or for SVG. So I'll try to avoid it as well.
The viewBox attribute provides information to your browser about the size and the coordinate origin of your SVG graphics. It defines the window into the SVG. Only parts within the window will be visible.
So let's look at an example:
<svg width="800" height="600" viewbox="100 100 200 150">
This tells the browser that it should draw an SVG graphics that will have the dimension 800px by 600px – in the browser's coordinate system. So within the browser DOM, the SVG component will have that size.
The viewbox attribute then tells the browser that the relevant/visible part of the SVG graphics is 200pt by 150pt in size (in the SVG coordinate system). So the browser knows that it will need to apply a scaling of 400% to convert SVG coordinates to browser coordinates.
Furthermore, the viewbox attribute tells the browser that the point (100, 100) in the SVG coordinate system will be the top left corner of the visible SVG graphics window. So the browser will translate it accordingly.
Everything with smaller x and y values in the SVG coordinate system will be clipped, i.e. not visible, as it's outside the windows and outside the space the browser has created for the SVG. Similarly, everything to the right of the SVG coordinate 300 (100 + 200) and below the coordinate 250 (100 + 150) will be outside the window and not visible.
I have this svg:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 ' + size + ' ' + size +'" width="'+ boxW +'" height="'+ boxH +'">
<text>Sample Text</text>
</svg>
size: A parameter that is needed to the viewBox in order to create the wrapper.
width & height: the width and height of the container of the text.
I have a function that generate this svg. The problem is that the text is not fitting into the box; the result is like this:
(Is blue due to the Chrome inspector, you can see up in the top-left corner the text being small instead of full size.
The SVG resulted is this:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
viewBox="0 0 580 532">
<text x="0" y="15" style="font-family:Arial;fill:%230000ff;fill-opacity:1;font-weight:normal;font-style:normal;"
>test</text>
</svg>
The whole img is this:
<img src="data:image/svg+xml;charset=UTF-8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 580 532"><text x="0" y="15" style="font-family:Arial;fill:%230000ff;fill-opacity:1;font-weight:normal;font-style:normal;">test</text></svg>" class="leaflet-marker-icon leaflet-zoom-animated leaflet-interactive" alt="" tabindex="0" style="margin-left: -298px; margin-top: -291px; width: 309px; height: 295px; transform: translate3d(683px, 317px, 0px); z-index: 317; outline: none;">
So my quesiton is: How to fit the text into the main wrapper?
You either have to:
fit the viewBox to the text, or
fit the text to the viewBox.
You are not doing either. You are not even setting a font-size.
Option 1 is not really available to you. You can measure the text if you have access to the SVG DOM, but you can't do that if you aren't in a rendering environment, like a browser.
Perhaps you could use a font loading library to get metadata about the glyphs in the font. Then calculate the size of a piece of text that way. You don't mention which language you are using to produce these SVGs, so I can't advise further on that.
So you are left with option 2. The only option that SVG has to let you fit text to a particular size, is the textLength and lengthAdjust attributes on the <text> element.
textLength
Sets a length to which you want the text to be fitted
lengthAdjust
Sets the method to be used to adjust the length. You can either stretch just the spacing between the letters, or you can stretch the letter glyphs
See the <text> section in the spec for more information
There are no options for adjusting the text height.
svg {
width: 400px;
background-color: linen;
}
<svg viewBox="0 0 200 40">
<line x1="10" y1="30" x2="190" y2="30" stroke="black" opacity="0.2"/>
<text x="10" y="30"
textLength="180"
lengthAdjust="spacing">Sample Text</text>
</svg>
<br/>
<svg viewBox="0 0 200 40">
<line x1="10" y1="30" x2="190" y2="30" stroke="black" opacity="0.2"/>
<text x="10" y="30"
textLength="180"
lengthAdjust="spacingAndGlyphs">Sample Text</text>
</svg>
If you want the font size to be a better match, then you are going to have to work out a method of calculating an approximate font size. Eg.
var numChars = text.length()
var fontSize = (desiredTextWidth / numChars) * someScalingFactor
The scaling factor will depend on your font.
This is my solution: I'm putting the text inside a <symbol>. I get the size of the text with getBBox() and use it to set the viewBox for the <symbol>. Please note that the <use> element has a width of 100%.
let bb = text.getBBox();
test.setAttributeNS(null, "viewBox", `${bb.x} ${bb.y} ${bb.width} ${bb.height}`);
*{font-size:16px;}
svg{border:1px solid;width:90vh}
body{font-family:Arial;fill:#0000ff;fill-opacity:1;font-weight:normal;font-style:normal;}
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 580 532">
<symbol id="test">
<text id="text" dominant-baseline="central" text-anchor="middle" >test</text>
</symbol>
<use id="_use" xlink:href="#test" width="100%" />
</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've created some svg graphics that I would like to set as backgrounds in some variable height divs. I'm trying to use them as a sprite, so one svg file has a few groups, each with its own id, and I'm using those ids to specify which background is rendered in each div.
Here's a link to a (non-working) JSFiddle.
HTML:
<svg width="0" height="0">
<defs>
<!-- BLUE GRAPHIC -->
<g id="blue_background">
<polygon fill="#C8D9E5" points="[...in the fiddle...]"/>
</g>
<!-- GREEN GRAPHIC -->
<g id="green_background">
<path fill="#49B974" d="[...in the fiddle...]"/>
</g>
</defs>
</svg>
<div class="box">
<h1>asdasdasd</h1>
<h1>asdasdasd</h1>
<h1>asdasdasd</h1>
<svg class="svg1" viewBox="0 0 600 200" >
<use xlink:href="#blue_background" width="100%" height="100%"></use>
</svg>
</div>
<div class="box">
<h1>asdasdasd</h1>
<h1>asdasdasd</h1>
<h1>asdasdasd</h1>
<svg class="svg2" viewBox="0 0 200 200">
<use xlink:href="#green_background" width="100%" height="100%"></use>
</svg>
</div>
Ideally the graphic would be aligned with the right side, and stretch to fill the div's height.
Thanks
Since you're describing your SVGs as "backgrounds", I assume you don't need them to push text out of the way.
In that case, I would recommend using absolute positioning for the SVGs (relative to the "box" containers). With absolute positioning, a height of 100% is valid even if the parent element doesn't have fixed height.
.box {
position: relative;
width: 400px;
border: 1px solid #ddd;
}
svg {
position:absolute;
height:100%;
top:0; right:0;
z-index:-1;
}
http://jsfiddle.net/svag2/1/