Strange diagonal viewBox SVG Behaviour - css

Trying to figure out how this svg viewBox thing works but sadly the code below is breaking all logic we know lol. Please someone explain if this is a bug or it's the correct svg behaviour. I can't see what I'm getting wrong...
<svg class="symbol"><symbol id="Atom" preserveAspectRatio="xMinYMin meet" viewBox="0 0 10 7"><path d="M3 6 4 7 6 7 4 4 5 3 8 7 10 7 5 0 0 7 2 7Z"></path></symbol></svg>
This my svg symbol here and I'm trying to stack more of them (both horizzontally and vertically)
//vertical stack ->viewBox = 0, 0, 10*1, 7*2
<svg viewBox="0 0 10 14">
<use href="#Atom"/>
<use href="#Atom" y="7"/>
</svg>
//horizontal stack ->viewBox = 0, 0, 10*2, 7*1
<svg viewBox="0 0 20 7">
<use href="#Atom"/>
<use href="#Atom" x="10"/>
</svg>
//"diagonal" stack ->viewBox = 0, 0, 10*2, 7*2
<svg viewBox="0 0 20 14">
<use href="#Atom"/>
<use href="#Atom" x="10"/>
<use href="#Atom" y="7"/>
</svg>
First one is 2 symbols in vertical so I kept the same viewBox as the symbol but doubling the height from 7 to 14. Works perfectly.
Second one is 2 symbols in horizontal so I kept the same viewBox as the symbol but doubled the width from 10 to 20. Works as well.
Third one is 2 symbols in horizontal and 2 symbols in vertical making both viewBox's width and height twice as the original. This doesn't work.
As you can see from the image the symbols get all messed up togheter (seems like I have a wrong viewBox)
This is kinda what I'm looking for (though as you can see I'm using overflow: visible and the symbols goes out of the viewbox)
Can somebody explain me what I'm getting wrong here? Any help is really appreciated.
Here's a codepen for playing with this stuff

your symbol want to take full width or height
when you stretch only one dimension of viewport then it fit nicely because it take full width or height and stops from stretching in other direction because it want to preserve aspect ratio.
But in this svg viewport is 2x bigger than symbol so it take it all
here is simple solution
.svg {
max-width: 200px;
border: 1px solid red;
}
<svg class="svg" viewBox="0 0 20 14">
<use href="#Atom" x="0" y="0" width="50%" />
<use href="#Atom" x="0" y="7" width="50%" />
<use href="#Atom" x="10" y="0" width="50%" />
<use href="#Atom" x="10" y="7" width="50%" />
</svg>
<svg class="symbol"><symbol id="Atom" preserveAspectRatio="xMinYMin meet" viewBox="0 0 10 7"><path d="M3 6 4 7 6 7 4 4 5 3 8 7 10 7 5 0 0 7 2 7Z"></path></symbol></svg>
UPDATE:
if you doesn't want to manually add width to use tag AND you are creating them programmatically then (I assumed that you use javascript but you could use same logic for other language) you just want to calc rows and columns in your viewport and calculate width and height of every use
here is an example
const config = {
svg: {
width: 20,
height: 14,
rows: 2,
cols: 2,
symbol: '#Atom'
}
}
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 '+config.svg.width+' '+config.svg.height);
for (let i = 0; i < config.svg.rows; i++) {
for (let j = 0; j < config.svg.cols; j++) {
const xPosition = (config.svg.width / config.svg.rows) * i;
const yPosition = (config.svg.height / config.svg.cols) * j;
const width = (100 / config.svg.rows);
const height = (100 / config.svg.cols);
const svgUse = document.createElementNS('http://www.w3.org/2000/svg', 'use');
svgUse.setAttribute('href', config.svg.symbol);
svgUse.setAttribute('x', xPosition);
svgUse.setAttribute('y', yPosition);
svgUse.setAttribute('width', width + '%');
svgUse.setAttribute('height', height + '%');
svg.append(svgUse);
}
}
document.body.prepend(svg)
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<svg class="symbol"><symbol id="Atom" preserveAspectRatio="xMinYMin meet" viewBox="0 0 10 7"><path d="M3 6 4 7 6 7 4 4 5 3 8 7 10 7 5 0 0 7 2 7Z"></path></symbol></svg>
</body>
</html>

