On this extra long vacation, boring… “, so start building a planned gadget, a fruit slot machine, and, well, it’s a mini-program not a mini-game…

  • Supports custom number of fruit bowls
  • Support to customize each prize
  • Support for a callback method after a win

Structure or Canvas?

The advantages of using template structure (VIEW) to generate fruit bowl are that users can customize the output of N x 4 custom slot machines, that it is easy to generate layout through algorithmic style, and that it is convenient to capture and locate data through wx.selectQueryAll. However, the problem is that the animation performance is too weak, as shown in the figure of building a 7×4 fruit bowl, the animation performance is estimated to be terrible, and the pure template structure regardless of using animation animation method or CSS keyframe animation method to get the animation effect is very poor (tested conclusion), and the known animation method is very poor controllability

The advantage of using Canvas to generate fruit bowl is good animation performance (Canvas2D), but poor customization and scalability

So to sum up, using template (View) layout, using canvas to achieve animation. It not only ensures the performance of the components, but also customizes and expands well

Prepare timer method

Animation generated without the timer method, settimeout/setinterval the two brothers is not really enough look, more, doing web development must know window. RequestAnimationFrame, this cargo does not exist in the small program timer method, Can use in the canvas2d Canvas. RequestAnimationFrame (function callback) method to implement

Ready motion algorithm

In a fruit slot machine, the active state moves nonlinearly along the square fruit bowl (easeInOut works better), requiring a basic motion algorithm to calculate the actual movement distance. In the animation method, we can use ease-in/ease-out algorithms to achieve the animation effect, but here we must use the ease-in algorithm in Tween.js to achieve the motion effect (because we need to control the motion node).

Would you think of using the CSS KeyFrame animation for this motion? In my tests, THE CSS and animation animations ease each edge once.

Recommend this article

Use one of these to save code

