Hoppa till innehåll

Bollklass

Den här handledningen förklarar hur man använder JavaScript-klasser i canvas-animationer och är inte avsedd för nybörjare. Jag rekommenderar att du först går igenom vår sektion om Lightscripts-motorn och provar handledningen om Färgcykel.

Med det sagt kommer jag inte att använda strikt klasssyntax här, eftersom det bara är “syntaktiskt socker” i JavaScript och de grundläggande koncepten är desamma. Om du vill lära dig mer om mer komplexa animationer rekommenderar jag att du tittar på den här sidan för en grundlig introduktion. Annars, låt oss fortsätta med grunderna.

Till att börja med är en klass helt enkelt en vanlig JavaScript-funktion. Den fungerar på samma sätt, men vi lägger till möjligheten att binda värden till en specifik instans av funktionen. I det här exemplet har jag skapat en boll som beter sig enligt grundläggande fysiklagar. Bollen håller koll på sin position och hastighet, samt den “gravitation” som verkar på den.

<head>
<title>Simple Ball Bounce</title>
<meta description="Ball bounce"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Hämta canvas-elementet från DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
// Canvas-variabler
var width = 320;
var height = 200;
// Bollens X-position
let ballX = 160;
// Bollens Y-position
let ballY = 100;
// Förändringen av ballY över tid. Positiva värden rör sig nedåt, negativa uppåt
let velocityY = 0;
// Förändringen av velocityY över tid.
let accelerationY = 1;
// Effektarray
let effects = [];
function update() {
// 1. Skriv över föregående bildruta
ctx.fillStyle = background;
ctx.fillRect(0, 0, 320,200);
// 2. Se till att det finns en instans av Ball-klassen i effektarrayen
if (effects.length < 1){
effects.push(new Ball());
}
// 3. Iterera genom effektarrayen och anropa draw-funktionen för varje element
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
// 4. Deklarera Ball-klassen
function Ball(){
// 5. Ange instansvariabler
this.bx = ballX;
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
this.impact = false;
// 6. Definiera draw-funktionen
this.draw = function(){
// 7. Ändra Y-hastigheten med Y-accelerationen
this.vy += this.ay;
// 8. Om en kollision med marken har detekterats, sätt hastigheten uppåt och sätt impact-boolean till false. Den här kontrollen sker före nästa funktion så att vi ritar kollisionsbilrutan, inte hoppar över den.
if (this.impact){
this.vy = -10;
this.impact = false;
}
// 9. Detektera en framtida kollision. Om bollens position plus radien plus hastigheten skulle placera bollen under marken gör vi ett par saker -
if (this.by + this.radius + this.vy > 200){
// Sätt hastigheten till 0 så att bollen stannar där vi placerar den i den här bildrutan.
this.vy = 0;
// Sätt impact till true så att nästa loop ritar studsen uppåt
this.impact = true;
// Nästa bildruta ritar studsen uppåt. Den här bildrutan detekterade en framtida kollision. Vi måste tvinga detta om vi vill se kollisionen i sig. Vi flyttar bollen uppåt medan bollens yttre radie är under marken.
while (this.by + this.radius < 200){
this.by++;
}
}
// 10. Ändra Y-komponenten med Y-hastigheten.
this.by += this.vy;
// Rita formen.
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Simple Ball Bounce</title>
<meta description="Ball bounce"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Hämta canvas-elementet från DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
// Canvas-variabler
var width = 320;
var height = 200;
// Bollens X-position
let ballX = 160;
// Bollens Y-position
let ballY = 100;
// Förändringen av ballY över tid. Positiva värden rör sig nedåt, negativa uppåt
let velocityY = 0;
// Förändringen av velocityY över tid.
let accelerationY = 1;
// Effektarray
let effects = [];
function update() {
// 1. Skriv över föregående bildruta
ctx.fillStyle = background;
ctx.fillRect(0, 0, 320,200);
// 2. Se till att det finns en instans av Ball-klassen i effektarrayen
if (effects.length < 1){
effects.push(new Ball());
}
// 3. Iterera genom effektarrayen och anropa draw-funktionen för varje element
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
// 4. Deklarera Ball-klassen
function Ball(){
// 5. Ange instansvariabler
this.bx = ballX;
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
this.impact = false;
// 6. Definiera draw-funktionen
this.draw = function(){
// 7. Ändra Y-hastigheten med Y-accelerationen
this.vy += this.ay;
// 8. Om en kollision med marken har detekterats, sätt hastigheten uppåt och sätt impact-boolean till false. Den här kontrollen sker före nästa funktion så att vi ritar kollisionsbilrutan, inte hoppar över den.
if (this.impact){
this.vy = -10;
this.impact = false;
}
// 9. Detektera en framtida kollision. Om bollens position plus radien plus hastigheten skulle placera bollen under marken gör vi ett par saker -
if (this.by + this.radius + this.vy > 200){
// Sätt hastigheten till 0 så att bollen stannar där vi placerar den i den här bildrutan.
this.vy = 0;
// Sätt impact till true så att nästa loop ritar studsen uppåt
this.impact = true;
// Nästa bildruta ritar studsen uppåt. Den här bildrutan detekterade en framtida kollision. Vi måste tvinga detta om vi vill se kollisionen i sig. Vi flyttar bollen uppåt medan bollens yttre radie är under marken.
while (this.by + this.radius < 200){
this.by++;
}
}
// 10. Ändra Y-komponenten med Y-hastigheten.
this.by += this.vy;
// Rita formen.
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
function update() {
// 1. Skriv över föregående bildruta
ctx.fillStyle = background;
ctx.fillRect(0, 0, 320,200);
// 2. Se till att det finns en instans av Ball-klassen i effektarrayen
if (effects.length < 1){
effects.push(new Ball());
}
// 3. Iterera genom effektarrayen och anropa draw-funktionen för varje element
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
// 4. Deklarera Ball-klassen
function Ball(){
// 5. Ange instansvariabler
this.bx = ballX;
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
this.impact = false;
// 6. Definiera draw-funktionen
this.draw = function(){
// 7. Ändra Y-hastigheten med Y-accelerationen
this.vy += this.ay;
// 8. Om en kollision med marken har detekterats, sätt hastigheten uppåt och sätt impact-boolean till false. Den här kontrollen sker före nästa funktion så att vi ritar kollisionsbilrutan, inte hoppar över den.
if (this.impact){
this.vy = -10;
this.impact = false;
}
// 9. Detektera en framtida kollision. Om bollens position plus radien plus hastigheten skulle placera bollen under marken gör vi ett par saker -
if (this.by + this.radius + this.vy > 200){
// Sätt hastigheten till 0 så att bollen stannar där vi placerar den i den här bildrutan.
this.vy = 0;
// Sätt impact till true så att nästa loop ritar studsen uppåt
this.impact = true;
// Nästa bildruta ritar studsen uppåt. Den här bildrutan detekterade en framtida kollision. Vi måste tvinga detta om vi vill se kollisionen i sig. Vi flyttar bollen uppåt medan bollens yttre radie är under marken.
while (this.by + this.radius < 200){
this.by++;
}
}
// 10. Ändra Y-komponenten med Y-hastigheten.
this.by += this.vy;
// Rita formen.
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}

När man väl tagit sig igenom den första komplexiteten blir det lättare att lägga till mer avancerade beteenden. I nästa exempel har jag lagt till väggkollisioner och lite slumpmässig variation i X-riktningen för att hålla bollen i rörelse.

<head>
<title>Full Bounce</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Hämta canvas-elementet från DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
// 1. velocityX tillagt
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
if (effects.length < 1){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
// 2. Ge X en slumpmässig starthastighet
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 3. Lägg till X- och Y-komponenter i impact-boolean för horisontell kontakt
this.impactX = false;
this.impactY = false;
this.draw = function(){
this.vy += this.ay;
// 4. Lägg till en liten mängd slumpmässig hastighet i X-riktningen varje bildruta för att bibehålla rörelseenergi
this.vx += Math.sin(Date.now());
if (this.impactY){
this.vy = -10;
this.impactY = false;
}
// 5. Om en horisontell kollision detekteras, vänd X-hastigheten och subtrahera en liten mängd kraft för att förhindra att bollen går utom kontroll. Att reflektera eller addera kraften här kan ge intressanta resultat.
if (this.impactX){
this.vx *= -.9;
this.impactX = false;
}
if (this.by + this.radius + this.vy > height){
this.vy = 0;
this.impactY = true;
while (this.by + this.radius < height){
this.by++;
}
}
// 6. Detektera vänster- eller höger horisontell kollision och justera som vi gjorde med Y-komponenten.
if (this.bx + this.radius + this.vx > width){
this.vx = 0;
this.impactX = true;
while (this.bx + this.radius > width){
this.bx--;
}
} else if (this.bx - this.radius + this.vx < 0){
this.vx = 0;
this.impactX = true;
while (this.bx - this.radius < 0){
this.bx++;
}
}
// 7. Ändra X- och Y-komponenterna med deras respektive hastigheter.
this.by += this.vy;
this.bx += this.vx;
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Full Bounce</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Hämta canvas-elementet från DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
// 1. velocityX tillagt
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
if (effects.length < 1){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
// 2. Ge X en slumpmässig starthastighet
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 3. Lägg till X- och Y-komponenter i impact-boolean för horisontell kontakt
this.impactX = false;
this.impactY = false;
this.draw = function(){
this.vy += this.ay;
// 4. Lägg till en liten mängd slumpmässig hastighet i X-riktningen varje bildruta för att bibehålla rörelseenergi
this.vx += Math.sin(Date.now());
if (this.impactY){
this.vy = -10;
this.impactY = false;
}
// 5. Om en horisontell kollision detekteras, vänd X-hastigheten och subtrahera en liten mängd kraft för att förhindra att bollen går utom kontroll. Att reflektera eller addera kraften här kan ge intressanta resultat.
if (this.impactX){
this.vx *= -.9;
this.impactX = false;
}
if (this.by + this.radius + this.vy > height){
this.vy = 0;
this.impactY = true;
while (this.by + this.radius < height){
this.by++;
}
}
// 6. Detektera vänster- eller höger horisontell kollision och justera som vi gjorde med Y-komponenten.
if (this.bx + this.radius + this.vx > width){
this.vx = 0;
this.impactX = true;
while (this.bx + this.radius > width){
this.bx--;
}
} else if (this.bx - this.radius + this.vx < 0){
this.vx = 0;
this.impactX = true;
while (this.bx - this.radius < 0){
this.bx++;
}
}
// 7. Ändra X- och Y-komponenterna med deras respektive hastigheter.
this.by += this.vy;
this.bx += this.vx;
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
if (effects.length < 1){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
// 2. Ge X en slumpmässig starthastighet
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 3. Lägg till X- och Y-komponenter i impact-boolean för horisontell kontakt
this.impactX = false;
this.impactY = false;
this.draw = function(){
this.vy += this.ay;
// 4. Lägg till en liten mängd slumpmässig hastighet i X-riktningen varje bildruta för att bibehålla rörelseenergi
this.vx += Math.sin(Date.now());
if (this.impactY){
this.vy = -10;
this.impactY = false;
}
// 5. Om en horisontell kollision detekteras, vänd X-hastigheten och subtrahera en liten mängd kraft för att förhindra att bollen går utom kontroll. Att reflektera eller addera kraften här kan ge intressanta resultat.
if (this.impactX){
this.vx *= -.9;
this.impactX = false;
}
if (this.by + this.radius + this.vy > height){
this.vy = 0;
this.impactY = true;
while (this.by + this.radius < height){
this.by++;
}
}
// 6. Detektera vänster- eller höger horisontell kollision och justera som vi gjorde med Y-komponenten.
if (this.bx + this.radius + this.vx > width){
this.vx = 0;
this.impactX = true;
while (this.bx + this.radius > width){
this.bx--;
}
} else if (this.bx - this.radius + this.vx < 0){
this.vx = 0;
this.impactX = true;
while (this.bx - this.radius < 0){
this.bx++;
}
}
// 7. Ändra X- och Y-komponenterna med deras respektive hastigheter.
this.by += this.vy;
this.bx += this.vx;
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}

För att vara ärlig är det här exemplet Komplicerat med stort K och kräver förmodligen mer ansträngning än vad du behöver för de flesta grundläggande effekter. För att slippa kommentera koden i alltför hög grad går vi igenom ett par utmanande koncept här.

För det första, hur vet en boll att den kommer att kollidera med en annan boll? Varje bildruta måste varje instans av bollklassen kontrollera positionen för varje annan bollinstans. Genom att jämföra deras positioner kan vi avgöra om två bollar är inom varandras radie och sedan vända X- och Y-hastigheterna därefter.

Ibland kan en boll få så hög hastighet att den passerar igenom en annan boll och de kan nästan uppehålla sig i samma utrymme. Eftersom matematiken som krävs för att justera båda bollarnas position skulle bli alltför komplex tar jag i stället bort bollinstansen vi för tillfället kontrollerar. Eftersom update-funktionen garanterar ett minsta antal bollar skapas en ny bollinstans och simuleringen fortsätter.

Slutligen, när ska vi beräkna hastighets- och positionsförändringar för varje boll? Spelar det någon roll om bollen ritas före eller efter att variablerna uppdateras? Problemet med programmering är att båda frågorna har mer än ett svar. För att hantera detta har jag introducerat ett något annorlunda sätt att hålla reda på kollisioner. Det är upp till dig att välja det du föredrar, men båda metoderna fungerar.

<head>
<title>Multiple Balls</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Hämta canvas-elementet från DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// 1. Öka antalet bollar i effektarrayen
if (effects.length < 3){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 2. Ge varje boll ett unikt ID så att de kan jämföra varandra (men inte sig själva). Vi har också tagit bort "impactX" och "impactY" till förmån för en bättre lösning för fler bollar.
this.id = Math.random();
this.draw = function(){
this.vy += this.ay;
this.vx += Math.sin(Date.now());
if (this.by + this.radius + this.vy > height){
this.vy = -10;
} else if (this.by - this.radius + this.vy < 0){
// 3. Lägg till ett "tak" på canvas om en bollkollision gör att höjden överskrider 0y.
this.vy = 1;
}
if (this.bx + this.radius + this.vx > width){
this.vx = -.9;
} else if (this.bx - this.radius + this.vx < 0){
this.vx = -.9;
}
// 4. Iterera genom varje boll i effektarrayen för att kontrollera kollisioner
for (let i = 0; i < effects.length; i++){
let ele = effects[i];
// Hoppa över den här loopen om bollen vi kontrollerar är den här bollen
if (this.id == ele.id){
continue;
}
// Ta bort den här bollen från canvas om bollarna passerar igenom varandra för mycket
if (Math.abs(this.bx - ele.bx) < this.radius && Math.abs(this.by - ele.by) < this.radius){
effects.splice(i, 1);
}
// Om bollarna kommer att kollidera, vänd deras hastigheter och subtrahera lite kraft för att ta hänsyn till elasticitet
if (Math.abs(this.bx + this.vx - ele.bx) < (this.radius * 2) && Math.abs(this.by + this.vy - ele.by) < (this.radius * 2)){
this.vx *= -.9;
this.vy *= -.9;
}
}
this.by += this.vy;
this.bx += this.vx;
// 5. Eftersom komplexa beräkningar för hastigheter sker före den här punkten, slutför positionen genom att hålla X- och Y-koordinaterna innanför canvas.
while (this.bx - this.radius < 0){
this.bx++
}
while (this.bx + this.radius > width){
this.bx--
}
while (this.by - this.radius < 0){
this.by++
}
while (this.by + this.radius > height){
this.by--
}
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
<head>
<title>Multiple Balls</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Hämta canvas-elementet från DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
let ballX = 160;
let velocityX = 0;
let ballY = 100;
let velocityY = 0;
let accelerationY = 1;
let effects = [];
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// 1. Öka antalet bollar i effektarrayen
if (effects.length < 3){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 2. Ge varje boll ett unikt ID så att de kan jämföra varandra (men inte sig själva). Vi har också tagit bort "impactX" och "impactY" till förmån för en bättre lösning för fler bollar.
this.id = Math.random();
this.draw = function(){
this.vy += this.ay;
this.vx += Math.sin(Date.now());
if (this.by + this.radius + this.vy > height){
this.vy = -10;
} else if (this.by - this.radius + this.vy < 0){
// 3. Lägg till ett "tak" på canvas om en bollkollision gör att höjden överskrider 0y.
this.vy = 1;
}
if (this.bx + this.radius + this.vx > width){
this.vx = -.9;
} else if (this.bx - this.radius + this.vx < 0){
this.vx = -.9;
}
// 4. Iterera genom varje boll i effektarrayen för att kontrollera kollisioner
for (let i = 0; i < effects.length; i++){
let ele = effects[i];
// Hoppa över den här loopen om bollen vi kontrollerar är den här bollen
if (this.id == ele.id){
continue;
}
// Ta bort den här bollen från canvas om bollarna passerar igenom varandra för mycket
if (Math.abs(this.bx - ele.bx) < this.radius && Math.abs(this.by - ele.by) < this.radius){
effects.splice(i, 1);
}
// Om bollarna kommer att kollidera, vänd deras hastigheter och subtrahera lite kraft för att ta hänsyn till elasticitet
if (Math.abs(this.bx + this.vx - ele.bx) < (this.radius * 2) && Math.abs(this.by + this.vy - ele.by) < (this.radius * 2)){
this.vx *= -.9;
this.vy *= -.9;
}
}
this.by += this.vy;
this.bx += this.vx;
// 5. Eftersom komplexa beräkningar för hastigheter sker före den här punkten, slutför positionen genom att hålla X- och Y-koordinaterna innanför canvas.
while (this.bx - this.radius < 0){
this.bx++
}
while (this.bx + this.radius > width){
this.bx--
}
while (this.by - this.radius < 0){
this.by++
}
while (this.by + this.radius > height){
this.by--
}
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}
window.requestAnimationFrame(update);
</script>
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// 1. Öka antalet bollar i effektarrayen
if (effects.length < 3){
effects.push(new Ball());
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
function Ball(){
this.bx = ballX;
this.vx = 100 * Math.sin(Date.now());
this.by = ballY;
this.vy = velocityY;
this.ay = accelerationY;
this.radius = 15;
// 2. Ge varje boll ett unikt ID så att de kan jämföra varandra (men inte sig själva). Vi har också tagit bort "impactX" och "impactY" till förmån för en bättre lösning för fler bollar.
this.id = Math.random();
this.draw = function(){
this.vy += this.ay;
this.vx += Math.sin(Date.now());
if (this.by + this.radius + this.vy > height){
this.vy = -10;
} else if (this.by - this.radius + this.vy < 0){
// 3. Lägg till ett "tak" på canvas om en bollkollision gör att höjden överskrider 0y.
this.vy = 1;
}
if (this.bx + this.radius + this.vx > width){
this.vx = -.9;
} else if (this.bx - this.radius + this.vx < 0){
this.vx = -.9;
}
// 4. Iterera genom varje boll i effektarrayen för att kontrollera kollisioner
for (let i = 0; i < effects.length; i++){
let ele = effects[i];
// Hoppa över den här loopen om bollen vi kontrollerar är den här bollen
if (this.id == ele.id){
continue;
}
// Ta bort den här bollen från canvas om bollarna passerar igenom varandra för mycket
if (Math.abs(this.bx - ele.bx) < this.radius && Math.abs(this.by - ele.by) < this.radius){
effects.splice(i, 1);
}
// Om bollarna kommer att kollidera, vänd deras hastigheter och subtrahera lite kraft för att ta hänsyn till elasticitet
if (Math.abs(this.bx + this.vx - ele.bx) < (this.radius * 2) &&
Math.abs(this.by + this.vy - ele.by) < (this.radius * 2)){
this.vx *= -.9;
this.vy *= -.9;
}
}
this.by += this.vy;
this.bx += this.vx;
// 5. Eftersom komplexa beräkningar för hastigheter sker före den här punkten, slutför positionen genom att hålla X- och Y-koordinaterna innanför canvas.
while (this.bx - this.radius < 0){
this.bx++
}
while (this.bx + this.radius > width){
this.bx--
}
while (this.by - this.radius < 0){
this.by++
}
while (this.by + this.radius > height){
this.by--
}
ctx.beginPath();
ctx.arc(this.bx, this.by, this.radius, 0, Math.PI * 2);
ctx.fillStyle = "black"
ctx.fill();
}
}

Slutligen lägger vi till några grundläggande användarkontroller för att göra det mer intressant. Jag har redan gått igenom färgtoningsändringar i detalj, så för att inte göra koden ännu mer komplex fokuserar vi på att justera antalet bollar och radien. Det här kräver bara ett par ändringar i de befintliga funktionerna.

<head>
<title>User Controls</title>
<meta description="Snake-like climbing gradient effect"/>
<meta publisher="WhirlwindFX" />
<!-- Lägg till två numeriska reglage: radie och antal --->
<meta property="radius" label="Size" type="number" min="5" max="100" default="20">
<meta property="amount" label="Amount" type="number" min="1" max="50" default="2">
</head>
function update() {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
// Sätt det minsta antalet effekter till det användarkontrollerade antalet. Om effekternas längd överstiger antalet, ta bort ett element från slutet.
if (effects.length < amount){
effects.push(new Ball());
} else if (effects.length > amount){
effects.pop();
}
effects.forEach(ele => {
ele.draw();
})
window.requestAnimationFrame(update);
}
this.draw = function(){
// Slutligen, sätt radien till den användarkontrollerade radien inuti draw-funktionen varje bildruta.
this.radius = radius;
this.vy += this.ay;
this.vx += Math.sin(Date.now());
// . . . resten av funktionen . . .
}

Hoppas du gillade vår handledning om JavaScript-klassanimation! Även på den här komplexitetsnivån utgör den här effekten bara en liten del av vad du kan åstadkomma med HTML canvas. Dessutom anser jag den inte riktigt klar ännu. Om vi strävar efter perfektion märker du att bollkollisioner detekteras en bildruta innan de inträffar, vilket innebär att de tekniskt sett ofta inte kolliderar alls. Kollisionerna är också förenklade vad gäller energiöverföring, eftersom den resulterande hastigheten bara tar hänsyn till bollens nuvarande hastighet och inte den andra bollens energi. Slutligen är det ineffektivt ur ett tidskomplexitetsperspektiv att kontrollera varje bolls position mer än en gång per loop, och det kan optimeras med grundläggande memoization.

Poängen är: kritisera alltid dina effekter. De kan nästan alltid förbättras på något sätt; skillnaden mellan en medioker effekt och en fantastisk effekt är ofta att du tillåter dig att titta på den och vara missnöjd minst en gång.

Se vår sektion om Callbacks för den sista steg-för-steg-handledningen, där jag går igenom processen att fånga mätardata för att aktivera och inaktivera effekter.