• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Using the Animation Feature
2
3The animation feature provides component animation and inter-page animation, and opens the animation capability APIs for [interpolation calculation](../reference/apis/js-apis-curve.md) and [matrix transformation](../reference/apis/js-apis-matrix4.md), allowing you to design animation effects to a great extent.
4
5This section describes the following animation effects:
6
71. Animation on the splash screen: fade-in and fade-out of the logo.
82. Transition animation of shared elements on the food list page and food details page.
9
10## AnimateTo for Splash Screen Animation
11
12Component animations include attribute animations and **animateTo** explicit animations.
13
141. Attribute animation: animation for the universal attribute changes of a component.
152. Explicit animation: animation for a component changing from state A to state B, including the style, location information, and node addition and deletion. You only need to specify the start and end states, without the need for paying attention to the change process. The **animateTo** class also provides the callback for the playback state change events, which is an enhancement and encapsulation of the attribute animation.
16
17The splash screen animation refers to the fade-in and fade-out of the logo. After the animation is complete, the food list page is displayed. The following describes how to use **animateTo** to implement the splash screen animation.
18
191. Configure the splash screen animation to automatically play. The expected effect is as follows: **animateTo** automatically starts to play the animation once the logo page is displayed. Call the **onAppear** API of **Shape** to set the explicit animation.
20
21   ```ts
22   Shape() {
23     ...
24   }
25   .onAppear(() => {
26      animateTo()
27   })
28   ```
29
302. Create member variables of **opacity** and **scale** values and decorate them using the **@State** decorator, which indicates that the data is stateful and its change will trigger page refresh.
31
32   ```ts
33   @Entry
34   @Component
35   struct Logo {
36     @State private opacityValue: number = 0
37     @State private scaleValue: number = 0
38     build() {
39       Shape() {
40         ...
41       }
42      .scale({ x: this.scaleValue, y: this.scaleValue })
43      .opacity(this.opacityValue)
44      .onAppear(() => {
45        animateTo()
46       })
47     }
48   }
49   ```
50
513. Sets the **animateTo** animation curve. The acceleration curve of the logo is slow first and then fast. The Bezier curve cubicBezier, **cubicBezier(0.4, 0, 1, 1)** is used.
52
53   Before using interpolation calculation in the **animateTo**, you must import the **Curves** module.
54
55   ```ts
56   import Curves from '@ohos.curves'
57   ```
58
59   The **@ohos.curves** module provides the linear curve. For initialization function of the linear, step, cubicBezier, and spring interpolation curves, an interpolation curve object can be created based on the input parameters.
60
61   ```ts
62   @Entry
63   @Component
64   struct Logo {
65     @State private opacityValue: number = 0
66     @State private scaleValue: number = 0
67     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
68
69     build() {
70       Shape() {
71         ...
72       }
73      .scale({ x: this.scaleValue, y: this.scaleValue })
74      .opacity(this.opacityValue)
75      .onAppear(() => {
76        animateTo({
77           curve: this.curve1
78        })
79       })
80     }
81   }
82   ```
83
844. Set the animation duration to 1s, set the delay to 0.1s, and set the closure function for displaying animation events. That is, set **opacityValue** and **scaleValue** to change from 0 to 1 from the start point to the end point. In this way, the logo fades in and out.
85
86   ```ts
87   @Entry
88   @Component
89   struct Logo {
90     @State private opacityValue: number = 0
91     @State private scaleValue: number = 0
92     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
93
94     build() {
95       Shape() {
96         ...
97       }
98      .scale({ x: this.scaleValue, y: this.scaleValue })
99      .opacity(this.opacityValue)
100      .onAppear(() => {
101        animateTo({
102         duration: 1000,
103         curve: this.curve1,
104         delay: 100,
105        }, () => {
106          this.opacityValue = 1
107          this.scaleValue = 1
108         })
109       })
110     }
111   }
112   ```
113
1145. After the splash screen animation plays for 1 second, the **FoodCategoryList** page is displayed. Set the **onFinish** callback of **animateTo**. Invoke the **setTimeout** API of the timer. After a delay of 1s, call **router.replace** to display the **FoodCategoryList** page.
115
116   ```ts
117   import router from '@ohos.router'
118
119   @Entry
120   @Component
121   struct Logo {
122     @State private opacityValue: number = 0
123     @State private scaleValue: number = 0
124     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
125
126     build() {
127       Shape() {
128         ...
129       }
130      .scale({ x: this.scaleValue, y: this.scaleValue })
131      .opacity(this.opacityValue)
132      .onAppear(() => {
133
134        animateTo({
135         duration: 1000,
136          curve: this.curve1,
137          delay: 100,
138          onFinish: () => {
139            setTimeout(() => {
140              router.replaceUrl({ url: "pages/FoodCategoryList" })
141            }, 1000);
142          }
143        }, () => {
144          this.opacityValue = 1
145          this.scaleValue = 1
146         })
147       })
148     }
149   }
150   ```
151
152   The code is as follows:
153
154   ```ts
155   import Curves from '@ohos.curves'
156   import router from '@ohos.router'
157
158   @Entry
159   @Component
160   struct Logo {
161     @State private opacityValue: number = 0
162     @State private scaleValue: number = 0
163     private curve1 = Curves.cubicBezier(0.4, 0, 1, 1)
164     private pathCommands1: string = 'M319.5 128.1 c103.5 0 187.5 84 187.5 187.5 v15 a172.5 172.5 0 0 3 -172.5 172.5 H198 a36 36 0 0 3 -13.8 -1 207 207 0 0 0 87 -372 h48.3 z'
165     private pathCommands2: string = 'M270.6 128.1 h48.6 c51.6 0 98.4 21 132.3 54.6 a411 411 0 0 3 -45.6 123 c-25.2 45.6 -56.4 84 -87.6 110.4 a206.1 206.1 0 0 0 -47.7 -288 z'
166
167     build() {
168       Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
169         Shape() {
170           Path()
171             .commands('M162 128.7 a222 222 0 0 1 100.8 374.4 H198 a36 36 0 0 3 -36 -36')
172             .fill(Color.White)
173             .stroke(Color.Transparent)
174           Path()
175             .commands(this.pathCommands1)
176             .fill('none')
177             .stroke(Color.Transparent)
178             .linearGradient(
179             {
180               angle: 30,
181               colors: [["#C4FFA0", 0], ["#ffffff", 1]]
182             })
183             .clip(new Path().commands(this.pathCommands1))
184
185           Path()
186             .commands(this.pathCommands2)
187             .fill('none')
188             .stroke(Color.Transparent)
189             .linearGradient(
190             {
191               angle: 50,
192               colors: [['#8CC36A', 0.1], ["#B3EB90", 0.4], ["#ffffff", 0.7]]
193             })
194             .clip(new Path().commands(this.pathCommands2))
195         }
196         .height('630px')
197         .width('630px')
198         .scale({ x: this.scaleValue, y: this.scaleValue })
199         .opacity(this.opacityValue)
200         .onAppear(() => {
201           animateTo({
202             duration: 1000,
203             curve: this.curve1,
204             delay: 100,
205             onFinish: () => {
206               setTimeout(() => {
207                 router.replaceUrl({ url: "pages/FoodCategoryList" })
208               }, 1000);
209             }
210           }, () => {
211             this.opacityValue = 1
212             this.scaleValue = 1
213           })
214         })
215
216         Text('Healthy Diet')
217           .fontSize(26)
218           .fontColor(Color.White)
219           .margin({ top: 300 })
220
221         Text('Healthy life comes from a balanced diet')
222           .fontSize(17)
223           .fontColor(Color.White)
224           .margin({ top: 4 })
225       }
226       .width('100%')
227       .height('100%')
228       .linearGradient(
229         {
230           angle: 180,
231           colors: [['#BDE895', 0.1], ["#95DE7F", 0.6], ["#7AB967", 1]]
232         })
233    }
234   }
235   ```
236
237   ![animation-feature](figures/animation-feature.gif)
238
239## Page Transition Animation
240
241Implement the shared element transition between the food list page and the food details page. That is, after you click **FoodListItem** or **FoodGridItem**, the food thumbnail is zoomed in, and then you are redirected to the large image on the food details page.
242
2431. Set the **sharedTransition** method for the **\<Image>** component of **FoodListItem** and **FoodGridItem**. The transition ID is **foodItem.id**, the duration of the transition animation is 1s, and the delay is 0.1s. The change curve is **Curves.cubicBezier(0.2, 0.2, 0.1, 1.0)**. You need to import the **Curves** module first.
244
245   During the shared element transition, the attributes of the current element are carried. Therefore, create a **\<Row>** component as the parent component of the **\<Image>** component, and set the background color on the **\<Row>** component.
246
247   Set **autoResize** to **false** for the **\<Image>** component of **FoodListItem**. The **\<Image>** component adjusts the size of the image source based on the final display area by default to optimize the image rendering performance. In the transition animation, the image will be reloaded during the zoom-in process. Therefore, to ensure the smoothness of the transition animation, set **autoResize** to **false**.
248
249   ```ts
250   // FoodList.ets
251   import Curves from '@ohos.curves'
252
253   @Component
254   struct FoodListItem {
255     private foodItem: FoodData
256     build() {
257       Navigator({ target: 'pages/FoodDetail' }) {
258         Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
259           Row() {
260             Image(this.foodItem.image)
261               .objectFit(ImageFit.Contain)
262               .autoResize(false)
263               .height(40)
264               .width(40)
265               .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 })
266           }
267
268           .margin({ right: 16 })
269           Text(this.foodItem.name)
270             .fontSize(14)
271             .flexGrow(1)
272           Text(this.foodItem.calories + ' kcal')
273             .fontSize(14)
274         }
275         .height(64)
276       }
277       .params({ foodData: this.foodItem })
278       .margin({ right: 24, left:32 })
279     }
280   }
281
282   @Component
283   struct FoodGridItem {
284     private foodItem: FoodData
285     build() {
286       Column() {
287         Row() {
288           Image(this.foodItem.image)
289             .objectFit(ImageFit.Contain)
290             .autoResize(false)
291             .height(152)
292             .width('100%')
293             .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 })
294         }
295         Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
296           Text(this.foodItem.name)
297             .fontSize(14)
298             .flexGrow(1)
299             .padding({ left: 8 })
300           Text(this.foodItem.calories + 'kcal')
301             .fontSize(14)
302             .margin({ right: 6 })
303         }
304         .height(32)
305         .width('100%')
306         .backgroundColor('#FFe5e5e5')
307       }
308       .height(184)
309       .width('100%')
310       .onClick(() => {
311         router.pushUrl({ url: 'pages/FoodDetail', params: { foodData: this.foodItem } })
312       })
313     }
314   }
315
316
317   ```
318
3192. Sets the **sharedTransition** method for the **\<Image>** component of **FoodImageDisplay** on the **FoodDetail** page. The setting method is the same as that mentioned above.
320
321   ```ts
322   import Curves from '@ohos.curves'
323
324   @Component
325   struct FoodImageDisplay {
326     private foodItem: FoodData
327     build() {
328       Stack({ alignContent: Alignment.BottomStart }) {
329         Image(this.foodItem.image)
330           .objectFit(ImageFit.Contain)
331           .sharedTransition(this.foodItem.id, { duration: 1000, curve: Curves.cubicBezier(0.2, 0.2, 0.1, 1.0), delay: 100 })
332         Text(this.foodItem.name)
333           .fontSize(26)
334           .fontWeight(500)
335           .margin({ left: 26, bottom: 17.4 })
336       }
337       .height(357)
338     }
339   }
340   ```
341
342   ![animation-feature1](figures/animation-feature1.gif)
343
344   Now we have completed the drawing of the startup logo, splash screen animation, and transition animation between pages. By applying and combining the various animation APIs in the declarative development framework, you can create a more immersive experience for your applications.
345