This post is a sequel to how to make Lottie animations and apply them to front-end projects, so I won’t go into reference and load animations here, but rather an introduction to further use of Lottie (I’m doing this demo in Vue, so there will be some vUE specific uses).

  • Json source file
  • Structure in AE

  • Animation effects

Flexible control animation playback

There are some business requirements where complex animations are suitable for Lottie, such as a lottery, animations of some physical collisions during the lottery, and some excessive magic lighting before showing the results. In front-end development, it is common to call the back-end interface (to get the results of the draw) while playing some excessive animation, and then show the results when they are obtained. The ideal effect is to get the result just as the animation ends. However, the interface request time is ultimately uncontrollable, so it is necessary to do some processing of the animation playback process and playback time to match the interface request time. The official provides some API portal to control the playback of the red envelope animation above as an example, animation is a complete lottery process, but the actual application because of the interface time, can be divided into three parts of the animation.

  1. Step1 prelude stage (red envelope amplification): play the animation once
  2. Step2 request phase (red envelope rotation) : when initiating the request, open the loop play until the interface request comes back, and end the play
  3. Step3 result stage (gradually show “amount”) : play the animation once.

    In a specific business scenario, the “amount” should be replaced with a specific result number, which we will discuss later, but will not discuss for now.

Advanced (1) animation segmentation, through asynchronous packaging to achieve playback control

Play clips

The playSegments method is officially available. The first argument is a two-element array [start,end] that plays the segments between the specified start and end frames. (Frames 0-50 of the animation are the enlargement process of the red envelope, 51-60 of the animation rotation process, and 61-90 of the animation text appearance process.)

Official document screenshot: playSegments ↑

Taking the prelude as an example, the general idea of the code is:

anim.playSegments([0.50]);
Copy the code

End of perception

To realize step-by-step playback after splitting, we need to use promise to encapsulate each step into an asynchronous task. To implement this asynchronous encapsulation, we need to be aware of the end of the play and call resolve at the end. An official complete event is provided, which is triggered every time an animation ends. The end of each play means that both Play and playSegments perform a callback when the whole or the last frame of a segment has been rendered. Note that Lottie does not tell us which animation is currently playing. If we need to know which animation is currently playing, We need to make our own distinctions. I will add a complete listener to each animation, resolve the different data in the callback and remove the current listener. (Why remove it? If not removed, the callback of the first animation will still be executed after the second animation, and the callback of the first and second animations will be executed after the third animation)



Screenshot of official document events ↑

Taking the prelude as an example, the general idea of the code is:

new Promise((resolve) = > {
  anim.playSegments([51.60].true);
  anim.addEventListener("complete".() = > {
    resolve("step1 end");
    anim.removeEventListener("complete");
  });
});
Copy the code

Combine async await to play the animation in 1-1-2-3 order

With the above foundation, we can freely control each clip. The reason why the first clip is played twice here is to make it obvious that the control of the animation is successful.

  • Define the methods step1st, step2nd, and step3rd respectively represent the prelude stage, the transition stage, and the result stage
  • Control playback with async await