/* * tween.js * t: current time; * B: beginning value; * c: change in value; * d: duration * /
// Quart to the fourth power
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;
}
Copy the code

Tween algorithm is based on time (time ratio = distance ratio) to calculate the actual movement distance per unit time

layout

For example, in the picture above, we need to make a fruit bowl of 7 x 4, and the actual number of effective prize grids is 7*4-4, a total of 24 effective grids

Efficient lattice algorithm JS

// All columns in the first row are valid
// All cells in the last row are valid
// The middle part I %7===0 and I %7=== (7-1) are valid
// The algorithm source code is a bit tiresome, according to the above thought, can traverse 28 squares and mark the prize box valide=true
6x6 5x5, same idea
Copy the code

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>
Copy the code

style

Only the key styles are selected so that the canvas can cover the fruit bowl with the same length and width

.fruits-container { position: relative; width: 400px; height: 400px; . } .fruits-table { position: absolute; width: 100%; height: 100%; top: 0; left: 0; . }Copy the code

Capture position information

Canvas drawing requires exact information of X and Y axes. You can use wx.createSelectorQuery to capture the position information of view(prize grid) named ‘Valide’

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
})
Copy the code

Once you have the location information for each prize grid, you can use canvas’s fillRect method to draw the active state.

Draw an active state

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)
    })
})
Copy the code

run

Having drawn an active state, let’s make it simple to animate

// Abstract activation method
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) // Erase the entire fruit bowl
    ctx.strokeRect(x, y, dx, dy) // Draw the active region
}

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

After executing the run method, you can see the activation state of the fruit bowl step by step (100 milliseconds), and the tractor can finally start

With the motion algorithm

After the above experiment we can finally see the basic motion effect, then with motion algorithm and timer method

// Quart to the fourth power
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  // Start time
let begin = 0  // Start the prize position
let end = 23  // Finish position, run one lap here
let during = 5000 // Total exercise time

// 1000/60 ≈ 17,
// 60 frames per second ≈ requestAnimationFrame count frequency
const steper = (a)= > {
  // left indicates the displacement distance
  // Slot machine movement is node displacement, not exact displacement
  // So parseInt is used here, and only integer parts are taken
  // data changes to 0,1,2,3,4,5... 23
  // Interval time/distance is calculated by easeInOutQuart algorithm
  var left = easeInOutQuart(start, begin, end, during);
  let idx = parseInt(left)
  start = start + 17; 
  if (idx <= end) {
    let point = this.ret[idx] // Get node location information
    this.rect(point) / / to draw
  }
  
  // time increments
  if (start <= during) {
    this.ctx.requestAnimationFrame(steper); / / timer
  } else {
    // End of animation, here can insert callback...
    // callback()...}}; steper();/ / start
Copy the code

Above for my small program fruit slot machine basic development ideas

Installation method

GITHUB has the details.

configuration

wxml

<view wx:if="{{fruitConfig}}" class="fruit-container {{fruitConfig.containerClass||''}}" style="{{fruitConfig.containerStyle||''}}">
  <ui-list list="{{fruitConfig}}" />
  <canvas type='2d' disable-scroll="true" id="fruit-canvas" class='fruit-canvas'></canvas>
</view>
Copy the code

js

const Pager = require('.. /.. /components/aotoo/core/index')
const mkFruits = require('.. /.. /components/modules/fruit')
Pager({
  data: {
    fruitConfig: mkFruits({
      id: 'fruitTable'. . },// callback, the response after the animation ends
    function (param) {
      console.log(param); // Lottery data
      console.log(param.value); / / the winning value})}})Copy the code

Id Id of the configuration instance

ContainerClass Container style

ContainerStyle Container inline style

Max After setting the Max value, the number of fruit bowls is automatically calculated according to the square algorithm. Max defines how many fruits in a row. The default is nine squares and Max is 3

The difference between count and Max is that the number of fruit bowls is specified by the user, and the number of fruit bowls is no longer in slot machine mode. Max number is still defined as several fruits in a row

Once count is set to valid data, the slot machine draw will be replaced with a regular draw

Confuse disorganizes the data in the fruit bowl. Only if count is valid can confuse take effect

FruitsData Object, specify the corresponding location of the grid content, support text/pictures

Callback method mkFruits(config, callback) response method after the fruit bowl animation ends

How to set up

Set the default numeric sort of fruit bowl to 9 squares

mkFruits({
  id: 'fruit'.max: 3,})Copy the code

Set the fruit plate content (text) for example, 16 squares fruit plate, and specify the related fruit content

mkFruits({
  id: 'fruit'.max: 4.fruitsData: {
    1: {title: 'banana'}, 
    8: {title: 'apple'}}})Copy the code

Set the fruit plate content (picture) for example, 16 squares fruit plate, and specify the related fruit content

mkFruits({
  id: 'fruit'.max: 4.fruitsData: {
    1: {img: {src: '/images/apple.png'.itemStyle: 'CSS Style string'.itemClass: 'Specify style class'}}},})Copy the code

Default value for all fruits to their subscript value in the array, or you can specify it manually

mkFruits({
  id: 'fruit'.max: 4.fruitsData: {
    1: {title: 'First Prize'.value: '001'}, 
    5: {img: {src: '/images/xxx.jpg'}, value: '003'}},})Copy the code

How to get an instance of a fruit bowl Running a fruit bowl requires calling the instance method, first getting the instance of a fruit bowl through getElementsById

const Pager = require('.. /.. /components/aotoo/core/index')
const mkFruits = require('.. /.. /components/modules/fruit')
Pager({
  data: {
    fruitConfig: mkNavball({
      id: 'fruit'
    }),
  },

  onReady(){
    // Get the fruit bowl instance
    const instance = this.fruit
    / / or
    // const instance = this.getElementsById('fruit')  }})Copy the code

Run Runs the fruit bowl

onReady() {
  // Get the fruit bowl instance
  const instance = this.getElementsById('fruit')
  instance.run()
}
Copy the code

Source code stamp here

The following small program DEMO containsDrop-down menu, general filter list, index list, Markdown (including table), scoring component, fruit slot machine, folding panel, two-column category navigation (left and right), scratch card, calendarComponents such as