So I ended up replying to myself but I really want to thanks #ciekals11 for its input.
The solution to get the result I wanted was to specify width and height attributes in my symbol. This makes everything works perfectly. Not sure why it does need this, but anyway I'll share my symbol again so you can see the difference.
<svg class="symbol"><symbol width="10" height="7" id="Atom" preserveAspectRatio="xMinYMin meet" viewBox="0 0 10 7"><path d="M3 6 4 7 6 7 4 4 5 3 8 7 10 7 5 0 0 7 2 7Z"/></symbol></svg>

Related

Cut corners of container using CSS

Im trying to cut the corners of a container using css like this
what im trying to achieve
I tried using clip-path but I cant manage to make the cuts rounded like in the picture above
what i have so far
clip-path: polygon(0 0,100% 0,100% calc(100% - 50px),calc(100% - 100px) calc(100% - 50px),calc(100% - 120.40px) 100%,0 100%);
I don't think you can do that with polygon clip-path only, there are outer angles and inside angles...
With svg it can be possible but a bit tricky.
First needed to design the svg shape by itself (red fill is only to view something in the svg editor: inkscape here).
<svg id="clipContainer" width="1920" height="1080" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
<path style="fill:#ff0000;stroke:none;"
d="m 35,0 h 654.05318 c 6.84803,0 13.02979,2.8828963 16.6429,6.7452744 l 63.1577,76.5094516 C 772.54893,87.959545 779.6304,90 785,90 h 350 c 5.6694,-0.04033 12.8528,-3.528812 17.4966,-8.545814 L 1213.8538,7.1954094 C 1219.2498,1.0400203 1224.6973,0 1230.1965,0 H 1885 c 18.8682,0 35,16.120082 35,35 v 875 c -0.2052,10.54136 -8.9432,19.77096 -20,20 h -608.8105 c -13.0687,-0.12776 -25.0997,3.20977 -33.0435,11.97991 L 1147.2068,1064.9984 C 1134.8748,1081.5357 1126.0043,1080 1106.409,1080 H 35 C 16.13187,1080 0,1063.8799 0,1045 V 35 C 0,16.120082 16.13187,0 35,0 Z"/>
</svg>
now have to put it in HTML body but as a clip-path:
<div class="container">
</div>
<svg width="0" height="0">
<defs>
<clipPath id="svg-clip">
<path style="fill:#ff0000;stroke:none;"
d="m 0.01822917,0 h 0.34065265 c 0.003567,0 0.006786,0.00266925 0.008668,0.0062454 l 0.0328946,0.07083957 c 0.001926,0.0043561 0.005613,0.0062455 0.008408,0.0062455 h 0.18229167 c 0.002953,-3.739e-5 0.006695,-0.0032675 0.009113,-0.0079124 L 0.63221469,0.00666224 C 0.63502588,9.6294739e-4 0.63786313,0 0.64072729,0 H 0.98177075 C 0.99159837,0 1,0.01492547 1,0.03240625 v 0.8101562 c -1.0691e-4,0.009762 -0.004658,0.0183059 -0.0104162,0.0185171 H 0.67249445 c -0.006807,-1.1867e-4 -0.0130727,0.00297 -0.0172102,0.0110924 l -0.0577808,0.11390178 c -0.006423,0.0153109 -0.0110433,0.0138917 -0.0212488,0.0138917 H 0.01822917 C 0.00840202,0.99996424 0,0.98503874 0,0.96755799 V 0.03240625 C 0,0.01492547 0.00840202,0 0.01822917,0 Z"/>
</clipPath>
</defs>
</svg>
plus a bit of css saying container using the clip-path svg-clip:
body {
display: flex;
justify-content: center;
align-items: center;
background-color: grey;
}
.container {
position: relative;
width: 90vw;
height: 90vh;
background-color: #222222;
border-radius: 2vw;
clip-path: url(#svg-clip);
}
Now the problem is the scale! That works but not really following the scale of the container. We will use clipPathUnits="objectBoundingBox" but to do so we need to have value of the path between 0 and 1 (yes yes 1 px!!!). So after edit in inkscape the svg is:
<svg id="clipContainer" width="1920" height="1080" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
<path style="fill:#ff0000;stroke:none;"
d="m 0.01822917,0 h 0.34065265 c 0.003567,0 0.006786,0.00266925 0.008668,0.0062454 l 0.0328946,0.07083957 c 0.001926,0.0043561 0.005613,0.0062455 0.008408,0.0062455 h 0.18229167 c 0.002953,-3.739e-5 0.006695,-0.0032675 0.009113,-0.0079124 L 0.63221469,0.00666224 C 0.63502588,9.6294739e-4 0.63786313,0 0.64072729,0 H 0.98177075 C 0.99159837,0 1,0.01492547 1,0.03240625 v 0.8101562 c -1.0691e-4,0.009762 -0.004658,0.0183059 -0.0104162,0.0185171 H 0.67249445 c -0.006807,-1.1867e-4 -0.0130727,0.00297 -0.0172102,0.0110924 l -0.0577808,0.11390178 c -0.006423,0.0153109 -0.0110433,0.0138917 -0.0212488,0.0138917 H 0.01822917 C 0.00840202,0.99996424 0,0.98503874 0,0.96755799 V 0.03240625 C 0,0.01492547 0.00840202,0 0.01822917,0 Z"/>
</svg>
That gives the following snippet:
body {
display: flex;
justify-content: center;
align-items: center;
background-color: grey;
}
.container {
position: relative;
width: 90vw;
height: 90vh;
background-color: #222222;
border-radius: 2vw;
clip-path: url(#svg-clip);
}
<div class="container">
</div>
<svg width="0" height="0">
<defs>
<clipPath id="svg-clip" clipPathUnits="objectBoundingBox">
<path style="fill:#ff0000;stroke:none;"
d="m 0.01822917,0 h 0.34065265 c 0.003567,0 0.006786,0.00266925 0.008668,0.0062454 l 0.0328946,0.07083957 c 0.001926,0.0043561 0.005613,0.0062455 0.008408,0.0062455 h 0.18229167 c 0.002953,-3.739e-5 0.006695,-0.0032675 0.009113,-0.0079124 L 0.63221469,0.00666224 C 0.63502588,9.6294739e-4 0.63786313,0 0.64072729,0 H 0.98177075 C 0.99159837,0 1,0.01492547 1,0.03240625 v 0.8101562 c -1.0691e-4,0.009762 -0.004658,0.0183059 -0.0104162,0.0185171 H 0.67249445 c -0.006807,-1.1867e-4 -0.0130727,0.00297 -0.0172102,0.0110924 l -0.0577808,0.11390178 c -0.006423,0.0153109 -0.0110433,0.0138917 -0.0212488,0.0138917 H 0.01822917 C 0.00840202,0.99996424 0,0.98503874 0,0.96755799 V 0.03240625 C 0,0.01492547 0.00840202,0 0.01822917,0 Z"/>
</clipPath>
</defs>
</svg>

how to solve svg path stoke width different thickness

I have a square hexagonal svg image, and i set stroke-width 4px for it.
But the stroke thickness is not consistent, the vertical lines are thinner and the slanted lines are thicker.
demo image
<svg width="173" height="199" viewBox="0 0 173 199" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke='#5088ff' stroke-width="4px" vector-effect="non-scaling-stroke" stroke-linecap="square" d="M86.5 0L173 50V150L86.5 199L0 150V50L86.5 0Z"/>
</svg>
I have searched and tried vector-effect="non-scaling-stroke", it does't work.
Make sure that the stroke is not outside the viewBox. A stroke will be painted equal inside and outside the path outline.
Here I moved the path using transform/translate and make the width and the height of the viewBox larger.
<svg width="200" viewBox="0 0 177 203" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path stroke='#5088ff' transform="translate(2 2)" stroke-width="4"
vector-effect="non-scaling-stroke" stroke-linecap="square"
d="M 86.5 0 L 173 50 V 150 L 86.5 199 L 0 150 V 50 L 86.5 0 Z"/>
</svg>

Color to CSS Color Filter

I have been trying to filter a fully white image to be able to change colors properly by inputting an RGB value and filtering the image to said color.
I have found a relatively good example on this, which I could use to manually input it, however I was hoping for a more clean and quick alternative rather than copying their code directly.
I have so far attempted color filtration manually through the link mentioned earlier and attempted to port it over, however it is very lengthy and doesn't work most of the time.
Any help would be appreciated!
I don't quite understand what you're exactly wanting to do, but if you wanna add an overlay effect with a color you can use:
box-shadow: /**height and width**/ inset /**color**/;
function recolorFilter({
red,
green,
blue,
alpha = 100,
r = red / 255,
g = green / 255,
b = blue / 255,
a = alpha / 100,
}) {
const recolorSvg = RECOLOR_SVG_TEMPLATE
.replace('R', String(Math.round((r + Number.EPSILON) * 1000) / 1000))
.replace('G', String(Math.round((g + Number.EPSILON) * 1000) / 1000))
.replace('B', String(Math.round((b + Number.EPSILON) * 1000) / 1000))
.replace('A', String(Math.round((a + Number.EPSILON) * 1000) / 1000))
return `url('data:image/svg+xml;utf8,${recolorSvg}#recolor')`
}
const RECOLOR_SVG_TEMPLATE = `
<svg xmlns="http://www.w3.org/2000/svg">
<filter id="recolor">
<feColorMatrix type="matrix" values="
0 0 0 0 R
0 0 0 0 G
0 0 0 0 B
0 0 0 A 0
"/>
</filter>
</svg>
`.replaceAll(/\s*\n\s*/g, '') // Remove unnecessary white-spaces
.replaceAll(/[RGB]/g, '$& ') // Reinsert spaces between matrix rows
const filter = recolorFilter({r: 0.565, g: 0.792, b: 0.976})
console.log(filter)
document.getElementById('my-image').style.filter = filter
<img
id="my-image"
src="https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon#2.png?v=73d79a89bded"
>
See: https://stackoverflow.com/a/62880368/5318303
Or easier: Use smart-color NPM library:
npm i smart-color
import recolorFilter from 'smart-color/recolorFilter.js'
const filter = recolorFilter('#90caf9')
// ...

How to calculate SVG path arc between two points along the slice of a cylinder

I'm trying to utilize SVG's to dynamically draw lines onto a cylindrical looking surface. Because it's cylindrical, any straight line between two points will actually follow an elliptical slice of the cylinder, and therefore need to be rendered as a section of an elliptical arc.
As the docs state, SVG arcs are defined as: "A rx ry x-axis-rotation large-arc-flag sweep-flag x y"
Please tell me where I'm going wrong in deriving this arc (AB).
So obviously I know my starting and ending points (AB).
I am assuming that ry in both the cylinder and slice are equal.
rx is half the slice's hypotenuse.
θ is the x-axis rotation... I think?
This is where I think I'm getting into trouble. As you can see in the second image when I try converting all my line paths into arcs, some of the ars turn out perfect when they are not rotated (they are parallel to the x-axis in the rectangular plane, meaning they follow the arc path of the primary cylindrical ellipse), however, something funny is happening with the rotation. When I turn on the large-arc-flag it's pretty evident that the ellipses being drawn are not at all lining up with my cylinder.
I'm fairly confident that the hypotenuse is being calculated correctly, as setting θ to zero gives me the cylinder's diameter, so I'm a bit flummoxed as to where I'm going wrong.
TLDR: Given the data I provided in image 1, how would you go about drawing the arc AB.
EDIT 1: Here is an SVG of a cylinder and a line to play around with. Again, I'm trying to make the line into an arc to fit on the cylinder surface so it matches the ellipse arc formed by a slice through the cylinder.
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path id="cylinder" fill="none" stroke="#000000" stroke-width="2" d="M 0 32 a148 32 0 0 0 296 0 a148 32 0 0 0 -296 0 v185 a148 32 0 0 0 296 0 v-185"/>
<path id="line_should_become_arc" fill="none" stroke="#000000" stroke-width="2" d="M 37 106 L 259 148"/>
</svg>
Perhaps you want something like this
(I am not familiear with JS, so don't know how to provide calculated parameters for SVG curves)
Angle of AB in rectangular coordinates is 15 degrees, 1/cos(15)=1.035 - coefficient for rx. Y-coordinates of blue arc are intentionally shifted by 10 pixels
<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg">
<path d="M50 50 L50 250" stroke="black" fill="transparent"/>
<path d="M50 250 A100 40 0 0 0 250 250" stroke="black" fill="transparent"/>
<path d="M250 250 L250 50 A100 40 0 0 0 50 50 A100 40 0 0 0 250 50" stroke="black" fill="transparent"/>
<path d="M50 150 A103.5 40 15 0 0 250 200 A103.5 40 15 0 0 50 150" stroke="black" fill="transparent"/>
<path d="M100 210 A103.5 40 15 0 0 200 232" stroke="blue" fill="transparent"/>
</svg>
I am assuming the following: you have a 3D coordinate system Oxyz and a right circular cylinder of radius a whose axis, running along the middle of the cylinder, coincides with the Oy axis. Then the circular base of the cylinder is perpendicular to the axis Oy and is therefore parallel to (or coincides with) the coordinate plane Oxz. In this coordinate system, the cylinder can described as all 3D points with the property
[x; y; z] such that x^2 + z^2 = a^2, while y is arbitrary or D <= y < = U.
I am assuming that the cylinder is projected from the 3D Oxyz coordinate system onto the Oxy coordinate plane so that the circle, obtained by the intersection of the cylinder with the coordinate plane Oxz, is projected as an ellipse with major axis of length a, aligned with the axis Ox, and minor axis of length b, aligned with the axis Oz. On your picture a = cylinder rx and b = ry.
This information allows us to determine the direction of projection:
direction = [0; b; a]
i.e. for any point P = [x_3D; y_3D; z_3D] in the 3D system Oxyz, we take the line through P and parallel to the vector direction, and its intersection with Oxy is the projection of P on Oxy. The formula for this is
[x_3D; y_3D; z_3D] ---> [x_3D; y_3D - (b/a)*z_3D]
i.e.
x = x_3D
y = y_3D - (b/a)*z_3D
(there is another option for the direction: direction = [0; - b; a] if projection is done "from under" the Oxz axis instead of "over", but let us stick with "over")
Conversely, if we are given a point [x; y] on the Oxy 2D coordinate plane, one can recover two points on the surface of the cylinder, which project onto [x; y]:
[x; y] ---> [x; y + (b/a)*sqrt(a^2 - x^2); sqrt(a^2 - x^2)]
which is the point on the cylinder in the half space where the axis Oz is positive, and
[x; y] ---> [x; y - (b/a)*sqrt(a^2 - x^2); - sqrt(a^2 - x^2)]
which is on the cylinder in the half space where the axis )z is negative.
The surface of the cylinder can be paramtrized by taking a flat planar rectangle and bend it in 3D by gluing together two of its parallel edges to form the right circular cylinder. This transformation can be written as
[s; y] ---> [a*cos(s/a); y; a*sin(s/a)]
i.e.
x = a*cos(s/a)
y = y
z = a*sin(s/a)
Then a generic straight line on the flat square
y = y0 + m*(s - s0)
turns into the 3D curve lying on the surface of the cylinder
x = a*cos(s/a)
y = y0 + m*(s - s0)
z = a*sin(s/a)
which is a helix.
Now, you are given as input
a, b, A = [xA; yA], B = [xB; yB]
Your goal is to find the equation of the curve in Oxy, that passes through A and B, and which is the projection of a helix on the cylinder in 3D.
Step1: Recover the 3D points A_3D and B_3D on the cylinder that project to A and B respectively. Using the formulas from above (and assuming that, let's say, A_3D and B_3D are on the positive side of Oz)
A_3D = [xA; yA + (b/a)*sqrt(a^2 - xA^2); sqrt(a^2 - xA^2)];
B_3D = [xB; yB + (b/a)*sqrt(a^2 - xB^2); sqrt(a^2 - xB^2)];
Step2: Represent A_3D and B_3D in the [s; y] surface coordinates of the cylinder:
s_A = a*arccos(xA);
y_A = yA + (b/a)*sqrt(a^2 - xA^2);
s_B = a*arccos(xB);
y_B = yB + (b/a)*sqrt(a^2 - xB^2);
Step 3: Construct the straight line in the [s; y] coordinates:
m = (y_B - y_A) / (s_B - s_A)
= (yB + (b/a)*sqrt(a^2-xB^2) - yA - (b/a)*sqrt(a^2-xA^2)) / (a*arccos(xB) - a*arccos(xA))
= ((yB - yA) + (b/a)*(sqrt(a^2-xB^2) - sqrt(a^2-xA^2))) / (a*arccos(xB) - a*arccos(xA));
y = y_A + m*(s - s_A);
Step 4: Represent it in 3D as a helix:
x_3D = a*cos(s/a)
y_3D = y_A + m*(s - s_A)
z_3D = a*sin(s/a)
Step 5: Project the helix from the cylinder onto the coordinate Oxy plane, along the direction = [0; b; a]:
x = a*cos(s/a)
y = y_A + m*(s - s_A) - b*sin(s/a)
It's pretty much plug and play. The only thing that required some effort is the two flags in the Arc command.
I started with the two path endpoints from your example SVG, and got rx and ry from the arc that forms the top of the cylinder. But you didn't provide any theta, so I picked one, and had to adjust the endpoints so that the slice lined up with the cylinder walls.
var arc = document.getElementById("line_should_become_arc");
var slice = document.getElementById("slice");
var Ax = 57, Ay = 126;
var Bx = 279, By = 168;
var rx = 148;
var ry = 32;
var theta = 14; // 14 deg
var slice_rx = rx / Math.cos(theta * Math.PI / 180);
arc.setAttribute("d", ['M', Ax,Ay, 'A', slice_rx, ry, theta, 0, 0, Bx,By].join(' '));
slice.setAttribute("d", ['M', Ax,Ay, 'A', slice_rx, ry, theta, 1, 1, Bx,By].join(' '));
<svg width="400" height="400">
<path id="cylinder" fill="none" stroke="#000000" stroke-width="2" d="M 0 32 a148 32 0 0 0 296 0 a148 32 0 0 0 -296 0 v185 a148 32 0 0 0 296 0 v-185"/>
<path id="slice" fill="none" stroke="#000000" stroke-width="2" d="M 0,0"/>
<path id="line_should_become_arc" fill="none" stroke="#f00" stroke-width="2" d="M 0,0"/>
</svg>

Animate SVG path for different screen sizes

I am new to SVG. I am trying to animate a straight line to curved line. I have followed a few links but could not find an appropriate way to do that. Following is my code:
<svg width="100%" height="960px">
<path id="shape" d="M0 0 C0 0, 0 00, 400 0" stroke="black" fill="transparent" stroke-width="1" vector-effect="non-scaling-stroke">
<animate
attributeName="d"
dur="500ms"
begin="indefinite"
repeatCount="1"
to = "M0 0 C0 0, 165 50, 400 0"
id="test" />
</path>
</svg>
I also have used:
values="M0 0 C0 0, 0 00, 400 0; M M0 0 C0 0, 165 50, 400 0;"
in animate tag, but still it didn't work.
What is problem: I am not able to make path curved, animation happens once and then path becomes straight again and I am not sure how to make the same curve on different screen sizes.
If you use begin="indefinite" the animation won't start, except via javascript. In order to demonstrate things I've set begin="1s"
If you want the animation to stay in the end position then add fill="freeze" to it.
If you want it to work with different screen sizes, use a viewBox.
<svg width="100%" height="960px" viewBox="0 0 500 500" preserveAspectRatio="none">
<path id="shape" d="M0 0 C0 0, 0 00, 400 0" stroke="black" fill="transparent" stroke-width="1" vector-effect="non-scaling-stroke">
<animate
attributeName="d"
dur="500ms"
begin="1s"
repeatCount="1"
fill="freeze"
to = "M0 0 C0 0, 165 50, 400 0"
id="test" />
</path>
</svg>

Resources