The leader threw me a picture, which was like this. I happened to be studying recentlycanvas
, decided to use canvas to achieve, by the way, practical theoretical knowledge, the following is my pit collection, for beginners to provide a little 🤏 thinking
Design ideas
- Food falls in a straight line from the top, and the position X is random. When it falls to the bottom, food disappears;
- The stick moves with the mouse;
- When the food touches the top of the stick, the food no longer moves downward and appears at the bottom of the stick;
Random food drops
Individual foods appear randomly
First, we prepare the small cut diagram of the food and draw it using drawImage
html:
<canvas id="canvasGame"></canvas>
Copy the code
js:
var myCanvas = document.getElementById('canvasGame');
var ctx = myCanvas.getContext('2d');
var w = myCanvas.width = window.innerWidth,
h = myCanvas.height = window.innerHeight;
var img=new Image();
img.src='./food1.png';
// drawImage is required after the image is loaded
img.onload=function(){
ctx.drawImage(img,0.0,img.width/3,img.height/3);
}
Copy the code
The drawImage method allows you to insert other elements (img or canvas elements) into the canvas using three syntax:
ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Copy the code
sx
(Optional) Needs to be drawn to the target context,image
The top left X-axis coordinates of the rectangle (cropped) selection box.sy
(Optional) Needs to be drawn to the target context,image
The Y-axis coordinates of the top left corner of the rectangular (cropped) selection box.sWidth
(Optional) Needs to be drawn to the target context,image
The rectangle (cropped) selects the width of the box. If not specified, the entire rectangle (clipped) from the coordinatessx
andsy
Began to,image
The bottom right corner of the end.sHeight
(Optional) Needs to be drawn to the target context,image
The rectangle (cropped) selects the height of the box.dx
:image
The upper-left corner of is the x-coordinate on the target canvas.dy
:image
The upper left corner of is the y-coordinate on the target canvas.dWidth
:image
The width to draw on the target canvas. Allows for the drawing ofimage
Scale. If not specified, when drawingimage
The width does not scale.dHeight
:image
The height drawn on the target canvas. Allows for the drawing ofimage
Scale. If not specified, when drawingimage
The height does not scale.
So you get a picture, you get a picture, you get a random X, you get a random Y, you get a random food picture
var myCanvas = document.getElementById('canvasGame');
var ctx = myCanvas.getContext('2d');
var w = myCanvas.width = window.innerWidth,
h = myCanvas.height = window.innerHeight,
foodX=Math.floor(Math.random() * (w - 40));// The X coordinates are randomly selected between 0 and w, minus 40 to prevent incomplete food display
var img=new Image();
var num=Math.ceil(Math.random() * 3);// Use a random number to fetch food
img.src=`./food${num}.png`;
console.log(img)
img.onload=function(){
ctx.drawImage(img,foodX,0,img.width/3,img.height/3);
}
Copy the code
When the food appears, the location is random, the picture is random
Food drops
Food falls, that is, food moves vertically. At this point, the X coordinate remains unchanged while the Y coordinate linearly increases. The picture needs to be redrawn continuously and the picture previously drawn is erased to achieve the effect of motion.
var myCanvas = document.getElementById('canvasGame');
var ctx = myCanvas.getContext('2d');
var w = myCanvas.width = window.innerWidth,
h = myCanvas.height = window.innerHeight,
foodX=Math.floor(Math.random() * (w - 40));
var img=new Image();
var num=Math.ceil(Math.random() * 3);
img.src=`./food${num}.png`;
img.onload=function(){
ctx.drawImage(img,foodX,0,img.width/3,img.height/3);
}
var foodY=0;
function trans(){
ctx.clearRect(foodX,foodY,img.width/3,img.height/3);// Erase the previously drawn food
foodY+=1;// Drop speed can be adjusted
ctx.drawImage(img,foodX,foodY,img.width/3,img.height/3);/ / to draw
if(foodY<h){
window.requestAnimationFrame(trans)
}
}
trans();
Copy the code
So, we have random food drops, and when we get to the bottom, the food disappears
Multiple foods fall at the same time
Animation of food is divided into two steps: generation – movement. When we have multiple foods, we need to encapsulate them. Since each food is generated and moved separately, if you call the same function at the same time, many variables would interfere, so I thought of encapsulating them in a prototype. Code:
var myCanvas = document.getElementById('canvasGame');
var ctx = myCanvas.getContext('2d');
var w = myCanvas.width = window.innerWidth;
var h = myCanvas.height = window.innerHeight;
var count = 0,
foods = [];
var Food = function() {
this.foodX = 0;
this.foodY = 0;
this.src = ' ';
this.img = {};
this.num = 0,
count++;
foods[count] = this;
}
Food.prototype.draw = function(val) {
this.num = Math.ceil(Math.random() * 3) + 1;
this.img = new Image();
this.src = `./canvasGame/foodThe ${this.num}.png`;
this.img.src = this.src;
this.foodX = Math.floor(Math.random() * (w - 40));
var _this = this;
this.img.onload = function() {
ctx.beginPath();
ctx.drawImage(_this.img, _this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
_this.trans();
}
}
Food.prototype.trans = function() {
this.img.src = this.src;
var _this = this;
this.img.onload = function() {
ctx.clearRect(_this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
_this.foodY += 1;// The speed can be changed
ctx.beginPath();
ctx.drawImage(_this.img, _this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
}
if (this.foodY < h) {
window.requestAnimationFrame(function() { _this.trans(); }); }}var times = setInterval(function() {
new Food();
foods[count].draw(count)
if (count > 20) {// Can change the conditions that control food production
clearInterval(times)
}
}, 1000)
Copy the code
Among them:
Food
It contains attributes that are private to each food- There are two methods on the prototype
draw()
andtrans()
- A timer to generate food,
draw()
Call after completiontrans()
In thetrans()
The loop calls itself in
⚠️ note: 1 ctx.clearRect clears the last drawn image after onload is completed, otherwise some images will not be cleared; 2. Each Image is a separate Image object, so put the object and SRC generated after the new Image() in the Food attribute, otherwise the movement of the previous Food will be affected after the next Food appears. — foodY++ — drawImage()
Effect:
The movement of the stick
Painting prod
When drawing a stick, I thought of two methods: canvas drawing and HTML drawing. Here I choose the latter, but I will write out both methods (the stick here is just a line, and the style problem will be optimized later). 1.
moveTo(x,y)
: Brush 🖌️ starting pointlineTo(x,y)
: Brush 🖌️ endstrokeStyle
: Line segment color 🎨lineWidth
: Thickness of line segmentstroke()
Draw a line:
// draw a line from (0,0) to (0,200) (default width 1px)
var stickCanvas=document.createElement('canvas');
var stickCtx=stickCanvas.getContext('2d');
stickCtx.moveTo(0.0);
stickCtx.lineTo(0.200);
stickCtx.strockStyle="# 000";
stickCtx.stroke();
ctx.drawImage(stickCanvas,0.0.200.200);// Draw on the previous canvas
Copy the code
Oh, by the way, it is an off-screen technique to draw a canvas graph first and then draw it on the canvas by drawImage, which can well solve the performance problem of canvas. Now we get a line, next we need to make the stick move with the movement of the mouse, add a listening event to the canvas, and draw new elements by obtaining the coordinate position of the event
var oldX=0,oldY=0;
/ / to monitor touch
myCanvas.addEventListener("touchmove" , function(e){
var stickX=e.targetTouches[0].clientX,
stickY=e.targetTouches[0].clientY;
ctx.clearRect(oldX,oldY,2.200);
ctx.drawImage(stickCanvas,stickX,stickY,200.200); ! [Screen recording2021-12-16 17.10The 09.gif](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9a867dcf814a4dbba95c97932b443d09~tplv-k3u1fbpfcp-watermark.image?)
oldX=stickX;
oldY=stickY;
})
Copy the code
This gives you a line that moves with the mouse 😂
But with food, it’s not so good, and that’s why I finally gave it up,clearRect
Clearing clears the entire canvas at this point, so when the food movement refreshes, it wipes out the lines,canvas
By default, the latest drawing is at the top), maybe twocanvas
The canvas.
Drawing in HTML is much easier. Give div width and height. The only thing to notice is that if you want other HTML elements on top of the canvas, you just set position: relative to the canvas; , other elements set position: absolute; Can.
<div class="canvasDiv">
<canvas id="canvasGame"></canvas>
<div id="stickDiv"></div>
</div>
Copy the code
.canvasDiv {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
#canvasGame {
position: relative;
}
#stickDiv {
width: 2px;
height: 200px;
background-color: # 000;
position: absolute;
top: 20px;
left: 20px;
z-index: 2;
}
Copy the code
The cursor moves with the mouse. Here we directly listen to the window event to change the cursor position.
var stickDom = document.getElementById("stickDiv");
var stickX = -1,
stickY = -1;
window.addEventListener("touchmove".function(e) {
stickX = e.targetTouches[0].clientX;
stickY = e.targetTouches[0].clientY;
stickDom.style.left = stickX + 'px';
stickDom.style.top = stickY + 'px';
})
Copy the code
Sticks make a connection with food
First, when the stick touches the food, the food disappears, and the connection between them can only be made by location information, i.e. whether stickX and stickY overlap with the food. Here I come up with a nice canvas method ☝️ — isPointInPath().
Usage: Determine if a point (x,y) passed in is in the currently planned path.
boolean ctx.isPointInPath(x, y);
boolean ctx.isPointInPath(x, y, fillRule);
boolean ctx.isPointInPath(path, x, y);
boolean ctx.isPointInPath(path, x, y, fillRule);
Copy the code
(x,y)
: Coordinates of the point passed infillRule
: Take two values,nozero
|evenodd
nozero
: Non-zero wrap principle
If the count is not zero, the point is in the path range and the browser fills it when it calls fill(). If the final value is 0, the area is not in the path scope and the browser will not populate it.
One direction is zero+ 1
, and the opposite direction is- 1
evenodd
The odd-even surround principle
Any point P in the plane leads to a ray. Be careful not to pass through the vertices of the polygon. If the ray intersects the polygon with an odd number of points, the point P is inside the polygon. If the number of intersections is even, the point P is on the outside of the polygon.
Code:
var Food = function() {
this.foodX = 0;
this.foodY = 0;
this.src = ' ';
this.img = {};
this.num = 0.this.isTouch = false,
count++;
foods[count] = this;
}
Food.prototype.draw = function(val) {
var n = Math.ceil(Math.random() * 3) + 1;
this.img = new Image();
this.src = `./canvasGame/food${n}.png`;
this.img.src = this.src;
this.foodX = Math.floor(Math.random() * (w - 40));
this.num=val;
var _this = this;
this.img.onload = function() {
ctx.beginPath();
ctx.drawImage(_this.img, _this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
ctx.rect(_this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
ctx.fillStyle='rgba (0,0,0,0)';
ctx.fill();
_this.trans();
}
}
Food.prototype.trans = function() {
this.img.src = this.src;
var _this = this;
this.img.onload = function() {
ctx.clearRect(_this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
_this.isTouch = ctx.isPointInPath(stickX, stickY)
if(! _this.isTouch) { _this.foodY +=1;
ctx.beginPath();
ctx.drawImage(_this.img, _this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
ctx.rect(_this.foodX, _this.foodY, _this.img.width / 3, _this.img.height / 3);
ctx.fill();
if (_this.foodY < h) {
window.requestAnimationFrame(function() { _this.trans(); }); }}}}Copy the code
- New objects and methods that we encapsulated before
isTouch
Property to store whether touched or not. You’ll see that in the code abovedrawImage
And then I drew another one of the same sizerect
, the reason isisPointInPath()
The method determines the path, so it overlays a transparent rectangular box on top of the picture, and actually determines whether a point is in the rectangular box. - when
isTouch
fortrue
Is not called againdrawImage
Method, and no longer runs the animationrequestAnimationFrame
.
Then I found a new problem, when I touched the whole screen of food disappeared, this problem has been bothering me all afternoon, the reason isisPointInPath()
Method only determines the newly drawn path, and when the stick hits the food,ctx.isPointInPath(stickX, stickY)
fortrue
, even thoughisTouch
Attributes are private attributes for each food, but at this point, eachtrans()
In theisTouch
fortrue
“Would cause all food to disappear.
In the end, I abandoned this method and used the original position judgment to determine whether the mouse moved the coordinate point inside the image (there is also a problem here, the actual range is still a rectangle and not the size of the image, but that doesn’t matter).
if(stickX>=_this.foodX&&stickX<=_this.foodX+_this.img.width / 3&&stickY>=_this.foodY&&stickY<=_this.foodY+_this.img.height / 3){
_this.isTouch=true;
}
Copy the code
The food disappears and is strung on a stick
Pit to write