I am trying to animate an svg element as following
a. I want to increase the width of the rect and control the rate of animation speed with a cubic-bezier
value, in this case it is cubic-bezier(0, 0, 0.58, 1)
.
b. There are also 3 lines which I also want to rotate, each of them exactly when the width of green
rect reaches them at it's animation cycle and not before that.
What I tried so far
I came across this post and my understanding was that in the bezier curve graph, for a given output ratio (progression of animation -plotted on y axis), the time ratio (time ratio plotted on x axis) can be calculated, by solving the following equation.
Since, I can already calculate, at what phase of the animation the green rect is going to come across those lines
{50/125, 80/125, 125/125},
they become the Y value and I can use them to solve for t
in y(t) and pass on that t
value to solve x by passing on t value in x(t), which becomes my time ratio and I can use that to calculate the delay of individual lines.
This is what the code looks like, which unfortunately does not do what I wanted.
I am not sure whether the math has gone wrong here or something else needs to be done. I can feel that I am close, but not sure what needs to be done here for the exact output.
const svg = document.querySelector("svg");
//max px to increase the width animation
var mxWidth = 125;
//animation duration of the green rect
var dur = 2000;
//set style of the green rect
var x = document.querySelector("[class=hitter");
x.style.setProperty('--v2', `${(mxWidth/svg.viewBox.baseVal.width)*100}%`);
x.style.setProperty('--dur', `${dur}ms`)
//what are the x corodinates of the lines
var lnX = [50, 80, 125];
//at what output ratio (width increase) the green rect is likely to
//come across the lines
var progPct = lnX.map(x => x / mxWidth);
//what is the desired cubic bez
var cubicBezCurvVal = "0, 0, 0.58, 1"
//split bezier curve value
var cleanVal = cubicBezCurvVal.split(',');
//clean space with map -retunrns new array with the function, original array unchnaged
var cleanVal = cleanVal.map((x) => parseFloat(x.replace(/ /g, '')));
//p0
const p0 = { x: 0, y: 0 };
//p3
const p3 = { x: 1, y: 1 };
//p1
const p1 = { x: cleanVal[0], y: cleanVal[1] };
//p2
const p2 = { x: cleanVal[2], y: cleanVal[3] };
const x0 = p0.x;
const y0 = p0.y;
const x1 = p1.x;
const y1 = p1.y;
const x2 = p2.x;
const y2 = p2.y;
const x3 = p3.x;
const y3 = p3.y;
const coefficient_a_y = y3 - 3 * y2 + 3 * y1 - y0;
const coefficient_b_y = 3 * (y2 - 2 * y1 + y0);
const coefficient_c_y = 3 * (y1 - y0);
const coefficient_d_y = y0 - y1;
function getY(prgRatio) {
const outputRatio = prgRatio /*y value of cubic bezier curve */
//get all the t values of y of the cubic equation
var solution_t_y = [
{ sol: 1, cubicBezierProgressRatio_y: outputRatio - coefficient_d_y },
{ sol: 2, cubicBezierProgressRatio_y: (-1 * coefficient_b_y + Math.sqrt(Math.pow(coefficient_b_y, 2) - (4 * coefficient_a_y * coefficient_c_y))) / (2 * coefficient_a_y) },
{ sol: 3, cubicBezierProgressRatio_y: (-1 * coefficient_b_y - Math.sqrt(Math.pow(coefficient_b_y, 2) - (4 * coefficient_a_y * coefficient_c_y))) / (2 * coefficient_a_y) }
]
var solTY = solution_t_y.filter(x => x.cubicBezierProgressRatio_y > 0 && x.cubicBezierProgressRatio_y < 1);
function getX(t) {
return Math.pow(1 - t, 3) * x0 +
3 * Math.pow(1 - t, 2) * t * x1 +
3 * (1 - t) * Math.pow(t, 2) * x2 +
Math.pow(t, 3) * x3;
}
solTY.forEach(
(a, i) => {
a.cubicBezierTimeRatio_x = getX(a.cubicBezierProgressRatio_y)
}
)
return solTY;
}
var timeRatio = [];
progPct.forEach(
(a) => timeRatio.push(getY(a))
)
//get all the receivers
var impacter = document.querySelectorAll("[class^='axisLines']");
timeRatio.forEach(
(a, i) => {
(i == 2) ? impacter[i].style.setProperty('--del', `${dur}ms`): impacter[i].style.setProperty('--del', `${(a[0].cubicBezierTimeRatio_x)*dur}ms`)
}
)
.hitter {
visibility: hidden;
/*hide default*/
animation-name: moveWidth;
animation-delay: 0s;
animation-duration: var(--dur);
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(0, 0, 0.58, 1);
animation-direction: normal;
animation-fill-mode: forwards;
}
[class^="axisLine"] {
transform-box: fill-box;
transform-origin: bottom;
stroke-width: 1;
color: green;
animation: rotate 1s cubic-bezier(0, 0, 0.58, 1) var(--del) 1 alternate forwards;
/*rotate 1s cubic-bezier(0, 0, 0.58, 1) var(--del) 1 alternate forwards;*/
}
@keyframes moveWidth {
0% {
visibility: visible;
/*show again*/
width: 0%;
}
100% {
visibility: visible;
/*Edit-2 maintains visibility after animation overs*/
width: var(--v2);
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(7.5deg);
}
}
<!DOCTYPE html>
<html>
<body>
<link rel="stylesheet" href="style.css">
</link>
<svg class="layer1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 200">
<rect class="bg" id="bg" width="200" height="100" fill="#e6e6e6"></rect>
<rect class="hitter" id="hitter" x="0" y="40" width="0" height="5" fill="#92D050" stroke="#92D050"
stroke-width="0.1" ></rect>
<line class="axisLines0" id="axisLines0" x1="50" x2="50" y1="0" y2="100" stroke="#ED7D31" stroke-width=".5"
></line>
<line class="axisLines1" id="axisLines1" x1="80" x2="80" y1="0" y2="100" stroke="#548235" stroke-width=".5"
></line>
<line class="axisLines2" id="axisLines2" x1="125" x2="125" y1="0" y2="100" stroke="#00B0F0" stroke-width=".5">
</line>
</svg>
<script src="index3.js"></script>
</body>
</html>