微信小程序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 放大镜 放大倍率和视窗尺寸
May 09 Javascript
silverlight线程与基于事件驱动javascript引擎(实现轨迹回放功能)
Aug 09 Javascript
dotopAlert 提示用户需安装播放器的代码
Sep 17 Javascript
A标签中通过href和onclick传递的this对象实现思路
Apr 19 Javascript
jquery实现漂浮在网页右侧的qq在线客服插件示例
May 13 Javascript
JavaScript异步编程:异步数据收集的具体方法
Aug 19 Javascript
JS弹性运动实现方法分析
Dec 15 Javascript
vue使用$emit时,父组件无法监听到子组件的事件实例
Feb 26 Javascript
原生JS实现前端本地文件上传
Sep 08 Javascript
Easyui 去除jquery-easui tab页div自带滚动条的方法
May 10 jQuery
Vue数字输入框组件的使用方法
Oct 19 Javascript
详解Vue3.0 + TypeScript + Vite初体验
Feb 22 Vue.js
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怎么实现网站保存快捷方式方便用户随时浏览
2013/08/15 PHP
php分页函数示例代码分享
2014/02/24 PHP
php使用PDO操作MySQL数据库实例
2014/12/30 PHP
php获取文件名后缀常用方法小结
2015/02/24 PHP
yii 2.0中表单小部件的使用方法示例
2017/05/23 PHP
javascript页面动态显示时间变化示例代码
2013/12/18 Javascript
JavaScript-RegExp对象只能使用一次问题解决方法
2014/06/23 Javascript
jQuery如何防止这种冒泡事件发生
2015/02/27 Javascript
基于jquery实现的自动补全功能
2015/03/12 Javascript
nodejs爬虫抓取数据乱码问题总结
2015/07/03 NodeJs
JS区分浏览器页面是刷新还是关闭
2016/04/17 Javascript
jQuery获取访问者IP地址的方法(基于新浪API与QQ查询接口)
2016/05/25 Javascript
干货!教大家如何选择Vue和React
2017/03/13 Javascript
详解如何在Angular中快速定位DOM元素
2017/05/17 Javascript
详解webpack + react + react-router 如何实现懒加载
2017/11/20 Javascript
Vue.js 中取得后台原生HTML字符串 原样显示问题的解决方法
2018/06/10 Javascript
layui+jquery支持IE8的表格分页方法
2019/09/28 jQuery
[04:11]2014DOTA2国际邀请赛 CIS遗憾出局梦想不灭
2014/07/09 DOTA
Python类的动态修改的实例方法
2017/03/24 Python
python爬虫使用cookie登录详解
2017/12/27 Python
对python cv2批量灰度图片并保存的实例讲解
2018/11/09 Python
Python利用字典破解WIFI密码的方法
2019/02/27 Python
Python qqbot 实现qq机器人的示例代码
2019/07/11 Python
selenium+PhantomJS爬取豆瓣读书
2019/08/26 Python
python+rsync精确同步指定格式文件
2019/08/29 Python
HTML5+CSS3实现无插件拖拽上传图片(支持预览与批量)
2017/01/05 HTML / CSS
Vision Direct比利时:在线订购隐形眼镜
2019/08/27 全球购物
业务助理岗位职责
2013/11/18 职场文书
尽职尽责村干部自我鉴定
2014/01/23 职场文书
四查四看剖析材料
2014/02/14 职场文书
工作评语大全
2014/04/26 职场文书
会议欢迎标语
2014/06/30 职场文书
2015年七七事变78周年纪念活动方案
2015/05/06 职场文书
学生会副主席竞选稿
2015/11/19 职场文书
3招让你摆脱即兴讲话冷场尴尬
2019/08/08 职场文书
七年级之开学家长寄语35句
2019/09/05 职场文书