I have a div that i want to project on a svg path. This path has 4 points (just as a dom-element) but is not a rectangle.
I've tried GSAP FLIP but this transforms to the boundingbox of the path.
https://codepen.io/KoentjeV/pen/mdQNgLr
<svg fill="none" xmlns="http://www.w3.org/2000/svg" id="thesvg" width="500" height="500" viewbox="0 0 500 500">
<path d="M0 103.85 L0 335.11L199.29 231.26 L199.29 0L0 103.85Z" fill="#6B8EFF" id="left" />
<path d="M201.29 231.26L400.57 335.11V103.85L201.29 0V231.26Z" fill="#1242FE" id="right" />
<path d="M1 336.11L200.29 232.26L399.57 336.11L200.29 431.56L1 336.11Z" fill="#5073FF" id="bottom" />
</svg>
<div class="toBeTransformed"></div>
.toBeTransformed {
width: 300px;
height: 300px;
position: absolute;
bottom: 50px;
right: 50px;
background: linear-gradient(180deg, #ffff00 0%, #e5e5e5 100%);
}
Flip.fit(".toBeTransformed", "#bottom", {
scale: true,
duration: 1.5,
delay: 1,
repeat: -1,
yoyo: 1
});
All credits to MvG's answer: "Finding the Transform matrix from 4 projected points (with Javascript)" on Math Exchange. I can't remotely explain the math behind.
The function explained will calculate a 3D matrix according to the 4 defined projection points and the target element's dimensions.
I've slightly modified the script to work with projection points in clockwise order. (original function expects an order of: top-left, top-right, bottom-left, bottom-right).
/**
* based on "Finding the Transform matrix from 4 projected points (with Javascript)"
* @MvG's answer
* https://math.stackexchange.com/questions/296794/finding-the-transform-matrix-from-4-projected-points-with-javascript#339033
* see original fiddle:
* http://jsfiddle.net/zbh98nLv/
*/
// retrieve coordinates from polygon
let projectionRect = document.getElementById('bottom');
let projectionPoints = projectionRect.points;
let targetEl = document.getElementById('box');
// set transform origin
targetEl.style.transformOrigin = '0 0';
// calculate matrix
let matrix3d = getMatrix3dFromPoints(targetEl, projectionPoints);
// apply matrix transform
targetEl.style.transform = `matrix3d(${matrix3d.join(', ')})`;
// calculate 3d transform matrix
function getMatrix3dFromPoints(el, pts) {
let [x1, y1, x2, y2, x4, y4, x3, y3] = [pts[0].x, pts[0].y, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y]
let w = el.offsetWidth,
h = el.offsetHeight;
let t = general2DProjection(0, 0, x1, y1, w, 0, x2, y2, 0, h, x3, y3, w, h, x4, y4);
for (let i = 0; i != 9; ++i) {
t[i] = t[i] / t[8];
}
let matrix3D = [t[0], t[3], 0, t[6],
t[1], t[4], 0, t[7],
0, 0, 1, 0,
t[2], t[5], 0, t[8]
];
return matrix3D;
}
function general2DProjection(
x1s, y1s, x1d, y1d,
x2s, y2s, x2d, y2d,
x3s, y3s, x3d, y3d,
x4s, y4s, x4d, y4d
) {
let s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s);
let d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d);
return multmm(d, adj(s));
}
function basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) {
let m = [x1, x2, x3, y1, y2, y3, 1, 1, 1];
let v = multmv(adj(m), [x4, y4, 1]);
return multmm(m, [
v[0], 0, 0,
0, v[1], 0,
0, 0, v[2]
]);
}
// multiply matrix and vector
function multmv(m, v) {
return [
m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
];
}
// multiply two matrices
function multmm(a, b) {
let c = Array(9);
for (let i = 0; i != 3; ++i) {
for (let j = 0; j != 3; ++j) {
let cij = 0;
for (let k = 0; k != 3; ++k) {
cij += a[3 * i + k] * b[3 * k + j];
}
c[3 * i + j] = cij;
}
}
return c;
}
// Compute the adjugate of m
function adj(m) {
return [
m[4] * m[8] - m[5] * m[7], m[2] * m[7] - m[1] * m[8], m[1] * m[5] - m[2] * m[4],
m[5] * m[6] - m[3] * m[8], m[0] * m[8] - m[2] * m[6], m[2] * m[3] - m[0] * m[5],
m[3] * m[7] - m[4] * m[6], m[1] * m[6] - m[0] * m[7], m[0] * m[4] - m[1] * m[3]
];
}
svg {
position: absolute;
top: 0px;
left: 0px;
}
#box {
position: absolute;
top: 0px;
left: 0px;
width: 200px;
height: 200px;
}
.toBeTransformed {
width: 300px;
height: 300px;
position: absolute;
left: 0;
top: 0px;
background: linear-gradient(180deg, #ffff00 0%, red 100%);
opacity: 1;
}
<svg fill="none" xmlns="http://www.w3.org/2000/svg" id="thesvg" width="500" height="500" viewbox="0 0 500 500">
<path d="M0 103.85 L0 335.11L199.29 231.26 L199.29 0L0 103.85Z" fill="#6B8EFF" id="left" />
<path d="M201.29 231.26L400.57 335.11V103.85L201.29 0V231.26Z" fill="#1242FE" id="right" />
<polygon points="1 336.11
200.29 232.26
399.57 336.11
200.29 431.56
1 336.11 " fill="#5073FF" id="bottom" />
</svg>
<div id="container">
<div id="box" class="toBeTransformed"></div>
</div>
To get the required 4 projection points I converted the <path>
to a <polygon>
.
This way we can easily get all points as a point array
let projectionRect = document.getElementById('bottom');
let projectionPoints = projectionRect.points;
In your case this is not a huge deal as you're projecting a HTMl element.