微信小程序canvas开发水果老虎机的思路详解


Posted in Javascript onFebruary 07, 2020

在这个超长假期中,无聊。。。,所以动手做一个早就计划要做的小玩意, 水果老虎机 ,嗯,这是一个小程序而不是小游戏...

微信小程序canvas开发水果老虎机的思路详解 

使用结构还是canvas?

使用模板结构(view)生成水果盘的好处一是用户可自定义产出 n x n 的定制化老虎机,二是容易通过算法样式生成布局,三是通过 wx.selectQueryAll 的方法能够很方便的抓到定位数据。但,问题是动画性能过于孱弱,如图构建一个 7x7 的水果盘,动画性能估计会惨不忍睹,而且纯粹模板结构无论使用 animation 动画方法还是 css 的keyframe的动画方法得到的动画效果都非常差(测试过的结论),还有是已知的动画方法可控性很差

使用canvas来生成水果盘好处是动画性能很好(canvas2d),但是定制性和扩展性比较差

so综上考虑,使用模板(view)布局,使用canvas来实现动画。既保证了组件的性能,同时定制型,扩展性也很好

准备计时器方法

动画的生成离不开计时器方法,settimeout/setinterval这两兄弟真的不够看啊,问题还多,做过web开发的一定都知道 window.requestAnimationFrame ,这货在小程序的计时器方法中不存在,好在 canvas2d 中可以使用 Canvas.requestAnimationFrame(function callback) 方法来实现

准备运动算法

在水果老虎机中,激活状态会沿着四方的水果盘做非线性运动(easeInOut比较好用),需要基础的运动算法来计算实际的运动距离。在 animation 动画方法中,我们可以使用 ease-in/ease-out 等缓动算法来实现动画效果,但在这里必须要借助 tween.js 中的缓动算法来实现运动效果(因为需要控制运动节点)。

你会不会想到用css的keyframe动画来做这个运动效果,经过我的测试,css的动画和animation的动画会在每一条边上实现一次(ease)缓动运动(很奇怪的效果)

推荐这篇文章

使用其中一个,节省代码量

/*
 * Tween.js
 * t: current time(当前时间);
 * b: beginning value(初始值);
 * c: change in value(变化量);
 * d: duration(持续时间)。
 */
// Quart 四次方的缓动
const easeInOutQuart = function (t, b, c, d) {
 if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
 return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}

tween算法是以时间为基准(时间比率 = 距离比率)来计算单位时间的实际运动距离

布局

以上面的图为例,我们需要做一个 7 x 7 的水果盘,实际有效的奖品格子数为 7*4-4 共24个有效格子

有效格子算法

js

// 0-6 第一行所有格子全部有效 
// 21-27 最后一行所有格子全部有效 
// 中间部分 i%7===0 和 i%7 === (7-1) 有效
// 算法源码有点无聊,依据上述思路,即可遍历28个格子并标识奖品格子valide=true
// 可以扩展想一想 6x6 5x5,思路是一样的

wxml

<view class="fruits-container" >
  <view class="fruits-table" >
    <block wx:for="{{ary}}" wx:key="index" >
      <view wx:if="{{item.valide}}" class="valide">{{item.title}}</view>
      <view wx:else class="in-valide"></view>
    </block>
  </view>
  <canvas type="2d" .... />
</view>

样式

只节选关键样式,目的是让canvas覆盖在水果盘上,长宽一致

.fruits-container {
  position: relative;
  width: 400px;
  height: 400px;
  ...
}

.fruits-table {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  ...
}

抓取位置信息

canvas的绘制需要X轴, Y轴的精确信息,可以使用 wx.createSelectorQuery 方式抓取类名为‘valide'的 view (奖品格子)的位置信息

let query = wx.createSelectorQuery().in(this)
query.selectAll(`.fruits-table .valide`).boundingClientRect(ret => {
  ....
  console.log(ret[0]) // top, left, right, bottom, width, height
  console.log(ret[1]) // top, left, right, bottom, width, height
  ...
  ...
  console.log(ret[23]) // top, left, right, bottom, width, height
})

得到每一个奖品格子的位置信息后,就可以使用canvas的 fillRect 方法来绘制激活状态了。

绘制一个激活状态

let query = wx.createSelectorQuery().in(this)
query.selectAll(`.fruits-table .valide`).boundingClientRect(ret => {
  ....
  let {top, left, right, bottom, width, height} = ret[0]
  const canvasQuery = wx.createSelectorQuery()
  canvasQuery.select('#fruit-canvas')
  .fields({ node: true, size: true })
  .exec((res) => {
    const canvas = res[0].node
    const ctx = canvas.getContext('2d') 
    let x = top
    let y = left
    let dx = width
    let dy = height
    ctx.shadowOffsetX = 2
    ctx.shadowOffsetY = -2
    ctx.shadowColor = 'red'
    ctx.shadowBlur = 50
    ctx.lineWidth = 5
    ctx.strokeStyle = 'red'
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.strokeRect(x, y, dx, dy)
  })
})

跑起来

已经绘制了一个激活状态,接下来使它能够简单动起来

// 抽象激活方法 
functon rect(point, canvas){
  let {x, y, dx, dy} = getPosition(point)
  ctx.shadowOffsetX = 2
  ctx.shadowOffsetY = -2
  ...
  ...
  ctx.clearRect(0, 0, canvas.width, canvas.height) // 擦除整个水果盘
  ctx.strokeRect(x, y, dx, dy) // 绘制激活区域
}

function run(){
  setTimeout(()=>{
    if (ret.length) {
      let point = ret.shift()
      rect(point, canvas)
      run()
    }
  }, 100)
}

执行run方法后可以看到水果盘的激活状态一步一步的往前走(100毫秒),拖拉机终于可以启动了

