在這個超長假期中,無聊。。。,所以動手做一個早就計劃要做的小玩意, 水果*** ,嗯,這是一個小程序而不是小游戲...
使用模板結(jié)構(gòu)(view)生成水果盤的好處一是用戶可自定義產(chǎn)出 n x n 的定制化***,二是容易通過算法樣式生成布局,三是通過 wx.selectQueryAll 的方法能夠很方便的抓到定位數(shù)據(jù)。但,問題是動畫性能過于孱弱,如圖構(gòu)建一個 7x7 的水果盤,動畫性能估計會慘不忍睹,而且純粹模板結(jié)構(gòu)無論使用 animation 動畫方法還是 css 的keyframe的動畫方法得到的動畫效果都非常差(測試過的結(jié)論),還有是已知的動畫方法可控性很差
使用canvas來生成水果盤好處是動畫性能很好(canvas2d),但是定制性和擴(kuò)展性比較差
so綜上考慮,使用模板(view)布局,使用canvas來實現(xiàn)動畫。既保證了組件的性能,同時定制型,擴(kuò)展性也很好
動畫的生成離不開計時器方法,settimeout/setinterval這兩兄弟真的不夠看啊,問題還多,做過web開發(fā)的一定都知道 window.requestAnimationFrame ,這貨在小程序的計時器方法中不存在,好在 canvas2d 中可以使用 Canvas.requestAnimationFrame(function callback) 方法來實現(xiàn)
在水果***中,激活狀態(tài)會沿著四方的水果盤做非線性運動(easeInOut比較好用),需要基礎(chǔ)的運動算法來計算實際的運動距離。在 animation 動畫方法中,我們可以使用 ease-in/ease-out 等緩動算法來實現(xiàn)動畫效果,但在這里必須要借助 tween.js 中的緩動算法來實現(xiàn)運動效果(因為需要控制運動節(jié)點)。
你會不會想到用css的keyframe動畫來做這個運動效果,經(jīng)過我的測試,css的動畫和animation的動畫會在每一條邊上實現(xiàn)一次(ease)緩動運動(很奇怪的效果
使用其中一個,節(jié)省代碼量
/*
* Tween.js
* t: current time(當(dāng)前時間);
* b: beginning value(初始值);
* c: change in value(變化量);
* d: duration(持續(xù)時間)。
*/
// 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;
}
復(fù)制代碼
tween算法是以時間為基準(zhǔn)(時間比率 = 距離比率)來計算單位時間的實際運動距離
以上面的圖為例,我們需要做一個 7 x 7 的水果盤,實際有效的獎品格子數(shù)為 7*4-4 共24個有效格子
有效格子算法
js
// 0-6 第一行所有格子全部有效 // 21-27 最后一行所有格子全部有效 // 中間部分 i%7===0 和 i%7 === (7-1) 有效 // 算法源碼有點無聊,依據(jù)上述思路,即可遍歷28個格子并標(biāo)識獎品格子valide=true // 可以擴(kuò)展想一想 6x6 5x5,思路是一樣的 復(fù)制代碼
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>
復(fù)制代碼
只節(jié)選關(guān)鍵樣式,目的是讓canvas覆蓋在水果盤上,長寬一致
.fruits-container {
position: relative;
width: 400px;
height: 400px;
...
}
.fruits-table {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
...
}
復(fù)制代碼
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
})
復(fù)制代碼
得到每一個獎品格子的位置信息后,就可以使用canvas的 fillRect 方法來繪制激活狀態(tài)了。
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)
})
})
復(fù)制代碼
已經(jīng)繪制了一個激活狀態(tài),接下來使它能夠簡單動起來
// 抽象激活方法
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) // 繪制激活區(qū)域
}
function run(){
setTimeout(()=>{
if (ret.length) {
let point = ret.shift()
rect(point, canvas)
run()
}
}, 100)
}
復(fù)制代碼
執(zhí)行run方法后可以看到水果盤的激活狀態(tài)一步一步的往前走(100毫秒),拖拉機(jī)終于可以啟動了
經(jīng)過上面的試驗我們終于可以看到基本的運動效果了,接下來配上運動算法和計時器方法
// 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計數(shù)頻率(一般情況)
const steper = () => {
// left為位移距離
// ***的運動位移是節(jié)點位移,不是精確位移
// 所以這里用parseInt處理,只取整數(shù)部分
// 數(shù)據(jù)變化為 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] // 取節(jié)點位置信息
this.rect(point) // 繪制
}
// 時間遞增
if (start <= during) {
this.ctx.requestAnimationFrame(steper); // 計時器
} else {
// 動畫結(jié)束,這里可以插入回調(diào)...
// callback()...
}
};
steper(); // 啟動
復(fù)制代碼
以上為我的小程序水果***的基本開發(fā)思路