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

  1. Food falls in a straight line from the top, and the position X is random. When it falls to the bottom, food disappears;
  2. The stick moves with the mouse;
  3. 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,imageThe top left X-axis coordinates of the rectangle (cropped) selection box.
  • sy(Optional) Needs to be drawn to the target context,imageThe Y-axis coordinates of the top left corner of the rectangular (cropped) selection box.
  • sWidth(Optional) Needs to be drawn to the target context,imageThe rectangle (cropped) selects the width of the box. If not specified, the entire rectangle (clipped) from the coordinatessxandsyBegan to,imageThe bottom right corner of the end.
  • sHeight(Optional) Needs to be drawn to the target context,imageThe rectangle (cropped) selects the height of the box.
  • dx:imageThe upper-left corner of is the x-coordinate on the target canvas.
  • dy:imageThe upper left corner of is the y-coordinate on the target canvas.
  • dWidth:imageThe width to draw on the target canvas. Allows for the drawing ofimageScale. If not specified, when drawingimageThe width does not scale.
  • dHeight:imageThe height drawn on the target canvas. Allows for the drawing ofimageScale. If not specified, when drawingimageThe 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:

  • FoodIt contains attributes that are private to each food
  • There are two methods on the prototypedraw()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 point
  • lineTo(x,y): Brush 🖌️ end
  • strokeStyle: Line segment color 🎨
  • lineWidth: Thickness of line segment
  • stroke()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,clearRectClearing clears the entire canvas at this point, so when the food movement refreshes, it wipes out the lines,canvasBy default, the latest drawing is at the top), maybe twocanvasThe 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 in
  • fillRule: 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

evenoddThe 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 beforeisTouchProperty to store whether touched or not. You’ll see that in the code abovedrawImageAnd 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.
  • whenisTouchfortrueIs not called againdrawImageMethod, 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 thoughisTouchAttributes are private attributes for each food, but at this point, eachtrans()In theisTouchfortrue“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