配上运动算法

经过上面的试验我们终于可以看到基本的运动效果了,接下来配上运动算法和计时器方法

// Quart 四次方的缓动
const easeInOutQuart = function (t, b, c, d) {
 if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
 return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}

let start = 0 // 开始时间
let begin = 0 // 开始奖品位置
let end = 23 // 终点位置,这里跑一圈
let during = 5000 // 运动总时间

// 1000/60 ≈ 17,
// 17毫秒即表示屏幕60帧刷新率每秒 ≈ requestAnimationFrame计数频率(一般情况) 
const steper = () => {
 // left为位移距离
 // 老虎机的运动位移是节点位移,不是精确位移
 // 所以这里用parseInt处理,只取整数部分
 // 数据变化为 0,1,2,3,4,5...23
 // 间隔时间/距离由easeInOutQuart算法计算
 var left = easeInOutQuart(start, begin, end, during);
 let idx = parseInt(left)
 start = start + 17; 
 if (idx <= end) {
  let point = this.ret[idx] // 取节点位置信息
  this.rect(point) // 绘制
 }
 // 时间递增
 if (start <= during) {
  this.ctx.requestAnimationFrame(steper); // 计时器
 } else {
  // 动画结束,这里可以插入回调...
  // callback()...
 }
};
steper(); // 启动

总结

以上所述是小编给大家介绍的微信小程序canvas开发水果老虎机的思路详解,希望对大家有所帮助!

Javascript 相关文章推荐
javascript实现的距离现在多长时间后的一个格式化的日期
Oct 29 Javascript
JQuery实现鼠标移动到图片上显示边框效果
Jan 09 Javascript
bootstrap改变按钮加载状态
Dec 01 Javascript
浅谈jQuery的offset()方法及示例分享
Jul 17 Javascript
Angular指令封装jQuery日期时间插件datetimepicker实现双向绑定示例
Jan 22 Javascript
VUE页面中加载外部HTML的示例代码
Sep 20 Javascript
除Console.log()外更多的Javascript调试命令
Jan 24 Javascript
浅谈Webpack打包优化技巧
Jun 12 Javascript
vue中如何实现pdf文件预览的方法
Jul 12 Javascript
对vuejs的v-for遍历、v-bind动态改变值、v-if进行判断的实例讲解
Aug 27 Javascript
js使用cookie实现记住用户名功能示例
Jun 13 Javascript
vue滚动插件better-scroll使用详解
Oct 18 Javascript
Node.js 在本地生成日志文件的方法
Feb 07 #Javascript
node.js 微信开发之定时获取access_token
Feb 07 #Javascript
jQuery操作选中select下拉框的值代码实例
Feb 07 #jQuery
webpack的 rquire.context用法实现工程自动化的方法
Feb 07 #Javascript
详解为element-ui的Select和Cascader添加弹层底部操作按钮
Feb 07 #Javascript
vue-cli设置css不生效的解决方法
Feb 07 #Javascript
js生成1到100的随机数最简单的实现方法
Feb 07 #Javascript
You might like
简单的方法让你的后台登录更加安全(php中加session验证)
2012/08/22 PHP
php Hex RGB颜色值互换的使用
2013/05/10 PHP
php结合curl实现多线程抓取
2015/07/09 PHP
Yii使用EasyWechat实现小程序获取用户的openID的方法
2020/04/29 PHP
在网页里看flash的trace数据的js类
2009/01/10 Javascript
js使下拉列表框可编辑不止是选择
2013/12/12 Javascript
Jquery实现遮罩层的简单实例(就是弹出DIV周围都灰色不能操作)
2016/07/14 Javascript
javascript动画之磁性吸附效果篇
2016/12/09 Javascript
微信小程序 数组中的push与concat的区别
2017/01/05 Javascript
vue观察模式浅析
2018/09/25 Javascript
vue实现与安卓、IOS交互的方法
2018/11/02 Javascript
bootstrap table实现合并单元格效果
2018/12/24 Javascript
NodeJS读取分析Nginx错误日志的方法
2019/05/14 NodeJs
layui自定义验证,用ajax查询后台是否有重复数据,form.verify的例子
2019/09/06 Javascript
微信小程序实现3D轮播图效果(非swiper组件)
2019/09/21 Javascript
基于vue-cli3和element实现登陆页面
2019/11/13 Javascript
three.js利用射线Raycaster进行碰撞检测
2020/03/12 Javascript
[01:10]DOTA2次级职业联赛 - U5战队宣传片
2014/12/01 DOTA
Python命令行解析模块详解
2018/02/01 Python
Python读写/追加excel文件Demo分享
2018/05/03 Python
Python3爬虫全国地址信息
2019/01/05 Python
python3 常见解密加密算法实例分析【base64、MD5等】
2019/12/19 Python
tensorflow 变长序列存储实例
2020/01/20 Python
python多进程使用函数封装实例
2020/05/02 Python
解决Keras 自定义层时遇到版本的问题
2020/06/16 Python
几道数据库的概念性面试题
2014/05/30 面试题
Java Servlet的主要功能和作用是什么
2014/02/14 面试题
大学毕业生最详细的自我评价分享
2013/11/18 职场文书
公务员综合考察材料
2014/02/01 职场文书
高三学习决心书
2014/03/11 职场文书
中班上学期幼儿评语
2014/04/30 职场文书
大学生精神文明先进个人事迹材料
2014/05/02 职场文书
如何使用php生成zip压缩包
2021/04/21 PHP
TensorFlow中tf.batch_matmul()的用法
2021/06/02 Python
SQL注入的实现以及防范示例详解
2021/06/02 MySQL
vue+echarts实现多条折线图
2022/03/21 Vue.js