微信小程序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 序列化对象实现代码
Dec 18 Javascript
基于JavaScript 声明全局变量的三种方式详解
May 07 Javascript
Javascript限制网页只能在微信内置浏览器中访问
Nov 09 Javascript
jQuery中removeData()方法用法实例
Dec 27 Javascript
JavaScript中Function()函数的使用教程
Jun 04 Javascript
js实现精确到秒的日期选择器完整实例
Apr 30 Javascript
原生js实现对Ajax的封装(仿jquery)
Jan 22 Javascript
js事件冒泡与事件捕获详解
Feb 20 Javascript
基于vue框架手写一个notify插件实现通知功能的方法
Mar 31 Javascript
新手入门带你学习JavaScript引擎运行原理
Jun 24 Javascript
使用JS监听键盘按下事件(keydown event)
Nov 07 Javascript
Vue中使用wangeditor富文本编辑的问题
Feb 07 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 intval的测试代码发现问题
2008/07/27 PHP
PHP实现无限极分类图文教程
2014/11/25 PHP
Yii不依赖Model的表单生成器用法实例
2014/12/04 PHP
php使用递归计算文件夹大小
2014/12/24 PHP
php中有关合并某一字段键值相同的数组合并的改进
2015/03/10 PHP
发现的以前不知道的函数
2006/09/19 Javascript
一个简单的实现下拉框多选的插件可移植性比较好
2014/05/05 Javascript
js父页面与子页面不同时显示的方法
2014/10/16 Javascript
JavaScript判断表单提交时哪个radio按钮被选中的方法
2015/03/21 Javascript
AngularJS手动表单验证
2016/02/01 Javascript
vue.js中$watch的用法示例
2016/10/04 Javascript
vue实现打印功能的两种方法
2018/09/07 Javascript
微信小程序导航栏滑动定位功能示例(实现CSS3的positionsticky效果)
2019/01/24 Javascript
JS使用百度地图API自动获取地址和经纬度操作示例
2019/04/16 Javascript
vue改变对象或数组时的刷新机制的方法总结
2019/04/24 Javascript
微信小程序实现发送模板消息功能示例【通过openid推送消息给用户】
2019/05/05 Javascript
常见的浏览器存储方式(cookie、localStorage、sessionStorage)
2019/05/07 Javascript
小程序实现锚点滑动效果
2019/09/23 Javascript
python中zip()方法应用实例分析
2016/04/16 Python
TensorFlow 合并/连接数组的方法
2018/07/27 Python
python实现年会抽奖程序
2019/01/22 Python
HTML5中的音频和视频媒体播放元素小结
2016/01/29 HTML / CSS
一个基于canvas的移动端图片编辑器的实现
2020/10/28 HTML / CSS
HMV日本官网:全球知名的音乐、DVD和电脑游戏零售巨头
2016/08/13 全球购物
乌克兰时尚鞋子和衣服购物网站:Born2be
2018/05/24 全球购物
管道维修工岗位职责
2013/12/27 职场文书
什么样的创业计划书可行性高?
2014/02/01 职场文书
保护环境建议书400字
2014/05/13 职场文书
服务承诺口号
2014/05/22 职场文书
年终晚会活动方案
2014/08/21 职场文书
活动总结结尾怎么写
2014/08/30 职场文书
红领巾心向党演讲稿
2014/09/10 职场文书
小学家长意见怎么写
2015/06/03 职场文书
2016年感恩教师节校园广播稿
2015/12/18 职场文书
vue-cropper组件实现图片切割上传
2021/05/27 Vue.js
SQL实现LeetCode(178.分数排行)
2021/08/04 MySQL