上面派錢啦。然而呢,tap and go 的 loading 實在煩人。於是仿著畫一個。
成果圖
起手式
我猜官方應該用的是 css 做的動畫。這裏寫的是用 P5js ,語言是 js 只能說是模仿,不能說一樣。
//p5 js 的起手式
function setup() {}
function draw() {}
分解動作
三顆球
第一印象就是有三顆球在跳。
第二印象是在頂跟底都會變扁。
第三印象是排列位置似乎是打斜一條直線。
基於上述三個印象就有了初步的雛形。
首先先搞三個球。
[PS 背景之所以改成黑色的原因是,現在為標準時間凌晨四點四十。]
const width = 200;
const height = 200;
const padding = 50;
const r = 25;
function setup() {
createCanvas(width, height);
fill("orange");
stroke("orange");
strokeWeight(3);
}
function draw() {
background("black");
ellipse(padding, (height * 2) / 3 , r , r );
ellipse(width / 2, (height * 2) / 3 , r , r );
ellipse(width - padding, (height * 2) / 3 , r , r );
}
黑色背景每個 frame 都會把畫板刷黑從而做到動畫效果。至於為什麼這三個球的位置在「這裏」,純粹隨緣而已。
上竄下跳的三顆球
接下來要讓他們動了。基本常識都知道,要讓物件上下跳,就要改變他的 Y 座標值。
考慮到這次有三個球,若每一個球都賦予一個或幾個 variable 的話,相信 code 的長度會爆炸。所以這裏可以用array 來儲存每一個球該有的 variable. 這裡只有三個球,用 OOP 或許有點大材小用了。OOP 適合更多更頻繁生成的環境,例如煙花、花瓣、模擬生活中常見的系統等等。
const width = 200;
const height = 200;
const padding = 50;
const r = 25;
const l = width / 2;
var loopCounters = [r, parseInt((height * 2) / 3), parseInt(((height * 2) / 3 + r) / 2)];
const increment = 3;
var increments = [-increment, increment, increment];
function setup() {
createCanvas(width, height);
fill("orange");
stroke("orange");
strokeWeight(3);
}
function draw() {
background("white");
ellipse(padding, padding + loopCounters[0] - r, r, r);
ellipse(width / 2, padding + loopCounters[1] - r, r , r);
ellipse(width - padding, padding + loopCounters[2] - r, r , r);
for (var i = 0; i < 3; i++) {
if (loopCounters[i] <= 0) increments[i] = increment;
if (loopCounters[i] >= parseInt((height * 2) / 3)) increments[i] = -increment;
loopCounters[i] += increments[i];
}
}
考慮到之後要做壓扁,有一個 0 -> A -> 0 -> A … 的序列會有所幫助。這裏的做法是球球的x座標不變,而y座標與 loopCounters
成正比關係改變。而球球的上下,取決於 increments
的正負值。increments
的正負值在每一次更新 loopCounters
時做出判定。當球移動出固定範圍(在這裡設定為0 - (height * 2) / 3
之間)時,increments
的正負值做出逆轉動作。
壓扁
這部分有點難,花了點智商與時間。
這裡需要留意的是扁的時機跟扁的比例。
const width = 200;
const height = 200;
const padding = 50;
const r = 25;
var loopCounters = [r, parseInt((height * 2) / 3), parseInt(((height * 2) / 3 + r) / 2)];
const increment = 3;
var increments = [-increment, increment, increment];
var ballVar = [0, 0, 0];
const ballVarMax = 10;
function setup() {
createCanvas(width, height);
fill("orange");
stroke("orange");
strokeWeight(3);
}
function draw() {
background("black");
ellipse(padding, padding + loopCounters[0] - r, r + ballVar[0], r - ballVar[0] / 2);
ellipse(width / 2, padding + loopCounters[1] - r, r + ballVar[1], r - ballVar[1] / 2);
ellipse(width - padding, padding + loopCounters[2] - r, r + ballVar[2], r - ballVar[2] / 2);
for (var i = 0; i < 3; i++) {
if (loopCounters[i] < ballVarMax) ballVar[i] = ballVarMax - loopCounters[i];
else if (loopCounters[i] >= parseInt((height * 2) / 3) - ballVarMax) ballVar[i] = loopCounters[i] - parseInt((height * 2) / 3) + ballVarMax;
else ballVar[i] = 0;
if (loopCounters[i] <= 0) increments[i] = increment;
if (loopCounters[i] >= parseInt((height * 2) / 3)) increments[i] = -increment;
loopCounters[i] += increments[i];
}
}
前一步埋下的伏筆終於要派上用場了。
在前一步裡,loopCounters
是以 0 -> A -> 0 -> A … 的序列存在的。 於是乎便有了兩個臨界點,一個是 0 一個是 A 。而這裡的做法是,ballVarMax -> 0 -> ballVarMax
這段序列跟 (height * 2) / 3) - ballVarMax -> (height * 2) / 3) -> (height * 2) / 3) - ballVarMax
這段序列形成了逐漸加大/減小的樣式。
接下來就看怎麼利用這兩條序列了。
一般情況下,我們想要球球是圓的,所以設定ballVar
為0。而經過算式,最後得到的是0 -> ballVarMax -> 0
序列儲存在 ballVar
裡。
如此一來就可以塞進畫球的指令裡。
留意此處的第三第四個項目分別代表長寬,按照比例帶入就可以得到壓扁效果。
飄移進度條
這裡需要單獨把進度條跟中間的球球抽出來講。仔細觀察後發現,原版的進度條只在中間的球球下降的時候出現。
const width = 200;
const height = 200;
const padding = 50;
const r = 25;
var loopCounters = [r, parseInt((height * 2) / 3), parseInt(((height * 2) / 3 + r) / 2)];
const increment = 3;
var increments = [-increment, increment, increment];
var ballVar = [0, 0, 0];
const ballVarMax = 10;
function setup() {
createCanvas(width, height);
fill("orange");
stroke("orange");
strokeWeight(3);
}
function draw() {
background("black");
ellipse(width / 2, padding + loopCounters[1] - r, r + ballVar[1], r - ballVar[1] / 2);
if (loopCounters[1] < ballVarMax) ballVar[1] = ballVarMax - loopCounters[1];
else if (loopCounters[1] >= parseInt((height * 2) / 3) - ballVarMax) ballVar[1] = loopCounters[1] - parseInt((height * 2) / 3) + ballVarMax;
else ballVar[1] = 0;
if (loopCounters[1] <= 0) increments[1] = increment;
if (loopCounters[1] >= parseInt((height * 2) / 3)) increments[1] = -increment;
loopCounters[1] += increments[1];
if (increments[1] == increment) {
const offset = map(loopCounters[1], 0, parseInt((height * 2) / 3), -width, width);
line(offset, height - 5, width / 2 + offset, height - 5);
}
}
有鑑於此,線/「進度條」只需要在當increments
為正時出現並更新。而移動範圍橫跨整個畫面。
完整代碼
const width = 200;
const height = 200;
const padding = 50;
const r = 25;
var loopCounters = [r, parseInt((height * 2) / 3), parseInt(((height * 2) / 3 + r) / 2)];
const increment = 3;
var increments = [-increment, increment, increment];
var ballVar = [0, 0, 0];
const ballVarMax = 10;
function setup() {
createCanvas(width, height);
fill("orange");
stroke("orange");
strokeWeight(3);
}
function draw() {
background("white");
ellipse(padding, padding + loopCounters[0] - r, r + ballVar[0], r - ballVar[0] / 2);
ellipse(width / 2, padding + loopCounters[1] - r, r + ballVar[1], r - ballVar[1] / 2);
ellipse(width - padding, padding + loopCounters[2] - r, r + ballVar[2], r - ballVar[2] / 2);
for (var i = 0; i < 3; i++) {
if (loopCounters[i] < ballVarMax) ballVar[i] = ballVarMax - loopCounters[i];
else if (loopCounters[i] >= parseInt((height * 2) / 3) - ballVarMax) ballVar[i] = loopCounters[i] - parseInt((height * 2) / 3) + ballVarMax;
else ballVar[i] = 0;
if (loopCounters[i] <= 0) increments[i] = increment;
if (loopCounters[i] >= parseInt((height * 2) / 3)) increments[i] = -increment;
loopCounters[i] += increments[i];
}
if (increments[1] == increment) {
const offset = map(loopCounters[1], 0, parseInt((height * 2) / 3), -width, width);
line(offset, height - 5, width / 2 + offset, height - 5);
}
}
結語
上面派的錢也收到了,tap and go 的咖啡也喝夠了。手癢癢就寫了這麼個動畫。現在時間,五點三十,早上。不知道一覺醒來還買不買得到switch。
[當天下午更新]
買到了:) 開森。某air 藍芽好用,低延遲。