开发指导—利用组件&插值器动画实现HarmonyOS动效
一. 组件动画
在组件上创建和运行动画的快捷方式。具体用法请参考通用方法。
获取动画对象
通过调用animate方法获得animation对象,animation对象支持动画属性、动画方法和动画事件。
1 2 3 4 | <!-- xxx.hml --> <div class = "container" > <div id= "content" class = "box" onclick= "Show" ></div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; } .box{ width: 200px; height: 200px; background-color: #ff0000; margin-top: 30px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | /* xxx.js */ export default { data: { animation: '' , }, onInit() { }, onShow() { var options = { duration: 1500, }; var frames = [ { width:200,height:200, }, { width:300,height:300, } ]; this .animation = this .$element( 'content' ).animate(frames, options); //获取动画对象 }, Show() { this .animation.play(); } } |

说明
● 使用animate方法时必须传入Keyframes和Options参数。
● 多次调用animate方法时,采用replace策略,即最后一次调用时传入的参数生效。
设置动画参数
在获取动画对象后,通过设置参数Keyframes设置动画在组件上的样式。
1 2 3 4 5 6 7 | <!-- xxx.hml --> <div class = "container" > <div id= "content" class = "box" onclick= "Show" ></div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } .box{ width: 200px; height: 200px; #ff0000; margin-top: 30px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /* xxx.js */ export default { data: { animation: '' , keyframes:{}, options:{} }, onInit() { this .options = { duration: 4000, } this .keyframes = [ { transform: { translate: '-120px -0px' , scale: 1, rotate: 0 }, transformOrigin: '100px 100px' , offset: 0.0, width: 200, height: 200 }, { transform: { translate: '120px 0px' , scale: 1.5, rotate: 90 }, transformOrigin: '100px 100px' , offset: 1.0, width: 300, height: 300 } ] }, Show() { this .animation = this .$element( 'content' ).animate( this .keyframes, this .options) this .animation.play() } } |

说明
● translate、scale和rtotate的先后顺序会影响动画效果。
● transformOrigin只对scale和rtotate起作用。
在获取动画对象后,通过设置参数Options来设置动画的属性。
1 2 3 4 5 | <!-- xxx.hml --> <div class = "container" > <div id= "content" class = "box" onclick= "Show" ></div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; } .box{ width: 200px; height: 200px; background-color: #ff0000; margin-top: 30px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /* xxx.js */ export default { data: { animation: '' , }, onInit() { }, onShow() { var options = { duration: 1500, easing: 'ease-in' , delay: 5, iterations: 2, direction: 'normal' , }; var frames = [ { transform: { translate: '-150px -0px' } }, { transform: { translate: '150px 0px' } } ]; this .animation = this .$element( 'content' ).animate(frames, options); }, Show() { this .animation.play(); } } |

说明
direction:指定动画的播放模式。
normal: 动画正向循环播放。
reverse: 动画反向循环播放。
alternate:动画交替循环播放,奇数次正向播放,偶数次反向播放。
alternate-reverse:动画反向交替循环播放,奇数次反向播放,偶数次正向播放。
二. 插值器动画
动画动效
通过设置插值器来实现动画效果。(从API Version 6 开始支持。)
创建动画对象
通过createAnimator创建一个动画对象,通过设置参数options来设置动画的属性。
1 2 3 4 5 6 7 8 9 10 11 | <!-- xxx.hml --> <div class = "container" > <div style= "width: 300px;height: 300px;margin-top: 100px;background: linear-gradient(pink, purple);transform: translate({{translateVal}});" > </div> <div class = "row" > <button type= "capsule" value= "play" onclick= "playAnimation" ></button> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* xxx.css */ .container { width:100%; height:100%; flex-direction: column; align-items: center; justify-content: center; } button{ width: 200px; } .row{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 50px; margin-left: 260px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | // xxx.js import animator from '@ohos.animator' ; export default { data: { translateVal: 0, animation: null }, onInit() {}, onShow(){ var options = { duration: 3000, easing: "friction" , delay: "1000" , fill: 'forwards' , direction: 'alternate' , iterations: 2, begin: 0, end: 180 }; //设置参数 this .animation = animator.createAnimator(options) //创建动画 }, playAnimation() { var _this = this ; this .animation.onframe = function(value) { _this.translateVal= value }; this .animation.play(); } } |
说明
● 使用createAnimator创建动画对象时必须传入options参数。
● begin插值起点,不设置时默认为0。
● end插值终点,不设置时默认为1。
添加动画事件和调用接口
animator支持事件和接口,可以通过添加frame、cancel、repeat、finish事件和调用update、play、pause、cancel、reverse、finish接口自定义动画效果。animator支持的事件和接口具体见动画中的createAnimator。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!-- xxx.hml --> <div style= "flex-direction: column;align-items: center;width: 100%;height: 100%;" > <div style="width:200px;height: 200px;margin-top: 100px;background: linear-gradient(#b30d29, #dcac1b); transform: scale({{scaleVal}});"></div> <div style="width: {{DivWidth}};height: {{DivHeight}};margin-top: 200px; background: linear-gradient(pink, purple);margin-top: 200px;transform:translateY({{translateVal}});"> </div> <div class = "row" > <button type= "capsule" value= "play" onclick= "playAnimation" ></button> <button type= "capsule" value= "update" onclick= "updateAnimation" ></button> </div> <div class = "row1" > <button type= "capsule" value= "pause" onclick= "pauseAnimation" ></button> <button type= "capsule" value= "finish" onclick= "finishAnimation" ></button> </div> <div class = "row2" > <button type= "capsule" value= "cancel" onclick= "cancelAnimation" ></button> <button type= "capsule" value= "reverse" onclick= "reverseAnimation" ></button> </div> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /* xxx.css */ button{ width: 200px; } .row{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 150px; position: fixed ; top: 52%; left: 120px; } .row1{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 120px; position: fixed ; top: 65%; left: 120px; } .row2{ width: 65%; height: 100px; align-items: center; justify-content: space-between; margin-top: 100px; position: fixed ; top: 75%; left: 120px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | // xxx.js import animator from '@ohos.animator' ; import promptAction from '@ohos.promptAction' ; export default { data: { scaleVal:1, DivWidth:200, DivHeight:200, translateVal:0, animation: null }, onInit() { var options = { duration: 3000, fill: 'forwards' , begin: 200, end: 270 }; this .animation = animator.createAnimator(options); }, onShow() { var _this= this ; //添加动画重放事件 this .animation.onrepeat = function() { promptAction.showToast({ message: 'repeat' }); var repeatoptions = { duration: 2000, iterations: 1, direction: 'alternate' , begin: 180, end: 240 }; _this.animation.update(repeatoptions); _this.animation.play(); }; }, playAnimation() { var _this= this ; //添加动画逐帧插值回调事件 this .animation.onframe = function(value) { _this. scaleVal= value/150, _this.DivWidth = value, _this.DivHeight = value, _this.translateVal = value-180 }; this .animation.play(); }, updateAnimation() { var newoptions = { duration: 5000, iterations: 2, begin: 120, end: 180 }; this .animation.update(newoptions); this .animation.play(); //调用动画播放接口 }, pauseAnimation() { this .animation.pause(); //调用动画暂停接口 }, finishAnimation() { var _this= this ; //添加动画完成事件 this .animation.onfinish = function() { promptAction.showToast({ message: 'finish' }) }; this .animation.finish(); //调用动画完成接口 }, cancelAnimation(){ this .animation.cancel(); //调用动画取消接口 }, reverseAnimation(){ this .animation.reverse(); //调用动画倒放接口 } } |
说明
在调用update接口的过程中可以使用这个接口更新动画参数,入参与createAnimator一致。
动画帧
请求动画帧
请求动画帧时通过requestAnimationFrame函数逐帧回调,在调用该函数时传入一个回调函数。
runframe在调用requestAnimationFrame时传入带有timestamp参数的回调函数step,将step中的timestamp赋予起始的startTime。当timestamp与startTime的差值小于规定的时间时将再次调用requestAnimationFrame,最终动画将会停止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!-- xxx.hml --> <div class = "container" > <tabs onchange= "changecontent" > <tab-content> <div class = "container" > <stack style= "width: 300px;height: 300px;margin-top: 100px;margin-bottom: 100px;" > <canvas id= "mycanvas" style= "width: 100%;height: 100%;coral;" > </canvas> <div style= "width: 50px;height: 50px;border-radius: 25px;indigo;position: absolute;left: {{left}};top: {{top}};" > </div> </stack> <button type= "capsule" value= "play" onclick= "runframe" ></button> </div> </tab-content> </tabs> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } button{ width: 300px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | // xxx.js export default { data: { timer: null , left: 0, top: 0, flag: true , animation: null , startTime: 0, }, onShow() { var test = this .$element( "mycanvas" ); var ctx = test.getContext( "2d" ); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(300, 300); ctx.lineWidth = 5; ctx.strokeStyle = "red" ; ctx.stroke(); }, runframe() { this .left = 0; this .top = 0; this .flag = true ; this .animation = requestAnimationFrame( this .step); }, step(timestamp) { if ( this .flag) { this .left += 5; this .top += 5; if ( this .startTime == 0) { this .startTime = timestamp; } var elapsed = timestamp - this .startTime; if (elapsed < 500) { console.log( 'callback step timestamp: ' + timestamp); this .animation = requestAnimationFrame( this .step); } } else { this .left -= 5; this .top -= 5; this .animation = requestAnimationFrame( this .step); } if ( this .left == 250 || this .left == 0) { this .flag = ! this .flag } }, onDestroy() { cancelAnimationFrame( this .animation); } } |
说明
requestAnimationFrame函数在调用回调函数时在第一个参数位置传入timestamp时间戳,表示requestAnimationFrame开始去执行回调函数的时刻。
取消动画帧
通过cancelAnimationFrame函数取消逐帧回调,在调用cancelAnimationFrame函数时取消requestAnimationFrame函数的请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!-- xxx.hml --> <div class = "container" > <tabs onchange= "changecontent" > <tab-content> <div class = "container" > <stack style= "width: 300px;height: 300px;margin-top: 100px;margin-bottom: 100px;" > <canvas id= "mycanvas" style= "width: 100%;height: 100%;coral;" > </canvas> <div style= "width: 50px;height: 50px;border-radius: 25px;indigo;position: absolute;left: {{left}};top: {{top}};" > </div> </stack> <button type= "capsule" value= "play" onclick= "runframe" ></button> </div> </tab-content> </tabs> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 | /* xxx.css */ .container { flex-direction: column; justify-content: center; align-items: center; width: 100%; height: 100%; } button{ width: 300px; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | // xxx.js export default { data: { timer: null , left: 0, top: 0, flag: true , animation: null }, onShow() { var test = this .$element( "mycanvas" ); var ctx = test.getContext( "2d" ); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(300, 300); ctx.lineWidth = 5; ctx.strokeStyle = "red" ; ctx.stroke(); }, runframe() { this .left = 0; this .top = 0; this .flag = true ; this .animation = requestAnimationFrame( this .step); }, step(timestamp) { if ( this .flag) { this .left += 5; this .top += 5; this .animation = requestAnimationFrame( this .step); } else { this .left -= 5; this .top -= 5; this .animation = requestAnimationFrame( this .step); } if ( this .left == 250 || this .left == 0) { this .flag = ! this .flag } }, onDestroy() { cancelAnimationFrame( this .animation); } } |
说明
在调用该函数时需传入一个具有标识id的参数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了