This time we will improve the animation library of last issue. In the Constructor argument of the Animation class, we see that all other arguments are used. But we haven’t used timingFunction yet. So let’s deal with that together.

The logic of timingFucntion is mainly used in the run method of Animation. If you remember from a previous article on CSS animation, we learned about cubic Bezier curves.

In cubic Bezier curves, the Y-axis is progress and the X-axis is time. A cubic Bezier curve is the path from [0, 0] coordinates to [1, 1] coordinates.

Our timingFunction is also such a function, passing in our time progress to get the progress of a moving trajectory of a cubic Bezier curve.

TimingFunction

TimingFunction TimingFunction is a function of 0 to 1 time that returns a progress of 0 to 1 through three Bessel calculations.

Within CSS we have several libraries, such as Linear timingFunction. So here we also try to write some timingfunctions that are close to the cubic Bezier curve.

Linear

First let’s implement Linear. This curve is relatively easy to implement. It’s a one-to-one timingFunction that doesn’t change by itself. So the code is very simple:

export let linear = v= > v;
Copy the code

Implement a cubic Bessel function

Linear does not have any easing effects. If we want to animate ease, ease-in, ease-out in cubic Bezier curves, we need to use Newton’s integral method to find the progress of a point in time.

So here we need to implement the function of the cubic Bezier curve to do the calculation.

First we can look at the website of the Cubic Bezier curve:

We can see that the cubic Bezier curve is computed by using four parameters. These four parameters determine the effect of our animation curve.

Here we also do not go to the detailed analysis and deduce the three times bezier curve calculation method, we can directly from the C++ library to take out the code of this function, and then convert the code into JavaScript and directly use it.

export function cubicBezier(p1x, p1y, p2x, p2y) {
  const ZERO_LIMIT = 1e-6;
  // Calculate the polynomial coefficients,
  Implicit first and last control points are (0,0) and (1,1).
  const ax = 3 * p1x - 3 * p2x + 1;
  const bx = 3 * p2x - 6 * p1x;
  const cx = 3 * p1x;

  const ay = 3 * p1y - 3 * p2y + 1;
  const by = 3 * p2y - 6 * p1y;
  const cy = 3 * p1y;

  function sampleCurveDerivativeX(t) {
    // `ax t^3 + bx t^2 + cx t` expanded using Horner's rule
    return (3 * ax * t + 2 * bx) * t + cx;
  }

  function sampleCurveX(t) {
    return ((ax * t + bx) * t + cx) * t;
  }

  function sampleCurveY(t) {
    return ((ay * t + by) * t + cy) * t;
  }

  // Given an x value, find a parametric value it came from.
  function solveCurveX(x) {
    let t2 = x;
    let derivative;
    let x2;

    // https://trac.webkit.org/browser/trunk/Source/WebCore/platform/animation
    // first try a few iterations of Newton's method -- normally very fast.
    // http://en.wikipedia.org/wikiNewton's_method
    for (let i = 0; i < 8; i++) {
      // f(t) - x = 0
      x2 = sampleCurveX(t2) - x;
      if (Math.abs(x2) < ZERO_LIMIT) {
        return t2;
      }
      derivative = sampleCurveDerivativeX(t2);
      // == 0, failure
      /* istanbul ignore if */
      if (Math.abs(derivative) < ZERO_LIMIT) {
        break;
      }
      t2 -= x2 / derivative;
    }

    // Fall back to the bisection method for reliability.
    // bisection
    // http://en.wikipedia.org/wiki/Bisection_method
    let t1 = 1;
    /* istanbul ignore next */
    let t0 = 0;

    /* istanbul ignore next */
    t2 = x;
    /* istanbul ignore next */
    while (t1 > t0) {
      x2 = sampleCurveX(t2) - x;
      if (Math.abs(x2) < ZERO_LIMIT) {
        return t2;
      }
      if (x2 > 0) {
        t1 = t2;
      } else {
        t0 = t2;
      }
      t2 = (t1 + t0) / 2;
    }

    // Failure
    return t2;
  }

  function solve(x) {
    return sampleCurveY(solveCurveX(x));
  }

  return solve;
}
Copy the code

