文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

HarmonyOS三方开源组件—鸿蒙JS实现仿蚂蚁森林

2024-12-03 00:39

关注

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

实现的效果图:

分析实现过程:

接收外部传递给组件的一个数组(小球能量列表),及收集能量动画结束的位置

  1. -- waterFlake.js --> 
  2.  props: { 
  3.     //后台返回的小球信息 
  4.         ballList: { 
  5.             default: [10, 11, 12, 13, 14], 
  6.         }, 
  7.     // 收集能量动画结束的X坐标 
  8.         collDestinationX: { 
  9.             default: 350 
  10.         }, 
  11.     // 收集能量动画结束的Y坐标 
  12.         collDestinationY: { 
  13.             default: 400 
  14.         } 
  15.     }, 

根据小球的数量,生成小球的随机位置坐标。

  1. // 生成小球的x坐标数组 
  2.  let xRandom = this.randomCommon(1, 8, this.ballList.length) 
  3.  let all_x = xRandom.map(item => { 
  4.      return item * width * 0.10 
  5.  }); 
  6.  //生成小球的y坐标数组 
  7.  let yRandom = this.randomCommon(1, 8, this.ballList.length); 
  8.  let all_y = yRandom.map(item => { 
  9.      return item * height * 0.08 
  10.  }) 
  1.  
  2.     randomCommon(minmax, n) { 
  3.         if (n > (max - min + 1) || max < min) { 
  4.             return null
  5.         } 
  6.         let result = []; 
  7.         let count = 0; 
  8.         while (count < n) { 
  9.             let num = parseInt((Math.random() * (max - min)) + min); 
  10.             let flag = true
  11.             for (let j = 0; j < n; j++) { 
  12.                 if (num == result[j]) { 
  13.                     flag = false
  14.                     break; 
  15.                 } 
  16.             } 
  17.             if (flag) { 
  18.                 result[count] = num; 
  19.                 count++; 
  20.             } 
  21.         } 
  22.         return result; 
  23.     }, 