import lottie from "lottie-web";
const JSON_DATA = require(".. /.. /public/lottery/data.json"); // Import the json source file
export default {
  data() {
    return {
      lottieAnim: null}; },async mounted() {
    this.lottieAnim = this.loadLottieAnimation(JSON_DATA);
    const r1a = await this.step1st();
    console.log(r1a);
    const r1b = await this.step1st();
    console.log(r1b);
    const r2 = await this.step2nd();
    console.log(r2);
    const r3 = await this.step3rd();
    console.log(r3);
  },
  methods: {
    loadLottieAnimation(data) {
      return lottie.loadAnimation({
        container: this.$refs.LottieD, // Mount to the corresponding DOM node
        renderer: "svg".loop: false.animationData: data,
        autoplay: false}); },step1st() {
      return new Promise((resolve) = > {
        this.lottieAnim.playSegments([0.50].true);
        this.lottieAnim.addEventListener("complete".() = > {
          resolve("step1 end");
          this.lottieAnim.removeEventListener("complete"); // Remove the event listener, otherwise this callback will also be performed during the second playback.
        });
        / / or
        /* this.lottieAnim.onComplete = () => { resolve("step1 end"); This. LottieAnim. The onComplete = null / / remove also, behind the onComplete assignment again can replace directly. }; * /
      });
    },
    step2nd() {
      return new Promise((resolve) = > {
        this.lottieAnim.playSegments([51.60].true);
        this.lottieAnim.addEventListener("complete".() = > {
          resolve("step2 end");
          this.lottieAnim.removeEventListener("complete");
        });
      });
    },
    step3rd() {
      return new Promise((resolve) = > {
        this.lottieAnim.playSegments([61.90].true);
        this.lottieAnim.addEventListener("complete".() = > {
          resolve("step3 end");
          this.lottieAnim.removeEventListener("complete"); }); }); ,}}};Copy the code

The effect

In advanced (2), the request logic is added, and the second animation is performed simultaneously with the request

The request is “concurrent” with the second animation, and the request and animation are both finished before the third animation. The key is “concurrent”, using Promise.all. The key ideas are as follows:

  • Add the getData method to simulate an asynchronous request
  • Modify the logic in Mounted. Use promise.all ([this.step2nd(), this.getData()]) to implement “concurrent” requests.
// vue-methods to simulate an asynchronous request
getData() {
  return new Promise((resolve) = > {
    setTimeout(() = > {
      resolve(parseInt((Math.random() + 1) * 100));
    }, 3000);
  });
},
// Wue-mounted, use promise.all ([this.step2nd(), this.getData()])) to make "concurrent" requests
this.lottieAnim = this.loadLottieAnimation(JSON_DATA);
const r1 = await this.step1st();
console.log(r1);
const [r2, data] = await Promise.all([this.step2nd(), this.getData()]);
console.log(r2, data);
const r3 = await this.step3rd();
console.log(r3);
Copy the code

The effect

Advance (3) Play the second animation in a loop until you get the request result

One major drawback to the above effect is that when the request takes a long time, the second animation (the transition phase) plays out before the results are available, making it feel stuck. You should implement the effect of playing the second animation in a loop. PlaySegments does not support passing loop parameters to control the loop of the segment.

  • Add a property pending to data as a condition control for the loop
  • Modify the getData method used to assign a value to pending
  • Add a rollStep2nd method that loops through the second animation function
  • Modify the logic in Mounted to replace step2nd with rollStep2nd

// vue-method
// Modify the getData method to assign a value to pending
async getData() {
  this.pending = true;
  const result = await new Promise((resolve) = > {
    setTimeout(() = > {
      resolve(parseInt((Math.random() + 1) * 100));
    }, 3000);
  });
  this.pending = false;
  return result
},
// Add a rollStep2nd method that loops through the second animation
async rollStep2nd() {
  while (this.pending) {
    await this.step2nd(); }},//vue-mounted, rollStep2nd instead of step2nd
this.lottieAnim = this.loadLottieAnimation(JSON_DATA);
const r1 = await this.step1st();
console.log(r1);
const [data] = await Promise.all([this.getData(),this.rollStep2nd()]);
console.log(data);
const r3 = await this.step3rd();
console.log(r3);

Copy the code

The effect

summary

Now we have achieved the desired state from the beginning:

  1. Step1 prelude stage (red envelope amplification): play the animation once
  2. Step2 request phase (red envelope rotation) : when initiating the request, open the loop play until the interface request comes back, and end the play.
  3. Step3 result stage (gradually show “amount”) : play the animation once. Next we will replace the “amount” with a specific result, and even replace the red envelope with another picture!

Replace the words

First we modify the above code to save the return value.

  • Add a new attribute, result, to data
  • Modify the getData method to assign a value to result
  • Add a changeText method to modify copywriting
  • Modify the logic in Mounted to call changeText before Step3rd
// vue-methods (getData) assigns a value to result
async getData() {
  this.pending = true;
  const result = await new Promise((resolve) = > {
    setTimeout(() = > {
      resolve(parseInt((Math.random() + 1) * 100));
    }, 500);
  });
  this.pending = false;
  this.result = result
  return result
}
// Vue-methods = new changeText ()
changeText(){},//vue-mounted
this.lottieAnim = this.loadLottieAnimation(JSON_DATA);
const r1 = await this.step1st();
console.log(r1);
const [data] = await Promise.all([this.getData(),this.rollStep2nd()]);
console.log(console.log('will change' amount 'to${data}`))
this.changeText()
const r3 = await this.step3rd();
console.log(r3);

Copy the code

Solution (1) Use the updateDocumentData method

There is a reference to the alternate text portal in the official Wiki.

UpdateDocumentData ↑

The idea is to find the element in anima. Render and call the updateDocumentData method. This method can change the value, size, color, and other information of the text.

// Vue-methods, change the changeText method
changeText(){
   this.lottieAnim.renderer.elements[0].updateDocumentData({ t: A '$'+this.result }, 0);// The value passed in must be a string, not a number, otherwise it will fail
}
Copy the code

The effect

You can see that the “amount” has become the specific return value.

Solution (2) through the Lottie-API library

Lottie-api is an official library for making modifications to the animation example, Portal. We mainly use the getKeyPath and getElements methods here. In contrast to solution (1), you only need to know the critical path of the text layer in AFTER Effects without having to look at the JSON data. GetKeyPath takes a string argument, comma separated for the path, and returns an object. This object has the getElements method, which returns a list of elements that satisfy the condition. The element has a setText method that takes a string parameter representing the new literal content. In this case, the layer name of the text is ‘result’, and call getKeyPath(‘result’).getelements () to get the list, since in this case there is only one layer named result, so the first element is the one we are looking for. Then you call the setText method, passing in the new text content.

In this example I’m exporting the composition of comp1 directly. If I create a comp that contains comp1, then getKeyPath is passed with the value “comp1,result”.



Official document screenshot getKeyPath ↑

  • Install the dependencies first and import them
  • Use the getKeyPath and getElements methods to find the element you need to modify
  • SetText modifies elements
import loApi from "lottie-api";
// Vue-methods, change the changeText method
changeText(){
   const api = loApi.createAnimationApi(this.lottieAnim);
   const elements = api.getKeyPath("result#DMNC_TXT").getElements(); // Find the object
   const ele = elements[0]
   ele.setText(A '$'+this.result);// The value passed in must be a string, not a number, otherwise it will fail
}
Copy the code

The effect

You can see that the “amount” has become the specific return value.

(3) Modify SVG with native JS

Leaving Lottie out of the way and rendering in SVG, all the data will eventually be converted to SVG elements, and it is possible to modify the SVG content with native JS. In this way, you need to know where the last element to be modified is located in the final SVG element, and you need to take a look at the SVG structure. However, a more convenient way is to add # XXX to the end of the layer name, so the generated SVG element structure is as follows, and you can quickly locate the element to be modified by document.querySelector. We then assign the innerHtml property to the new content.

SVG structure write

  • With the help of the designer, after effects layers were named with a special suffix “# XXX”
  • Document.queryselector locates the SVG element
  • Modify the value of the innerHtml property
// Vue-methods, change the changeText method
changeText(){
  const node = document.querySelector("#DMNC_TXT tspan");
    if (node) {
      node.innerHTML = A '$'+this.result; }}Copy the code

summary

  • Plan (1, 2) VS Plan (3)

    The third option has the advantage of being the most convenient and fast, but it also has the biggest limitation compared with the first two. ② If the text has complex animation such as path animation, the text is rendered separately by characters, and the replacement of the whole text content destroys the structure, and the animation will be wrong.

  • Plan (1) VS Plan (2)

    UpdateDocumentData has the advantage of being flexible, but is more cumbersome than using lottie-API. You need to find out which element is the object you want to modify, and if the JSON file is very large, or the elements are deeply nested, then this method is not suitable. Lottie-api is a convenient approach with a little less flexibility, but I think the key downside is the high learning cost. The official instructions for its use are too simple, other related learning resources are also few, a lot of things to look at the source code to explore.

    The underlying setText method above also calls updateDocumentData

Other Application Scenarios

Dynamically replace content other than text

The dynamic substitution mentioned above is only about replacing text, but updateDocumentData, Lottie-API, and SVG can be used to modify graphics colors, image urls, and other requirements. It’s a little bit more complicated, so this part is going to be a follow-up but an article.

Static replacement

There may be some business scenarios where you don’t need to replace the content during the animation, but you just need to modify the text and pictures before the animation is played, and then play the animation. Basic ideas:

  1. JSON conversion to A JS object or string handles some modifications
  2. Return a JSON
  3. loadAnimation

Next, I will replace the picture of the red envelope with the picture of the gift box, and replace the “¥amount” with “I won”.

As in the previous two sections, the code is not associated with the above.

import lottie from "lottie-web";
const JSON_DATA = require(".. /.. /public/lottery/data.json");
export default {
  data() {
    return {};
  },
  async mounted() {
    const data = this.changeData();
    this.loadLottieAnimation(data);
  },
  methods: {
    changeData(){
      return JSON_DATA
    }
    loadLottieAnimation(data) {
      return lottie.loadAnimation({
        container: this.$refs.LottieD, // Mount to the corresponding DOM node
        renderer: "svg".loop: false.animationData: data,
        autoplay: true}); ,}}};Copy the code

Modify the data by modifying the property value of the JS object

The key point is to find the location of the text and image in the JSON source file. ① Image resources are stored in the assets array in the JSON file, assets[n]. U represents the directory where the image is located, and aseets[n]. So you just reassign these two properties. JSON_DATA. Layers [0].t.D.k [0].s.t

JSON_DATA.assets[0].u = ""; // The relative directory where the image is located
JSON_DATA.assets[0].p = "Https://img2.baidu.com/it/u=3027700886, 229179963 & FM = 26 & FMT = auto&gp = 0. JPG"; //' image name '
JSON_DATA.layers[0].t.d.k[0].s.t = "I won the lottery."; // Table of contents
Copy the code

Convert to string processing

① This scheme is more convenient to modify the text, but the content of the replacement is best to have a unique identifier (such as the prefix of ¥), to prevent the replacement error. ② For the modification of the picture, if it is an online picture, also modify the text. But if it’s a local resource, it’s not appropriate. Why is that? When using local resources, many images may be in the same path. When replacing an image, you need to replace both the path (asset[n].u) and the file name (asset[n].p). This example only uses a local resource image, the following code I will still replace the image, just to show the effect, but be careful in practical application!!

const str = JSON.stringify(JSON_DATA);
const newStr = str
  .replace("¥amount"."I won the lottery.")
  .replace("images/"."")
  .replace("img_0.png"."Https://img2.baidu.com/it/u=3027700886, 229179963 & FM = 26 & FMT = auto&gp = 0. JPG");
const newJson = JSON.parse(newStr);
Copy the code

Use a combination of

In general, you can use a combination of these two approaches, which is the least error-prone and convenient to handle. Change the changeData method:

changeData(){
  JSON_DATA.assets[0].u = ""; // The relative directory where the image is located
  JSON_DATA.assets[0].p =
    "Https://img2.baidu.com/it/u=3027700886, 229179963 & FM = 26 & FMT = auto&gp = 0. JPG"; //' image name '
  const str = JSON.stringify(JSON_DATA);
  const newStr = str
    .replace("¥amount"."I won the lottery.")
  const newJson = JSON.parse(newStr);
  return newJson;
},
Copy the code

The effect

As you can see, the animation stays the same, just the image and the copy are replaced.

The logic of static replacement is simpler than that of dynamic replacement. In some simple business scenarios, you only need to change the resources at the time of initialization, which is more convenient and efficient.

reference

Github · Lottie – Web github· Lottie – API Four common and commonly used methods of SVG interactive animation