If you want to learn more about the logic and reasoning behind this code, check out the link in the code remarks.

Implement the animation effect library

All right, so we have this function called cubicBezier, and we can use it to implement ease, ease-in, ease-out, ease-in-out, and so on.

We can obtain four parameters for each commonly used animation effect through the three Bezier curves website. Pass in the function cubicBezier to get the desired progress value.

The method for each animation effect is implemented as follows:

Ease

export let ease = cubicBezier(0.25.0.1.0.25.1);
Copy the code

Ease In

export let easeIn = cubicBezier(0.42.0.1.1);
Copy the code

Ease Out

export let easeOut = cubicBezier(0.0.0.58.1);
Copy the code

Ease In Out

export let easeInOut = cubicBezier(0.42.0.0.58.1);
Copy the code

Finally we put all of these functions into an ease.js JavaScript file. This completes our timingFunction library.

Using TimingFunction

With the timingFunction library, we can use these animation functions in animation-demo.js to animate our elements.

Before we used this, the timingFunction and template parameters in animation.js were not given default values. To prevent unnecessary errors, we can give them a default value.

All we need to do is add two lines of logic to the Animation Class that give the parameters default values:

constructor(object, property, startValue, endValue, duration, delay, timingFunction, template) {
  timingFunction = timingFunction || (v= > v);
  template = template || (v= > v);

  this.object = object;
  this.property = property;
  this.startValue = startValue;
  this.endValue = endValue;
  this.duration = duration;
  this.timingFunction = timingFunction;
  this.delay = delay;
  this.template = template;
}
Copy the code

Next we go back to animation-demo.js and introduce the ease animation function via ease.js.

import {ease} from './ease.js';
Copy the code

Then add the Animation parameters to Timeline and pass in the Ease timingFunction.

tl.add(
  new Animation(
    document.querySelector('#el').style,
    'transform'.0.500.2000.0,
    ease,
    v= > `translate(${v}px)`));Copy the code

Does our ease animation implement the same animation as our TRANSITION ease in CSS? To prove that we have implemented the same ease animation as in CSS, we create an additional div and give it a TRANSITION from CSS. So we can visually see if they’re the same.

First add the HTML and CSS code to animation.html:

<style>
  :root {
    --bg-color: #0f0e18;
    --sub-bg-color: #2d2f42;
    --purple-color: fuchsia;
    --blue-color: aqua;
  }
  body {
    background: var(--bg-color);
  }
  .buttons {
    color: var(--purple-color);
  }
  .purple {
    color: var(--purple-color);
  }
  .blue {
    color: var(--blue-color);
  }
  .box {
    width: 100px;
    height: 100px;
    background-color: aqua;
    margin-bottom: 1rem;
  }
  .ease-box {
    background-color: fuchsia ! important;
  }
  button {
    background: transparent;
    color: var(--blue-color);
    border: 1px solid aqua;
    padding: 0.5 rem 1rem;
    cursor: pointer;
    transition: all 400ms ease;
  }
  button:hover {
    background: var(--blue-color);
    color: # 333;
  }
</style>

<body>
  <div class="box" id="el"></div>
  <div class="box ease-box" id="el2"></div>
  <button id="pause-btn">Pause</button>
  <button id="resume-btn">Resume</button>
  <script src="./main.js"></script>
</body>
Copy the code

Then we’ll go back to animation-demo.js and add the transition attribute to el2.

document.querySelector('#el2').style.transition = 'transform 2s ease';
document.querySelector('#el2').style.transform = 'translateX(500px)';
Copy the code

Then let’s look at the effect:

The blue box is the Ease animation we wrote ourselves, and the purple box is the EASE animation in CSS.