根据传递进来的能量列表及生成的小球坐标,组装成我们需要的小球数据列表ballDataList[]

  1.  
  2. ballDataList: [], 
  1. let dataList = [] 
  2.  for (let index = 0; index < this.ballList.length; index++) { 
  3.      dataList.push({ 
  4.          content: this.ballList[index] + 'g'
  5.          x: all_x[index], 
  6.          y: all_y[index
  7.      }) 
  8.  } 
  9.  this.ballDataList = dataList; // 触发视图更新 

绘制小球随机显示界面

  1. -- waterFlake.hml --> 
  2. "main_contain" ref="main_contain" id="main_contain"
  3.     for="{{ ballDataList }}" 
  4.           style="top : {{ $item.y }} px; 
  5.                   left : {{ $item.x }} px;" 
  6.             >{{ $item.content }} 
  7.  
 
  1. .main_contain { 
  2.     width: 100%; 
  3.     position: relative
  4.  
  5. .ball { 
  6.     width: 120px; 
  7.     height: 120px; 
  8.     background-color: #c3f593; 
  9.     background-size: 100%; 
  10.     border-radius: 60px; 
  11.     border: #69c78e; 
  12.     border-bottom-style: solid; 
  13.     border-width: 1px; 
  14.     position: absolute
  15.     text-align: center; 

给小球添加动画:

由于鸿蒙JSUI框架@keyframes 动画只能指定动画初始样式(from属性)和终止样式(to属性),故只能采用JS给小球指定动画。

小球移动轨迹为上下浮动的简单动画,可有两种思路实现:

方式一:为每个小球设置连续无限次数动画

  1. createShakeAnimate(el) { 
  2.         if (el == null || el == undefined) { 
  3.             return 
  4.         } 
  5.         var options = { 
  6.             duration: 2000, 
  7.             easing: 'friction'
  8.             fill: 'forwards'
  9.             iterations: "Infinity"
  10.         }; 
  11.         var frames = [ 
  12.             { 
  13.                 transform: { 
  14.                     translate: '0px 0px' 
  15.                 }, 
  16.                 offset: 0.0 // 动画起始时 
  17.             }, 
  18.             { 
  19.                 transform: { 
  20.                     translate: '0px 20px' 
  21.                 }, 
  22.                 offset: 0.5 // 动画执行至一半时 
  23.             }, 
  24.             { 
  25.                 transform: { 
  26.                     translate: '0px 0px' 
  27.                 }, 
  28.                 offset: 1.0 // 动画结束时 
  29.             }, 
  30.  
  31.         ]; 
  32.         let animation = el.animate(frames, options); 
  33.         return animation 
  34.     }, 

方式二:每个小球设置为单向动画,只执行一次,监听动画结束时,调用reverse()方法执行反转动画

  1. createShakeAnimate(el) { 
  2.       if (el == null || el == undefined) { 
  3.           return 
  4.       } 
  5.       var options = { 
  6.           duration: 2000, 
  7.           easing: 'friction'
  8.           fill: 'forwards'
  9.           iterations: 1, 
  10.       }; 
  11.       var frames = [ 
  12.           { 
  13.               transform: { 
  14.                   translate: '0px 0px' 
  15.               }, 
  16.               offset: 0.0 
  17.           }, 
  18.           { 
  19.               transform: { 
  20.                   translate: '0px 20px' 
  21.               }, 
  22.               offset: 1.0 
  23.           }, 
  24.       ]; 
  25.       let animation = el.animate(frames, options); 
  26.        animation.onfinish = function () { 
  27.           animation.reverse() 
  28.       }; 
  29.       return animation 

执行浮动动画

  1. -- waterFlake.hml 为每个小球指定id --> 
  2.   for="{{ ballDataList }}" 
  3.           class="ball" 
  4.           id="ball{{ $idx }}" 
  5.           onclick="onBallClick($idx,$item)" 
  6.           style="top : {{ $item.y }} px; 
  7.                   left : {{ $item.x }} px;" 
  8.             >{{ $item.content }} 
  1. -- waterFlake.js  执行动画 --> 
  2.  playShakeAnimate() { 
  3.       setTimeout(() => { 
  4.           console.info('xwg playShakeAnimate '); 
  5.           for (var index = 0; index < this.ballDataList.length; index++) { 
  6.               let el = this.$element(`ball${index}`) 
  7.               let animate = this.createShakeAnimate(el) 
  8.               animate.play() 
  9.           } 
  10.       }, 50) 
  11.     }, 

为小球设置点击事件及收集能量动画

  1. onBallClick(index, item) { 
  2.    // 发送事件给父组件 并将小球信息作为参数传递出去 
  3.    this.$emit('ballClick', item); 
  4.  
  5.    let el = this.$element(`ball${index}`) 
  6.    this.playCollectionAnimate(el, index
  7. }, 
  1.  
  2. playCollectionAnimate(el, index) { 
  3.     if (this.isCollect) { // 正在执行收集动画则直接return 
  4.         return 
  5.     } 
  6.     var options = { 
  7.         duration: 1500, 
  8.         easing: 'ease-in-out'
  9.         fill: 'forwards'
  10.     }; 
  11.     let offsetX = this.collDestinationX - this.ballDataList[index].x 
  12.     let offsetY = this.collDestinationY - this.ballDataList[index].y 
  13.     var frames = [ 
  14.         { 
  15.             transform: { 
  16.                 translate: '0px 0px' 
  17.             }, 
  18.             opacity: 1 
  19.         }, 
  20.         { 
  21.             transform: { 
  22.                 translate: `${offsetX}px ${offsetY}px` 
  23.             }, 
  24.             opacity: 0 
  25.         } 
  26.     ]; 
  27.     let animation = el.animate(frames, options); 
  28.     let _t = this 
  29.     animation.onfinish = function () { 
  30.         console.info('onBallClick collection animation onFinish'); 
  31.         _t.isCollect = false
  32.         _t.ballDataList.splice(index, 1); 
  33.         console.info(JSON.stringify(_t.ballDataList)); 
  34.  
  35.         // 调用splice方法后,原index位置的小球不再执行动画,故手动再创建动画 
  36.         if (index <= _t.ballDataList.length) { 
  37.             setTimeout(() => { 
  38.                 let animate = _t.createShakeAnimate(el) 
  39.                 animate.play() 
  40.             }, 5) 
  41.         } 
  42.     }; 
  43.     this.isCollect = true 
  44.     animation.play() 
  45. }, 

父组件点击重置时,更新界面

  1.  onInit() { 
  2.         this.$watch('ballList''onBallListChange'); //注册数据变化监听 
  3. }, 
  4. onBallListChange(newV) { // 外部数据发生变化 重新渲染组件 
  5.         console.log('onBallListChange newV = ' + JSON.stringify(newV)) 
  6.         this.onReady() 
  7.     } 

完整代码如下:

子组件:

  1. -- waterFlake.css --> 
  2. .main_contain { 
  3.     width: 100%; 
  4.     position: relative
  5.  
  6. .ball { 
  7.     width: 100px; 
  8.     height: 100px; 
  9.     background-color: #c3f593; 
  10.     background-size: 100%; 
  11.     border-radius: 60px; 
  12.     border: #69c78e; 
  13.     border-bottom-style: solid; 
  14.     border-width: 1px; 
  15.     position: absolute
  16.     text-align: center; 
  17.  
  18. @keyframes Wave { 
  19.     from { 
  20.         transform: translateY(0px); 
  21.     } 
  22.  
  23.     to { 
  24.         transform: translateY(10px); 
  25.     } 
  1. -- waterFlake.hml --> 
  2. "main_contain" ref="main_contain" id="main_contain"
  3.     for="{{ ballDataList }}" 
  4.           ref="ball{{ $idx }}" class="ball" 
  5.           id="ball{{ $idx }}" 
  6.           tid="ball{{ $idx }}" 
  7.           onclick="onBallClick($idx,$item)" 
  8.           style="top : {{ $item.y }} px; 
  9.                   left : {{ $item.x }} px;" 
  10.             >{{ $item.content }} 
  11.  
 
  1. -- waterFlake.js --> 
  2. export default { 
  3.     props: { 
  4.     //后台返回的小球信息 
  5.         ballList: { 
  6.             default: [10, 11, 12, 13, 14], 
  7.         }, 
  8.     // 收集能量动画结束的X坐标 
  9.         collDestinationX: { 
  10.             default: 0 
  11.         }, 
  12.     // 收集能量动画结束的Y坐标 
  13.         collDestinationY: { 
  14.             default: 600 
  15.         } 
  16.     }, 
  17.     data() { 
  18.         return { 
  19.          
  20.             ballDataList: [], 
  21.             isCollect: false // 是否正在执行收集能量动画 
  22.         }; 
  23.     }, 
  24.     onInit() { 
  25.         this.$watch('ballList''onBallListChange'); //注册数据变化监听 
  26.     }, 
  27.     onReady() { 
  28.         let width = 720 //组件的款第 
  29.         let height = 600 //组件的高度 
  30.         // 生成小球的x坐标数组 
  31.         let xRandom = this.randomCommon(1, 8, this.ballList.length) 
  32.         let all_x = xRandom.map(item => { 
  33.             return item * width * 0.10 
  34.         }); 
  35.         //生成小球的y坐标数组 
  36.         let yRandom = this.randomCommon(1, 8, this.ballList.length); 
  37.         let all_y = yRandom.map(item => { 
  38.             return item * height * 0.08 
  39.         }) 
  40.         if (xRandom == null || yRandom == null) { 
  41.             return 
  42.         } 
  43.         let dataList = [] 
  44.         for (let index = 0; index < this.ballList.length; index++) { 
  45.             dataList.push({ 
  46.                 content: this.ballList[index] + 'g'
  47.                 x: all_x[index], 
  48.                 y: all_y[index
  49.             }) 
  50.         } 
  51.         this.ballDataList = dataList; // 触发视图更新 
  52.         console.info('onReady ballDataList = ' + JSON.stringify(this.ballDataList)); 
  53.  
  54.         this.playShakeAnimate() // 开始执行抖动动画 
  55.     }, 
  56.     onBallClick(index, item) { 
  57.         console.info('onBallClick index = ' + index); 
  58.         console.info('onBallClick item = ' + JSON.stringify(item)); 
  59.         this.$emit('ballClick', item); 
  60.         let el = this.$element(`ball${index}`) 
  61.         this.playCollectionAnimate(el, index
  62.     }, 
  63.  
  64.     playCollectionAnimate(el, index) { 
  65.         if (this.isCollect) { // 正在执行收集动画则直接return 
  66.             return 
  67.         } 
  68.         var options = { 
  69.             duration: 1500, 
  70.             easing: 'ease-in-out'
  71.             fill: 'forwards'
  72.         }; 
  73.         let offsetX = this.collDestinationX - this.ballDataList[index].x 
  74.         let offsetY = this.collDestinationY - this.ballDataList[index].y 
  75.         var frames = [ 
  76.             { 
  77.                 transform: { 
  78.                     translate: '0px 0px' 
  79.                 }, 
  80.                 opacity: 1 
  81.             }, 
  82.             { 
  83.                 transform: { 
  84.                     translate: `${offsetX}px ${offsetY}px` 
  85.                 }, 
  86.                 opacity: 0 
  87.             } 
  88.         ]; 
  89.         let animation = el.animate(frames, options); 
  90.         let _t = this 
  91.         animation.onfinish = function () { 
  92.             console.info('onBallClick collection animation onFinish'); 
  93.             _t.isCollect = false
  94.             _t.ballDataList.splice(index, 1); 
  95.             console.info(JSON.stringify(_t.ballDataList)); 
  96.  
  97.             // 调用splice方法后,原index位置的小球不再执行动画,故手动再创建动画 
  98.             if (index <= _t.ballDataList.length) { 
  99.                 setTimeout(() => { 
  100.                     let animate = _t.createShakeAnimate(el) 
  101.                     animate.play() 
  102.                 }, 5) 
  103.             } 
  104.         }; 
  105.         this.isCollect = true 
  106.         animation.play() 
  107.     }, 
  108.     createShakeAnimate(el) { 
  109.         if (el == null || el == undefined) { 
  110.             return 
  111.         } 
  112.         var options = { 
  113.             duration: 2000, 
  114.             easing: 'friction'
  115.             fill: 'forwards'
  116.             iterations: "Infinity"
  117.         }; 
  118.         var frames = [ 
  119.             { 
  120.                 transform: { 
  121.                     translate: '0px 0px' 
  122.                 }, 
  123.                 offset: 0.0 
  124.             }, 
  125.             { 
  126.                 transform: { 
  127.                     translate: '0px 20px' 
  128.                 }, 
  129.                 offset: 0.5 
  130.             }, 
  131.             { 
  132.                 transform: { 
  133.                     translate: '0px 0px' 
  134.                 }, 
  135.                 offset: 1.0 
  136.             }, 
  137.  
  138.         ]; 
  139.         let animation = el.animate(frames, options); 
  140.         return animation 
  141.     }, 
  142.     playShakeAnimate() { 
  143.         setTimeout(() => { 
  144.             console.info('xwg playShakeAnimate '); 
  145.             for (var index = 0; index < this.ballDataList.length; index++) { 
  146.                 let el = this.$element(`ball${index}`) 
  147.                 let animate = this.createShakeAnimate(el) 
  148.                 animate.play() 
  149.             } 
  150.         }, 50) 
  151.     }, 
  152.  
  153.     randomCommon(minmax, n) { 
  154.         if (n > (max - min + 1) || max < min) { 
  155.             return null
  156.         } 
  157.         let result = []; 
  158.         let count = 0; 
  159.         while (count < n) { 
  160.             let num = parseInt((Math.random() * (max - min)) + min); 
  161.             let flag = true
  162.             for (let j = 0; j < n; j++) { 
  163.                 if (num == result[j]) { 
  164.                     flag = false
  165.                     break; 
  166.                 } 
  167.             } 
  168.             if (flag) { 
  169.                 result[count] = num; 
  170.                 count++; 
  171.             } 
  172.         } 
  173.         return result; 
  174.     }, 
  175.     onBallListChange(newV) { // 外部数据发生变化 重新渲染组件 
  176.         console.log('onBallListChange newV = ' + JSON.stringify(newV)) 
  177.         this.onReady() 
  178.     } 

父组件:

  1. -- index.css --> 
  2. .container { 
  3.     flex-direction: column
  4.     align-items: flex-start; 
  5.  
  6. .title { 
  7.     font-size: 100px; 
  8.  
  9. .forestContainer { 
  10.     width: 100%; 
  11.     height: 750px; 
  12.     background-image: url("/common/bg.jpg"); 
  13.     background-size: 100%; 
  14.     background-repeat: no-repeat; 
  1. -- index.hml --> 
  2. name='waterFlake' src='../../../default/common/component/waterflake/waterFlake.hml'
  3. "container"
  4.     "forestContainer"
  5.         "{{ ballList }}" @ball-click="onBallClick"
  6.      
  7.     "padding : 20px; align-content : center; background-color : #222222;" 
  8.             onclick="reset">重置 
  9.      
  10.  
  11.  
  1. -- index.js --> 
  2. import prompt from '@system.prompt'
  3. export default { 
  4.     data() { 
  5.         return { 
  6.             ballList: [] 
  7.         } 
  8.     }, 
  9.     onInit() { 
  10.         this.ballList = this.genRandomArray(5); 
  11.     }, 
  12.     onBallClick(info) { 
  13.         console.info('xwg parent  onBallClick item = ' + JSON.stringify(info.detail)); 
  14.         let content = info.detail.content 
  15.         prompt.showToast({message:`点击了${content}`,duration:1500}) 
  16.     }, 
  17.     reset() { 
  18.         console.info("xwg reset clicked "
  19.         this.ballList = this.genRandomArray(6); 
  20.         console.info("xwg reset  ballList = " + JSON.stringify(this.ballList)) 
  21.     }, 
  22.     genRandomArray(count) { 
  23.         let ballArray = [] 
  24.         for (var index = 0; index < countindex++) { 
  25.             let v = this.random(1, 60) 
  26.             ballArray.push(parseInt(v)) 
  27.         } 
  28.         return ballArray 
  29.     }, 
  30.     random(minmax) { 
  31.         return Math.floor(Math.random() * (max - min)) + min
  32.     } 

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

来源:鸿蒙社区内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