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>
When solving such problems, when several animations are interconnected, it is better to use SMIL SVG (in my opinion).
Since smil has a powerful tool that allows you to flexibly set the conditions for sequential start, pause, stop animations. If there is a need to further change the logic of the work of many animations, then it is enough to change the logical chains in the
begin
attribute.begin="dash1.end+1s"
The example below runs multiple animations one after the other, but we don't need to count the timings and delays for each animation.
Animation starts after clicking on the canvas
The example below runs multiple animations one after the other, but we don't need to count the timings and delays for each animation:
id="dash1"
starts after clicking on the canvasbegin="svg1.click"
begin="dash1.end"
Below is the complete code:
In the following example, I will double the time of 1 and 3 of the line growth animation -
dur="2s"
Note: that the logic of the application will not break, the animations will still go one after anotherIf CSS animations were used, then timings and delays would have to be recalculated for each animation.
Animation starts after clicking on the canvas