We can say that they are basically the same, except that there is a slight difference in the calculation of the three bezier curves in C++ and CSS resulting in a slight difference in the animation. But it’s pretty much the same.

The point is that our JavaScript animation can be suddenly paused at any time, can also be triggered to continue playing at any time. But CSS animations can’t do the same, they just play to the end.


Realize the reset

Finally, we implemented the reset function in Timeline as well. In fact, the logic of this function is very simple, it needs to use the function we have.

  • First I need to pause the animation
  • resetstartTimeThe start time is the current time
  • resetPAUSE_TIME0
  • resetPAUSE_START0
  • resetANIMATIONnew Set()
  • resetSTART_TIMESnew Map()
  • resetTICK_HANDLERnull
reset() {
  this.pause();
  this[PAUSE_TIME] = 0;
  this[PAUSE_START] = 0;
  this[ANIMATIONS] = new Set(a);this[START_TIMES] = new Map(a);this[TICK_HANDLER] = null;
}
Copy the code

In this way we have completed a relatively complete animation library.


Add status management to the timeline

Although our animation library and timeline function has been very perfect. But in fact there are some existing problems. For example, we called resume instead of pause, which might cause some problems.

So we need to arrange state management for this library to make this class more robust.

Initialization time

First of all, when the Timeline is instantiated, we inject a Initiated state, indicating that the Timeline is in the initialization state.

constructor() {
  this.state = 'Initiated';
  this[ANIMATIONS] = new Set(a);this[START_TIMES] = new Map(a); }Copy the code

At the beginning of the

When the start method of the Timeline is executed, we can determine whether the Timeline is initialized or not. If it is not directly disconnected, the execution will not continue. Of course we can throw an error here, but that depends on our API design style.

If we can start the Timeline, we can change the status to Started. So let’s make this Timeline a state after the start.

start() {
    if (!this.state === 'Initiated') return;
    this.state = 'Started';
    / *... * /
}
Copy the code

When the suspended

The judgment of the suspended state is the same as that of the start state. Whether the Timeline has entered the start state is determined first. If not, the Timeline will be directly launched and executed.

If it can be executed, update the status to Paused.

pause() {
	if (!this.state === 'Started') return;
    this.state = 'Paused';
    / *... * /
}
Copy the code

recovery

Check whether the execution is paused. If not, stop execution. Otherwise, update the status to Started.

resume() {
	if (!this.state === 'Paused') return;
    this.state = 'Started';
    / *... * /
}
Copy the code

When the reset

Resets can be performed at any time and do not require any previous day conditions. Here we just need to update the state to Initiated.

reset() {
	this.pause();
    this.state = 'Initiated';
    / *... * /
}
Copy the code

The final addition does not require any interception or state change. So add method we can leave it alone.


The last

In terms of management, we should actually put animation related files into a new project, but we will not do this part in detail here. Students can arrange by themselves.

So we have completed the Animation Library.

In the next article we will use this animation library in conjunction with our Carousel. Finally use this to make a complete custom component library.

I’m Three Diamonds from Tech Galaxy, a tech guy who is reinventing knowledge. See you next time.


⭐️ three elder brother recommended

Open Source Project Recommendation

Hexo Theme Aurora

Thanks for your support and continuous feedback and suggestions

Recently, bloggers are fully engaged in the development of a”future-oriented“Hexo theme, aurora-themed blog theme.

If you’re a developer, creating a personal blog can be another bright spot on your resume. And if you have a really cool blog, it’s even brighter. It’s just sparkling.

If you like this theme, please click 🌟 on Github and let each other shine

Github address: github.com/auroral-ui/…

Topics using document: aurora. Tridiamond. Tech/useful /


Bloggers are learning live at station B. Welcome to live Studio.

We are here to supervise each other, encourage each other, and strive to embark on the road of learning in life, so that learning changes our life!

It is boring and lonely on the way to study, but I hope it can bring us more company and more encouragement. Let’s refuel together! (๑ • ̀ ㅂ ́) organisation