The effect
Technology matting
- canvas
- es6
- requestAnimationFrame
canvas
Canvas is a canvas label in the Web, which allows us to freely draw corresponding graphics on it through JS.
es6
Part of ES6 grammar is used in the case, please refer to RUan Yifeng’s ES6
requestAnimationFrame
RequestAnimationFrame, an API that needs to be covered separately here.
Usually we want to achieve animation effects in JS, no more than the following ways
-
Pure CSS means
- Transition the transition
- Animation animation
-
JavaScript means
-
SetInterval timer
Call a function repeatedly or execute a code segment with a fixed time delay between calls
-
SetTimeout decelerator
The **setTimeout()** method sets a timer that executes a function or a specified piece of code after the timer expires
-
requestAnimationFrame
-
requestAnimationFrame
Due to the macro task execution mechanism, although we use the timer setInterval, it is inevitable that the program execution time will be inaccurate, which will eventually lead to a slight lag in the animation effect on the user’s perspective.
RequestAnimationFrame does not have this problem, because the average screen refresh rate is 60 times per second, so when we use requestAnimationFrame, it can execute 60 times per second. So we use it for animation, which is generally much smoother.
If we want to perform a continuous animation, we need to continue calling itself in the requestAnimationFrame callback function.
Such as:
let index = 0;
function step(timestamp) {
console.log(index);
index++;
window.requestAnimationFrame(step);
}
step();
Copy the code
RequestAnimationFrame returns an ID to stop its execution
let id = window.requestAnimationFrame(() = >{
console.log(1);
});
// It was cancelled
window.cancelAnimationFrame(id);
Copy the code
Initialize the project environment
Setting up basic Labels
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1, minimum - scale = 1, the user - scalable = no"
/>
<title>index.html</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding-top: 200px;
text-align: center;
}
canvas {
border: 1px solid # 000;
background-image: url(./images/20200820014508687.jpg);
background-size: cover;
}
</style>
</head>
<body>
<canvas width="600" height="400"> </canvas>
</body>
</html>
Copy the code
Create three classes
- Bounce is used to Bounce balls
- Baffle is used to implement the bottom Baffle
- The Game controls the ball and the paddle
// Used to control the ball and baffle
class Game {}
/ / ball
class Bounce {}
/ / damper
class Baffle {}
Copy the code
A ball
Initialize the ball
class Bounce {
constructor(ctx, option) {
this.ctx = option.canvas.getContext('2d'); // Get the canvas context
this.x = option.x; // The x coordinate of the ball
this.y = option.y; // The y-coordinate of the ball
this.speedX = option.speedX; // Horizontal speed
this.speedY = option.speedY; // Vertical speed
this.radius = option.radius; // Small sphere radius
this.canvas = option.canvas; // Canvas DOM object}}Copy the code
Paint balls
class Bounce {
show() {
this.ctx.fillStyle = 'aqua'; // Set the ball color
this.ctx.beginPath();// Reroute the path
this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2); // Draw the sphere
this.ctx.fill(); // Fill the color}}Copy the code
Set the ball to move automatically
class Bounce {
move() {
this.x += this.speedX;
this.y += this.speedY;
this.show(); }}Copy the code
Determine when the ball hits the boundary
class Bounce {
move() {
this.x += this.speedX;
this.y += this.speedY;
// Determine if the boundary is exceeded
this.crash();
this.show();
}
// Judge up and down out of bounds
get overRect() {
return this.y >= this.canvas.height || this.y <= 0;
}
// The ball moves backwards when it hits the boundary
crash() {
// The judgment level is out of bounds
if (this.x >= this.canvas.width || this.x <= 0) {
// Set the opposite direction
this.speedX = -this.speedX;
}
// Judge up and down out of bounds
if (this.overRect) {
// Set the opposite direction
this.speedY = -this.speedY; }}}Copy the code
Let the ball move
<! DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1, minimum - scale = 1, the user - scalable = no"
/>
<title>index.html</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding-top: 200px;
text-align: center;
}
canvas {
border: 1px solid # 000;
background-image: url(./images/20200820014508687.jpg);
background-size: cover;
}
</style>
</head>
<body>
<canvas width="600" height="400"> </canvas>
<script>
/ / ball
class Bounce {
constructor(ctx, option) {
this.ctx = ctx;
this.x = option.x;
this.y = option.y;
this.speedX = option.speedX;
this.speedY = option.speedY;
this.radius = option.radius;
this.canvas = option.canvas;
}
move() {
// Clear old clearRect(x,y, width, height)
this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height);
this.x += this.speedX;
this.y += this.speedY;
this.crash();
this.show();
}
// out of bounds
get overRect() {
return this.y >= this.canvas.height || this.y <= 0;
}
crash() {
if (this.x >= this.canvas.width || this.x <= 0) {
this.speedX = -this.speedX;
}
// The center touches the top and the bottom
if (this.overRect) {
this.speedY = -this.speedY; }}show() {
this.ctx.fillStyle = 'aqua';
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2);
this.ctx.fill(); }}const canvas = document.querySelector('canvas'); // Get the canvas tag
const ctx = canvas.getContext('2d'); // Get the canvas context
// Create a small ball
const bounce = new Bounce(ctx, {
x: parseInt(Math.random() * canvas.width - 50), // random x
y: 0.speedX: 3.speedY: 3.radius: 30,
canvas,
});
// Temporarily use a timer instead
setInterval(() = > {
bounce.move();
}, 16);
</script>
</body>
</html>
Copy the code
baffle
Initialize the baffle
class Baffle {
constructor(ctx, option) {
this.ctx = ctx;// Canvas context
this.canvas = option.canvas; // Canvas DOM element
this.x = option.x; // Block x coordinates
this.y = option.y; // Block y coordinates
this.width = option.width; // The width of the baffle
this.height = option.height; // The height of the baffle
this.color = option.color; // The color of the baffle}}Copy the code
Depicting the baffle
class Baffle {
show() {
const fillStyle = this.ctx.fillStyle; // Cache the current canvas color
this.ctx.fillStyle = this.color; // Set the canvas color
this.ctx.fillRect(this.x, this.y, this.width, this.height); // Draw rectangle - baffle
this.ctx.fillStyle = fillStyle; // Reset the canvas color}}Copy the code
Add keyboard control events
class Baffle {
// Control the baffle movement through the keyboard
controlMove() {
Canvas does not support binding keyboard events
document.body.addEventListener('keydown'.(e) = > {
switch (e.key) {
case 'ArrowLeft':
this.x -= 30;
// If the baffle goes beyond the left boundary
if (this.x < 0) {
this.x = 0;
}
break;
case 'ArrowRight':
this.x += 30;
// If the baffle goes beyond the right boundary
if (this.x + this.width > this.canvas.width) {
this.x = this.canvas.width - this.width;
}
break;
default:
break; }}); }}Copy the code
The render panel
/ / damper
class Baffle {
constructor(ctx, option) {
this.ctx = ctx;
this.canvas = option.canvas;
this.x = option.x;
this.y = option.y;
this.width = option.width;
this.height = option.height;
this.color = option.color;
// Add keyboard events
this.controlMove();
}
show() {
console.log(this.x, this.y);
const fillStyle = this.ctx.fillStyle;
this.ctx.fillStyle = this.color;
this.ctx.fillRect(this.x, this.y, this.width, this.height);
this.ctx.fillStyle = fillStyle;
}
controlMove() {
Canvas does not support binding keyboard events
document.body.addEventListener('keydown'.(e) = > {
switch (e.key) {
case 'ArrowLeft':
this.x -= 30;
if (this.x < 0) {
this.x = 0;
}
break;
case 'ArrowRight':
this.x += 30;
if (this.x + this.width > this.canvas.width) {
this.x = this.canvas.width - this.width;
console.log(this.x);
}
break;
default:
break; }}); }}const canvas = document.querySelector("canvas");
const ctx = canvas.getContext('2d');
this.baffle = new Baffle(ctx, {
x: 0.y: canvas.height - 4.width: 100.height: 4,
canvas,
color: 'orange'});// Temporarily use the timer
setInterval(() = > {
ctx.clearRect(0.0, canvas.width, canvas.height);
this.baffle.show();
}, 16);
Copy the code
To optimize the
Since, to consider adding a Game class for unified control of balls and baffles, we need to package and optimize the code for balls and baffles first.
Complete ball code
class Bounce {
/ / initialization
constructor(ctx, option) {
this.ctx = ctx;
this.x = option.x;
this.y = option.y;
this.speedX = option.speedX;
this.speedY = option.speedY;
this.radius = option.radius;
this.canvas = option.canvas;
}
// Set ball to move
move() {
this.x += this.speedX;
this.y += this.speedY;
this.crash();
this.show();
}
// Determine if the bottom is reached
get isReachBottom() {
return this.y >= this.canvas.height;
}
// out of bounds
get overRect() {
return this.y >= this.canvas.height || this.y <= 0;
}
// Handle collision redirection
crash() {
if (this.x >= this.canvas.width || this.x <= 0) {
this.speedX = -this.speedX;
}
// The center touches the top and the bottom
if (this.overRect) {
this.speedY = -this.speedY; }}// Draw the ball
show() {
this.ctx.fillStyle = 'aqua';
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2);
this.ctx.fill(); }}Copy the code
Complete baffle code
/ / damper
class Baffle {
/ / initialization
constructor(ctx, option) {
this.ctx = ctx;
this.canvas = option.canvas;
this.x = option.x;
this.y = option.y;
this.width = option.width;
this.height = option.height;
this.color = option.color;
// Add keyboard control events
this.controlMove();
}
// Draw the baffle
show() {
const fillStyle = this.ctx.fillStyle;
this.ctx.fillStyle = this.color;
this.ctx.fillRect(this.x, this.y, this.width, this.height);
this.ctx.fillStyle = fillStyle;
}
// Add keyboard control events
controlMove() {
Canvas does not support binding keyboard events
document.body.addEventListener('keydown'.(e) = > {
switch (e.key) {
case 'ArrowLeft':
this.x -= 30;
if (this.x < 0) {
this.x = 0;
}
break;
case 'ArrowRight':
this.x += 30;
if (this.x + this.width > this.canvas.width) {
this.x = this.canvas.width - this.width;
}
break;
default:
break; }}); }}Copy the code
Game class
Build the Game class structure
/ / game class
class Game {
// constructor
constructor(el, option){}// Take care of initialization
init(el, option){}// Empty the canvas
clear(){}// Add the animation to play continuously
addInanimationList() {
this.animationList.push(this.clear.bind(this));
}
/ / start
start() {
this.requestAnimationFrame();
}
// Start the animation
requestAnimationFrame(){}// Check whether the ball and baffle collide
get isCrashBaffle() {}}Copy the code
Complete game code
class Game {
// constructor
constructor(el, option) {
this.init(el, option);
this.start();
}
// Take care of initialization
init(el, option) {
const canvas = document.querySelector(el);// Get the canvas DOM
canvas.width = option.width; // Set the width
canvas.height = option.height; // Set the height
const ctx = canvas.getContext('2d'); // Get the canvas context
this.canvas = canvas;
this.ctx = ctx;
// Create a small ball
this.bounce = new Bounce(ctx, {
x: parseInt(Math.random() * canvas.width - 50),
y: 0.speedX: 3.speedY: 3.radius: 30,
canvas,
});
// Create a baffle
this.baffle = new Baffle(ctx, {
x: 0.y: canvas.height - 4.width: 100.height: 4,
canvas,
color: 'orange'});this.animationList = []; // Store the animation to be continuously executed
this.addInanimationList(); // Statistics adds animations to be played continuously
}
// Empty the canvas
clear() {
this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height);
}
// Add the animation to play continuously
addInanimationList() {
this.animationList.push(this.clear.bind(this));
this.animationList.push(this.bounce.move.bind(this.bounce));
this.animationList.push(this.baffle.show.bind(this.baffle));
// Determine the collision
}
/ / start
start() {
this.requestAnimationFrame();
}
// Start the animation
requestAnimationFrame() {
// If the ball does not touch the baffle and touches the floor
if (!this.isCrashBaffle && this.bounce.isReachBottom) {
alert("You lost.");
location.reload();
} else {
this.animationList.forEach((fn) = > fn());
this.requestAnimationFrameId = window.requestAnimationFrame(() = > {
this.requestAnimationFrame(); }); }}// Check whether the ball and baffle collide
get isCrashBaffle() {
const isBetweenX =
this.bounce.x + this.bounce.radius >= this.baffle.x &&
this.bounce.x <= this.baffle.x + this.baffle.width;
const isBetweenY =
this.bounce.y + this.bounce.radius >= this.baffle.y;
returnisBetweenX && isBetweenY; }}Copy the code
Case complete code
<! DOCTYPE html><html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="Width = device - width, initial - scale = 1.0, the maximum - scale = 1, minimum - scale = 1, the user - scalable = no"
/>
<title>index.html</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
padding-top: 200px;
text-align: center;
}
canvas {
border: 1px solid # 000;
background-image: url(./images/20200820014508687.jpg);
background-size: cover;
}
</style>
</head>
<body>
<canvas width="600" height="400"> </canvas>
<script>
class Game {
constructor(el, option) {
this.init(el, option);
this.start();
}
init(el, option) {
const canvas = document.querySelector(el);
canvas.width = option.width;
canvas.height = option.height;
const ctx = canvas.getContext('2d');
this.canvas = canvas;
this.ctx = ctx;
this.bounce = new Bounce(ctx, {
x: parseInt(Math.random() * canvas.width - 50),
y: 0.speedX: 3.speedY: 3.radius: 30,
canvas,
});
this.baffle = new Baffle(ctx, {
x: 0.y: canvas.height - 4.width: 100.height: 4,
canvas,
color: 'orange'});this.requestAnimationFrameId = undefined;
this.animationList = [];
this.addInanimationList();
}
clear() {
this.ctx.clearRect(0.0.this.canvas.width, this.canvas.height);
}
addInanimationList() {
this.animationList.push(this.clear.bind(this));
this.animationList.push(this.bounce.move.bind(this.bounce));
this.animationList.push(this.baffle.show.bind(this.baffle));
// Determine the collision
}
start() {
this.requestAnimationFrame();
}
requestAnimationFrame() {
if (!this.isCrashBaffle && this.bounce.isReachBottom) {
alert('You lost');
location.reload();
} else {
this.animationList.forEach((fn) = > fn());
this.requestAnimationFrameId = window.requestAnimationFrame(() = > {
this.requestAnimationFrame(); }); }}get isCrashBaffle() {
const isBetweenX =
this.bounce.x + this.bounce.radius >= this.baffle.x &&
this.bounce.x <= this.baffle.x + this.baffle.width;
const isBetweenY =
this.bounce.y + this.bounce.radius >= this.baffle.y;
returnisBetweenX && isBetweenY; }}/ / ball
class Bounce {
constructor(ctx, option) {
this.ctx = ctx;
this.x = option.x;
this.y = option.y;
this.speedX = option.speedX;
this.speedY = option.speedY;
this.radius = option.radius;
this.canvas = option.canvas;
}
move() {
this.x += this.speedX;
this.y += this.speedY;
this.crash();
this.show();
}
get isReachBottom() {
return this.y >= this.canvas.height;
}
// out of bounds
get overRect() {
return this.y >= this.canvas.height || this.y <= 0;
}
crash() {
if (this.x >= this.canvas.width || this.x <= 0) {
this.speedX = -this.speedX;
}
// The center touches the top and the bottom
if (this.overRect) {
this.speedY = -this.speedY; }}show() {
this.ctx.fillStyle = 'aqua';
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0.Math.PI * 2);
this.ctx.fill(); }}/ / damper
class Baffle {
constructor(ctx, option) {
this.ctx = ctx;
this.canvas = option.canvas;
this.x = option.x;
this.y = option.y;
this.width = option.width;
this.height = option.height;
this.color = option.color;
this.controlMove();
}
show() {
const fillStyle = this.ctx.fillStyle;
this.ctx.fillStyle = this.color;
this.ctx.fillRect(this.x, this.y, this.width, this.height);
this.ctx.fillStyle = fillStyle;
}
controlMove() {
Canvas does not support binding keyboard events
document.body.addEventListener('keydown'.(e) = > {
switch (e.key) {
case 'ArrowLeft':
this.x -= 30;
if (this.x < 0) {
this.x = 0;
}
break;
case 'ArrowRight':
this.x += 30;
if (this.x + this.width > this.canvas.width) {
this.x = this.canvas.width - this.width;
}
break;
default:
break; }}); }}const game = new Game('canvas', { width: 600.height: 400 });
</script>
</body>
</html>
Copy the code
other
- 10 minutes. – Takes you to H5-backgammon
- Canvas generated poster
- The code address
- Online demo address