1# Application Animation Practice
2
3## Introduction
4Animations add a lively, delightful touch to your application. Yet, they must be handled carefully to avoid performance issues. This topic describes how to make smart use of animations in applications, and examines how less frequent layout and property changes and avoidance of redundant re-renders can reduce performance overhead.
5Specifically, the following modes are recommended in implementing animations:
6
7- Using **transition** for component transitions
8- Using transform properties for component layout changes
9- Using the same **animateTo** for properties with same animation settings.
10- Updating state variables for multiple **animateTo** calls at once
11
12## Smart Use of Animations
13
14### Using transition for Component Transitions
15To apply an animation to a component when it appears or disappears, two common methods are available:
16
17- Use the **animateTo** API and add logic processing to the animation end callback.
18- Use the **transition** API.
19
20**transition** is preferred over **animateTo**, primarily for two reasons:
21
22- **transition** leads to higher performance, in that it involves only one property change with a conditional statement, while **animateTo** involves two property changes, one before the animation and one after.
23- **transition** is easier to implement, without the need for complex logic processing in the animation end callback.
24
25Avoid: Change a component's opacity property from 1 to 0 to hide the component, and control the disappearance of the component in the animation end callback.
26
27```typescript
28@Entry
29@Component
30struct MyComponent {
31  @State mOpacity: number = 1;
32  @State show: boolean = true;
33  count: number = 0;
34
35  build() {
36    Column() {
37      Row() {
38        if (this.show) {
39          Text('value')
40            .opacity(this.mOpacity)
41        }
42      }
43      .width('100%')
44      .height(100)
45      .justifyContent(FlexAlign.Center)
46      Text('toggle state')
47        .onClick(() => {
48          this.count++;
49          const thisCount: number = this.count;
50          this.show = true;
51          // Hide or hide the <Text> component by changing its opacity property.
52          animateTo({ duration: 1000, onFinish: () => {
53            // In the last animation, hide the <Text> component, and then change the conditional statement to make the component disappear.
54            if (thisCount === this.count && this.mOpacity === 0) {
55              this.show = false;
56            }
57          } }, () => {
58            this.mOpacity = this.mOpacity === 1 ? 0 : 1;
59          })
60        })
61    }
62  }
63}
64```
65
66Preferable: Directly use the **transition** API to animate the opacity when the **Text** component appears or disappears.
67
68```typescript
69@Entry
70@Component
71struct MyComponent {
72  @State show: boolean = true;
73
74  build() {
75    Column() {
76      Row() {
77        if (this.show) {
78          Text('value')
79            // Set the ID so that the transition can be interrupted.
80            .id('myText')
81            .transition(TransitionEffect.OPACITY.animation({ duration: 1000 }))
82        }
83      }.width('100%')
84      .height(100)
85      .justifyContent(FlexAlign.Center)
86      Text('toggle state')
87        .onClick(() => {
88          // Use transition to animate the opacity when the component appears or disappears.
89          this.show = !this.show;
90        })
91    }
92  }
93}
94```
95
96### Using Transform Properties for Component Layout Changes
97You can change the layout of a component in either of the following methods:
98
99- Modify [layout properties](../ui/arkts-attribute-animation-overview.md), which will cause a UI re-layout. Common layout properties include **width**, **height**, and **layoutWeight**.
100- Modify [transform properties](../reference/apis-arkui/arkui-ts/ts-universal-attributes-transformation.md), which will cause the component to translate, rotate, or scale.
101
102As modifying transform properties does not involve the time-consuming UI re-layout, it is more time efficient than modifying layout properties, and is therefore recommended. The following examples use the preceding methods to scale up a component by 10 times.
103
104Avoid: Scale up a component by modifying its **width** and **height** properties.
105
106```typescript
107@Entry
108@Component
109struct MyComponent {
110  @State textWidth: number = 10;
111  @State textHeight: number = 10;
112
113  build() {
114    Column() {
115      Text()
116        .backgroundColor(Color.Blue)
117        .fontColor(Color.White)
118        .fontSize(20)
119        .width(this.textWidth)
120        .height(this.textHeight)
121
122      Button ('Layout Properties')
123        .backgroundColor(Color.Blue)
124        .fontColor(Color.White)
125        .fontSize(20)
126        .margin({ top: 30 })
127        .borderRadius(30)
128        .padding(10)
129        .onClick(() => {
130          animateTo({ duration: 1000 }, () => {
131            this.textWidth = 100;
132            this.textHeight = 100;
133          })
134        })
135    }
136}
137}
138```
139
140Because animating the location and size properties of a component involves new UI measurement and layout, performance overhead is high. If a component's location or size changes continuously, as in a pinch gesture, using the **scale** property is a better choice for the animation, as in the example below.
141
142Preferable: Scale up a component by modifying the **scale** property.
143
144```typescript
145@Entry
146@Component
147struct MyComponent {
148  @State textScaleX: number = 1;
149  @State textScaleY: number = 1;
150
151  build() {
152    Column() {
153      Text()
154        .backgroundColor(Color.Blue)
155        .fontColor(Color.White)
156        .fontSize(20)
157        .width(10)
158        .height(10)
159        .scale({ x: this.textScaleX, y: this.textScaleY })
160        .margin({ top: 100 })
161
162      Button ('Transform Properties')
163        .backgroundColor(Color.Blue)
164        .fontColor(Color.White)
165        .fontSize(20)
166        .margin({ top: 60 })
167        .borderRadius(30)
168        .padding(10)
169        .onClick(() => {
170          animateTo({ duration: 1000 }, () => {
171            this.textScaleX = 10;
172            this.textScaleY = 10;
173          })
174        })
175    }
176}
177}
178```
179
180### Using Same animateTo for Properties with Same Animation Settings
181Each time **animateTo** is called, a before-after comparison is required. Less **animateTo** calls can contribute to fewer component re-renders and thereby higher performance.
182In light of this, if properties share the same animation settings, consider placing them in the same animation closure.
183
184Avoid: Place state variables with the same animation settings in different animation closures.
185
186```typescript
187@Entry
188@Component
189struct MyComponent {
190  @State textWidth: number = 200;
191  @State color: Color = Color.Red;
192
193  func1() {
194    animateTo({ curve: Curve.Sharp, duration: 1000 }, () => {
195      this.textWidth = (this.textWidth === 100 ? 200 : 100);
196    });
197  }
198
199  func2() {
200    animateTo({ curve: Curve.Sharp, duration: 1000 }, () => {
201      this.color = (this.color === Color.Yellow ? Color.Red : Color.Yellow);
202    });
203  }
204
205  build() {
206    Column() {
207      Row()
208        .width(this.textWidth)
209        .height(10)
210        .backgroundColor(this.color)
211      Text('click')
212        .onClick(() => {
213          this.func1();
214          this.func2();
215        })
216    }
217    .width('100%')
218    .height('100%')
219  }
220}
221```
222
223Preferable: Combine animations with the same animation settings into one animation closure.
224
225```typescript
226@Entry
227@Component
228struct MyComponent {
229  @State textWidth: number = 200;
230  @State color: Color = Color.Red;
231
232  func() {
233    animateTo({ curve: Curve.Sharp, duration: 1000 }, () => {
234      this.textWidth = (this.textWidth === 100 ? 200 : 100);
235      this.color = (this.color === Color.Yellow ? Color.Red : Color.Yellow);
236    });
237  }
238
239  build() {
240    Column() {
241      Row()
242        .width(this.textWidth)
243        .height(10)
244        .backgroundColor(this.color)
245      Text('click')
246        .onClick(() => {
247          this.func();
248        })
249    }
250    .width('100%')
251    .height('100%')
252  }
253}
254```
255
256### Updating State Variables for Multiple animateTo Calls At Once
257**animateTo** compares the states before and after the animation closure is executed and animates the differences. For comparison, all changed state variables and dirty nodes are re-rendered before the animation closure of **animateTo** is executed.
258If state variables are re-rendered between **animateTo** calls, there may exist dirty nodes that need to be re-rendered before the next **animateTo** call, which may cause redundant re-renders.
259
260Avoid: Re-render state variables between **animateTo** calls.
261
262![Re-render State Variables Between animateTo Calls](figures/multi_animateto.png)
263
264The following code updates other states of a component between two **animateTo** calls.
265
266```typescript
267@Entry
268@Component
269struct MyComponent {
270  @State textWidth: number = 200;
271  @State textHeight: number = 50;
272  @State color: Color = Color.Red;
273
274  build() {
275    Column() {
276      Row()
277        .width(this.textWidth)
278        .height(10)
279        .backgroundColor(this.color)
280      Text('click')
281        .height(this.textHeight)
282        .onClick(() => {
283          this.textWidth = 100;
284          // textHeight is a non-animatable property.
285          this.textHeight = 100;
286          animateTo({ curve: Curve.Sharp, duration: 1000 }, () => {
287            this.textWidth = 200;
288          });
289          this.color = Color.Yellow;
290          animateTo({ curve: Curve.Linear, duration: 2000 }, () => {
291            this.color = Color.Red;
292          });
293        })
294    }
295    .width('100%')
296    .height('100%')
297  }
298}
299```
300
301Before the first **animateTo** call, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered. In the animation closure of the first **animateTo**, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered again and compared with the last rendering result to generate a width and height animation. Before the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again. In the animation closure of the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again to generate a background color animation. In sum, the **Row** component is re-rendered four times for its property changes.
302In this example, the state variable **textHeight** irrelevant to the animation is also modified. If the state change is not needed, it should be avoided to reduce redundant re-renders.
303
304Preferable: Update state variables in a unified manner.
305
306![Update State Variables in a Unified Manner 1](figures/unify_animateto.png) or ![Update State Variables in a Unified Manner 2](figures/unify_animateto_three_step.png)
307
308Preferable 1: Use the original state before **animateTo** to drive the animation from the original state to the target state. In this way, abrupt, unnatural changes can be avoided at the beginning of the animation.
309
310```typescript
311@Entry
312@Component
313struct MyComponent {
314  @State textWidth: number = 100;
315  @State textHeight: number = 50;
316  @State color: Color = Color.Yellow;
317
318  build() {
319    Column() {
320      Row()
321        .width(this.textWidth)
322        .height(10)
323        .backgroundColor(this.color)
324      Text('click')
325        .height(this.textHeight)
326        .onClick(() => {
327          animateTo({ curve: Curve.Sharp, duration: 1000 }, () => {
328            this.textWidth = (this.textWidth === 100 ? 200 : 100);
329          });
330          animateTo({ curve: Curve.Linear, duration: 2000 }, () => {
331            this.color = (this.color === Color.Yellow ? Color.Red : Color.Yellow);
332          });
333        })
334    }
335    .width('100%')
336    .height('100%')
337  }
338}
339```
340
341Before the first **animateTo** call, no dirty state variable or dirty node needs to be updated, and no re-render is required. In the animation closure of the first **animateTo**, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered and compared with the last rendering result to generate a width and height animation. Before the second **animateTo** call, because no additional statement is executed, there is no dirty state variable or dirty node that needs to be updated, and no re-render is required. In the animation closure of the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again to generate a background color animation. In sum, the **Row** component is re-rendered twice for its property changes.
342
343Preferable 2: Explicitly specify the initial values of all properties that require animation before **animateTo**, update the values to the node, and then animate the properties.
344
345```typescript
346@Entry
347@Component
348struct MyComponent {
349  @State textWidth: number = 200;
350  @State textHeight: number = 50;
351  @State color: Color = Color.Red;
352
353  build() {
354    Column() {
355      Row()
356        .width(this.textWidth)
357        .height(10)
358        .backgroundColor(this.color)
359      Text('click')
360        .height(this.textHeight)
361        .onClick(() => {
362          this.textWidth = 100;
363          this.color = Color.Yellow;
364          animateTo({ curve: Curve.Sharp, duration: 1000 }, () => {
365            this.textWidth = 200;
366          });
367          animateTo({ curve: Curve.Linear, duration: 2000 }, () => {
368            this.color = Color.Red;
369          });
370          this.textHeight = 100;
371        })
372    }
373    .width('100%')
374    .height('100%')
375  }
376}
377```
378
379Before the first **animateTo** call, the **textWidth** and **color** properties are modified. Therefore, the **Row** component needs to be re-rendered. In the animation closure of the first **animateTo**, the **textWidth** property is modified. Therefore, the **Row** component needs to be re-rendered again and compared with the last rendering result to generate a width and height animation. Before the second **animateTo** call, because no additional statement is executed, there is no dirty state variable or dirty node that needs to be updated, and no re-render is required. In the animation closure of the second **animateTo** call, the **color** property is modified. Therefore, the **Row** component needs to be re-rendered again to generate a background color animation. In sum, the **Row** component is re-rendered three times for its property changes.
380