1# Basic Camera Animation (ArkTS)
2
3When using the camera, transitions such as changing camera modes or switching between front and rear cameras will always involve replacing the preview stream. To enhance user experience, smooth animations can be effectively incorporated. This topic describes how to use preview stream snapshots and ArkUI's [explicit animations](../../reference/apis-arkui/arkui-ts/ts-explicit-animatetoimmediately.md) to implement three key scene transitions:
4
5- Mode switching: Use preview stream snapshots to create a blur effect for transition.
6
7   The following depicts the transition from video mode to photo mode.
8
9   ![](figures/mode-switching.gif)
10
11- Front/Rear camera switching: Use preview stream snapshots to create a blur-and-flip effect for transition.
12
13   The following demonstrates the transition from using the front camera to the rear camera.
14
15   ![](figures/front-rear-switching.gif)
16
17- Photo capture blackout: Use a blackout component to overlay the preview stream to create a blackout transition.
18
19  The following depicts the moment of photo capture.
20
21  ![](figures/flash-black.gif)
22
23## Blackout Animation
24
25Use component overlay to implement the blackout effect.
26
271. Import dependencies. Specifically, import the camera, image, and ArkUI modules.
28
29   ```ts
30   import { curves } from '@kit.ArkUI';
31   ```
32
332. Build a blackout component.
34
35   Define a blackout component, which is displayed during camera blackouts for photo capture or front/rear camera switching. This component will block the XComponent.
36
37   Define the component's properties as follows:
38
39   ```ts
40   @State isShowBlack: boolean = false; // Whether to show the blackout component.
41   @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; // Entry for the blackout animation.
42   @State flashBlackOpacity: number = 1; // Opacity of the blackout component.
43   ```
44
45   Implement the logic of the blackout component as follows:
46
47   ```ts
48   // The component is displayed during camera blackouts for photo capture or camera switching and is used to block the XComponent.
49   if (this.isShowBlack) {
50     Column()
51       .key('black')
52       .width(px2vp(1080)) // The width and height must be the same as those of the XComponent of the preview stream. The layer is above the preview stream and below the snapshot component.
53       .height(px2vp(1920))
54       .backgroundColor(Color.Black)
55       .opacity(this.flashBlackOpacity)
56   }
57   ```
58
593. Implement the blackout effect.
60
61   ```ts
62   function flashBlackAnim() {
63     console.info('flashBlackAnim E');
64     this.flashBlackOpacity = 1; // The blackout component is opaque.
65     this.isShowBlack = true; // Show the blackout component.
66     animateToImmediately({
67       curve: curves.interpolatingSpring(1, 1, 410, 38),
68       delay: 50, // A black screen is displayed after a delay of 50 ms.
69       onFinish: () => {
70         this.isShowBlack = false; // Hide the blackout component.
71         this.flashBlackOpacity = 1;
72         console.info('flashBlackAnim X');
73       }
74     }, () => {
75       this.flashBlackOpacity = 0; // The blackout component is changed from opaque to transparent.
76     })
77   }
78   ```
79
804. Trigger the blackout effect.
81
82   When a user touches the **PHOTO** button, the value of **CaptureClick** bound to the StorageLink is updated, and **onCaptureClick** is invoked. In this case, the animation starts to play.
83
84   ```ts
85   onCaptureClick(): void {
86     console.info('onCaptureClick');
87       console.info('onCaptureClick');
88       this.flashBlackAnim();
89   }
90   ```
91
92
93
94## Blur Animation
95
96You can use preview stream snapshots to create a blur effect for mode switching or front/rear camera switching.
97
981. Import dependencies. Specifically, import the camera, image, and ArkUI modules.
99
100   ```ts
101   import { camera } from '@kit.CameraKit';
102   import { image } from '@kit.ImageKit';
103   import { curves } from '@kit.ArkUI';
104   ```
105
1062. Obtain a preview stream snapshot.
107
108   Preview stream snapshots are obtained by calling [image.createPixelMapFromSurface](../../reference/apis-image-kit/js-apis-image.md#imagecreatepixelmapfromsurface11) provided by the image module. In this API, **surfaceId** is the surface ID of the current preview stream, and **size** is the width and height of the current preview stream profile. Create a snapshot utility class (TS file), import the dependency, and export the snapshot retrieval API for the page to use. The code snippet below shows the implementation of the snapshot utility class:
109
110   ```ts
111   import { image } from '@kit.ImageKit';
112
113   export class BlurAnimateUtil {
114     public static surfaceShot: image.PixelMap;
115
116     /**
117      * Obtain a surface snapshot.
118      * @param surfaceId
119      * @returns
120      */
121     public static async doSurfaceShot(surfaceId: string) {
122       console.info(`doSurfaceShot surfaceId:${surfaceId}.`);
123       if (surfaceId === '') {
124         console.error('surface not ready!');
125         return;
126       }
127       try {
128         if (this.surfaceShot) {
129           await this.surfaceShot.release();
130         }
131         this.surfaceShot = await image.createPixelMapFromSurface(surfaceId, {
132           size: { width: 1920, height: 1080 }, // Obtain the width and height of the preview stream profile.
133           x: 0,
134           y: 0
135         });
136         let imageInfo: image.ImageInfo = await this.surfaceShot.getImageInfo();
137         console.info('doSurfaceShot surfaceShot:' + JSON.stringify(imageInfo.size));
138       } catch (err) {
139         console.error(JSON.stringify(err));
140       }
141     }
142
143     /**
144      * Obtain the snapshot captured by calling doSurfaceShot.
145      * @returns
146      */
147     public static getSurfaceShot(): image.PixelMap {
148       return this.surfaceShot;
149     }
150   }
151   ```
152
1533. Build a snapshot component.
154
155   Define a snapshot component, and place it above the XComponent of the preview stream to block the XComponent.
156
157   Define the component's properties as follows:
158
159   ```ts
160   @State isShowBlur: boolean = false; // Whether to show the snapshot component.
161   @StorageLink('modeChange') @Watch('onModeChange') modeChangeFlag: number = 0; // Entry for triggering the mode switching animation.
162   @StorageLink('switchCamera') @Watch('onSwitchCamera') switchCameraFlag: number = 0; // Entry for triggering the front/rear camera switching animation.
163   @StorageLink('frameStart') @Watch('onFrameStart') frameStartFlag: number = 0; // Entry for the fade-out animation.
164   @State screenshotPixelMap: image.PixelMap | undefined = undefined; // PixelMap of the snapshot component.
165   @State surfaceId: string ="; // Surface ID of the XComponent of the current preview stream.
166   @StorageLink('curPosition') curPosition: number = 0; // Current camera position (front or rear).
167   @State shotImgBlur: number = 0; // Blur degree of the snapshot component.
168   @State shotImgOpacity: number = 1; // Opacity of the snapshot component.
169   @State shotImgScale: ScaleOptions = {x: 1, y: 1}; // Scale ratio of the snapshot component.
170   @State shotImgRotation: RotateOptions = { y: 0.5, angle: 0 } // Rotation angle of the snapshot component.
171   ```
172
173   Implement the snapshot component as follows:
174
175   ```ts
176   // Snapshot component is placed above the XComponent of the preview stream.
177   if (this.isShowBlur) {
178     Column() {
179       Image(this.screenshotPixelMap)
180         .blur(this.shotImgBlur)
181         .opacity(this.shotImgOpacity)
182         .rotate(this.shotImgRotation) // Provided by ArkUI for component rotation.
183         .scale(this.shotImgScale)
184         .width(px2vp(1080)) // The width and height must be the same as those of the XComponent of the preview stream. The layer is above the preview stream.
185         .height(px2vp(1920))
186         .syncLoad(true)
187     }
188     .width(px2vp(1080))
189     .height(px2vp(1920))
190   }
191   ```
192
1934. (Optional) Implement the fade-in blur effect.
194
195   The mode switching animation is implemented in two phases: fade-in blur animation and fade-out blur animation.
196
197   When a user touches a button, and the camera takes a snapshot of the preview stream. The snapshot component is displayed, which gradually blurs over the existing preview stream, implementing the fade-in blur animation.
198
199   > **NOTE**
200   >
201   > The **image.createPixelMapFromSurface** API, which is used to extract the surface content into a PixelMap, operates differently from the rendering logic of the XComponent. As such, different rotation compensations must be applied to both the image content and the component for the front and rear cameras.
202
203   ```ts
204   async function showBlurAnim() {
205     console.info('showBlurAnim E');
206     // Obtain the surface snapshot.
207     let shotPixel = BlurAnimateUtil.getSurfaceShot();
208     // The rear camera is used.
209     if (this.curPosition === 0) {
210       console.info('showBlurAnim BACK');
211       // For candy bar phones, a 90° rotation compensation is applied to the content for a snapshot taken with the rear camera.
212       await shotPixel.rotate(90); // Provided by Image Kit for image content rotation.
213       // For candy bar phones, a 0° rotation compensation is applied to the component for a snapshot taken with the rear camera.
214       this.shotImgRotation = { y: 0.5, angle: 0 };
215     } else {
216       console.info('showBlurAnim FRONT');
217       // For candy bar phones, a 270° rotation compensation is applied to the content for a snapshot taken with the front camera.
218       await shotPixel.rotate(270);
219       // For candy bar phones, a 180° rotation compensation is applied to the component for a snapshot taken with the front camera.
220       this.shotImgRotation = { y: 0.5, angle: 180 };
221     }
222     this.screenshotPixelMap = shotPixel;
223     // Initialize animation parameters.
224     this.shotImgBlur = 0; // No blur.
225     this.shotImgOpacity = 1; // Opaque.
226     this.isShowBlur = true; // Show the snapshot component.
227     animateToImmediately(
228       {
229         duration: 200,
230         curve: Curve.Friction,
231         onFinish: async () => {
232           console.info('showBlurAnim X');
233         }
234       },
235       () => {
236         this.shotImgBlur = 48; // Blur intensity of the snapshot component.
237       }
238     );
239   }
240   ```
241
2425. Implement the fade-out blur animation.
243
244   The fade-out blur animation is triggered by the event [on('frameStart')](../../reference/apis-camera-kit/js-apis-camera.md#onframestart) of the new preview stream. During this effect, the snapshot component gradually becomes clear, revealing the new preview stream.
245
246   ```ts
247   function hideBlurAnim(): void {
248     this.isShowBlack = false;
249     console.info('hideBlurAnim E');
250     animateToImmediately({
251       duration: 200,
252       curve: Curve.FastOutSlowIn,
253       onFinish: () => {
254         this.isShowBlur = false; // Hide the blur component.
255         this.shotImgBlur = 0;
256         this.shotImgOpacity = 1;
257         console.info('hideBlurAnim X');
258       }
259     }, () => {
260       // Change the opacity of the snapshot component.
261       this.shotImgOpacity = 0; // Opacity change of the snapshot component.
262     });
263   }
264   ```
265
2666. (Optional) Implement the blur-and-flip animation.
267
268   The animation is carried out in two phases: blur-and-flip and fade-out blur, where fade-out blur is the same as that in step 5.
269
270   The blur-and-flip animation is realized through two stages of component rotation—initially a 90° rotation outwards, and then a 90° rotation inwards—accompanied by additional effects such as blur, opacity changes, and scaling.
271
272   To ensure that the preview stream is not exposed during flip, you must also build a blackout component to mask the XComponent, following the instructions provided in step 2 in [Blackout Animation](#blackout-animation).
273
274   ```ts
275   /**
276    * A 90° rotation outwards first for transition between the front and real cameras.
277    */
278   async function rotateFirstAnim() {
279     console.info('rotateFirstAnim E');
280     // Obtain the surface snapshot.
281     let shotPixel = BlurAnimateUtil.getSurfaceShot();
282     // Switch from the rear camera to the front camera.
283     if (this.curPosition === 1) {
284       console.info('rotateFirstAnim BACK');
285       // For candy bar phones, a 90° rotation compensation is applied to the content for a snapshot when switching from the rear camera to the front camera.
286       await shotPixel.rotate(90); // Provided by Image Kit for image content rotation.
287       // For candy bar phones, a 0° rotation compensation is applied to the component for a snapshot when switching from the rear camera to the front camera.
288       this.shotImgRotation = { y: 0.5, angle: 0 };
289     } else {
290       console.info('rotateFirstAnim FRONT');
291       // For candy bar phones, a 270° rotation compensation is applied to the content for a snapshot when switching from the front camera to the rear camera.
292       await shotPixel.rotate(270);
293       // For candy bar phones, a 180° rotation compensation is applied to the component for a snapshot when switching from the front camera to the rear camera.
294       this.shotImgRotation = { y: 0.5, angle: 180 };
295     }
296     this.screenshotPixelMap = shotPixel;
297     this.isShowBlack = true; // Show the blackout component to mask the preview stream.
298     this.isShowBlur = true; // Show the snapshot component.
299     animateToImmediately(
300       {
301         duration: 200,
302         delay: 50, // This delay ensures that the component's scaling and blur effects are triggered in prior to the flip effect.
303         curve: curves.cubicBezierCurve(0.20, 0.00, 0.83, 1.00),
304         onFinish: () => {
305           console.info('rotateFirstAnim X');
306           // Trigger the second-phase flip effect after onFinish.
307           this.rotateSecondAnim();
308         }
309       },
310       () => {
311         // Flip outwards.
312         if (this.curPosition === 1) {
313           this.shotImgRotation = { y: 0.5, angle: 90 };
314         } else {
315           this.shotImgRotation = { y: 0.5, angle: 270 };
316         }
317       }
318     )
319   }
320
321   /**
322    * Flip inwards by 90°.
323    */
324   async function rotateSecondAnim() {
325     console.info('rotateSecondAnim E');
326     // Obtain the surface snapshot.
327     let shotPixel = BlurAnimateUtil.getSurfaceShot();
328     // The rear camera is used.
329     if (this.curPosition === 1) {
330       // For candy bar phones, a 90° rotation compensation is applied to the content for a snapshot taken with the rear camera.
331       await shotPixel.rotate(90);
332       // A -90° rotation compensation is applied to the component to ensure that the image is not mirrored after the second-phase flip.
333       this.shotImgRotation = { y: 0.5, angle: 90 };
334     } else { // The front camera is used.
335       // For candy bar phones, a 270° rotation compensation is applied to the content for a snapshot taken with the front camera.
336       await shotPixel.rotate(270);
337       // For candy bar phones, a 180° rotation compensation is applied to the component for a snapshot taken with the front camera.
338       this.shotImgRotation = { y: 0.5, angle: 180 };
339     }
340     this.screenshotPixelMap = shotPixel;
341     animateToImmediately(
342       {
343         duration: 200,
344         curve: curves.cubicBezierCurve(0.17, 0.00, 0.20, 1.00),
345         onFinish: () => {
346           console.info('rotateSecondAnim X');
347         }
348       },
349       () => {
350         // Flip inwards to the initial state.
351         if (this.curPosition === 1) {
352           this.shotImgRotation = { y: 0.5, angle: 0 };
353         } else {
354           this.shotImgRotation = { y: 0.5, angle: 180 };
355         }
356       }
357     )
358   }
359
360   /**
361    * Flip outwards by 90°.
362    */
363   function blurFirstAnim() {
364     console.info('blurFirstAnim E');
365     // Initialize animation parameters.
366     this.shotImgBlur = 0; // No blur.
367     this.shotImgOpacity = 1; // Opaque.
368     this.shotImgScale = { x: 1, y: 1 };
369     animateToImmediately(
370       {
371         duration: 200,
372         curve: Curve.Sharp,
373         onFinish: () => {
374           console.info('blurFirstAnim X');
375           this.blurSecondAnim();
376         }
377       },
378       () => {
379         // Blur.
380         this.shotImgBlur = 48;
381         // Scale out.
382         this.shotImgScale = { x: 0.75, y: 0.75 };
383       }
384     );
385   }
386
387   /**
388    * Flip inwards by 90°.
389    */
390   function blurSecondAnim() {
391     console.info('blurSecondAnim E');
392     animateToImmediately(
393       {
394         duration: 200,
395         curve: Curve.Sharp,
396         onFinish: () => {
397           console.info('blurSecondAnim X');
398         }
399       },
400       () => {
401         // Scale in to the initial size.
402         this.shotImgScale = { x: 1, y: 1 };
403       }
404     )
405   }
406   ```
407
4087. Trigger the animations on demand.
409
410   For the mode switching animation, once a user touches the mode button, the **doSurfaceShot** API is invoked, the value of **modeChange** bound to the StorageLink is updated, and the **onModeChange** callback is triggered. In this case, the animation starts to play.
411
412   ```ts
413   onModeChange(): void {
414     console.info('onModeChange');
415     this.showBlurAnim();
416   }
417   ```
418
419   For the front/rear camera switching animation, once a user touches the button to switch between the front and rear cameras, the **doSurfaceShot** API is invoked, the value of **switchCamera** bound to the StorageLink is updated, and the **onSwitchCamera** callback is triggered. In this case, the animation starts to play.
420
421   ```ts
422   onSwitchCamera(): void {
423     console.info('onSwitchCamera');
424     this.blurFirstAnim();
425     this.rotateFirstAnim();
426   }
427   ```
428
429   For the fade-out blur animation, you must listen for the event [on('frameStart')](../../reference/apis-camera-kit/js-apis-camera.md#onframestart) of the preview stream. Once the value of **frameStart** bound to the StorageLink is updated, and the **onFrameStart** callback is triggered, the animation starts.
430
431   ```ts
432   onFrameStart(): void {
433     console.info('onFrameStart');
434     this.hideBlurAnim();
435   }
436   ```
437