答案 0 :(得分:0)
查看此答案:https://stackoverflow.com/a/30739547/3200577
基本上,html canvas的工作方式与html元素的渲染和绘制方式有很大不同。虽然您可以在页面上选择并移动html元素,但您无法选择并移动已添加到画布中的内容,因为您可以在画布上执行的操作是添加和清除像素。因此,当您将图像添加到画布时,您将图像的像素添加到画布。如果您要再次添加图像但稍微向左添加图像,那么看起来您已经添加了两个图像,其中第二个图像与第一个图像重叠,这不是您想要的。
因此,要在画布上为图像的运动设置动画,您需要:
对此流程的更抽象的描述:基本上,您需要创建,存储和管理您自己的模型画布的外观;当您想添加,删除您在画布上绘制的更改内容时,您实际上不会在画布上添加,删除或更改任何直接。您可以在自己的模型中添加,删除和更改内容,清除画布,然后根据模型重绘画布。
例如,您的模型可能是JS对象,例如
myModel = {
images: [
{ url: "my/picture.png", position: [123,556] },
{ url: "another/picture.jpg", position: [95,111] }
]
}
你会编写函数,1)递增模型中图像位置的值,2)清除画布,3)将模型绘制到画布上。然后,您将创建一个循环(使用requestAnimationFrame
或setInterval
),它将重复执行这三个函数。
对于大型或复杂的项目,我强烈建议使用诸如paperjs之类的画布库,为您实现该流程(这样您就不必考虑创建模型并清除和重绘画布)。它提供了高级功能,例如开箱即用的动画。
答案 1 :(得分:0)
像所有动画一样,使某些东西看起来像是在移动就是绘制一系列静止图像(一帧),每个图像都略有不同。如果帧的速率足够高(超过每秒20次),人类的眼睛和大脑会将静止图像的序列看作连续的运动。从第一部电影到今天的高端游戏,这就是动画的完成方式。
因此对于画布来说,绘制框架的过程很简单。创建一个清除画布,绘制所需内容的函数,退出该函数,以便浏览器可以将完成的帧移动到显示中。 (请注意,在函数中,在画布上看不到画布上的任何内容,您必须退出该函数以查看绘制的内容)
要制作动画,您必须至少按秒执行上述操作20次以上。为获得最佳效果,您应该以与显示硬件显示帧相同的速率执行此操作。对于总是每秒60帧(fps)的浏览器。
为了帮助您与显示器同步,您可以使用函数requestAnimationFrame(myDrawFunction)
它告诉浏览器您正在设置动画,并且只有在显示硬件准备好显示新的完整帧时才会显示渲染结果,而不是绘制函数退出时(可能是硬件框架的中途)。
因此,作为一个简单的例子,让我们创建一个动画对象。
const imageSrc = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const myImage = {
posX: 0, // current position of object
posY: 0,
speed: 3, // speed in pixels per frame (1/60th second)
direction: 0, // direction of movement in radians 0 is at 3oclock
image: (() => { // create and load an image (Image will take time to load
// and may not be ready until after the code has run.
const image = new Image;
image.src = imageSrc;
})(),
然后是在画布上绘制对象的绘图功能
draw(ctx) {
ctx.drawImage(this.image, this.posX, this.posY);
},
当我们动画时,我们需要每帧移动一次对象,为此我们创建一个更新函数。
update() {
this.posX += (mx = Math.cos(this.direction)) * this.speed;
this.posY += (my = Math.sin(this.direction)) * this.speed;
}
} // end of object
要制作动画,我们会创建一个主循环,通过requestAnimationFrame
调用每个硬件显示帧。
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// update the obj
myImage.update();
// draw the object
myImage.draw(ctx);
// request the next frame
requestAnimationFrame(mainLoop);
// note all of the above code is not seen by the use until the
// next hardwares display frame. If you used setTimeout or setInterval
// then it would be displayed when this function exits (which may be
// halfway through a frame resulting in the animation being cut in two)
}
// request the first frame
requestAnimationFrame(mainLoop);
这是最基本的动画。当然,您需要获取画布上下文并等待图像加载。另外,由于画布的图像移动,您需要检查它何时停止动画。
例如停止动画是图像不在屏幕上
if(myImage.posX < canvas.width){ // only render while image is on the canvas
requestAnimationFrame(mainLoop);
} else {
console.log("Animation has ended");
}
现在把它放在一起作为演示。
该演示具有一些额外的智能,可以使图像环绕,确保图像在开始之前加载并使其从屏幕开始,但基本上与上面概述的相同。
// get the 2D context from the canvas id
const ctx = canvas.getContext("2d");
// setup the font and text rendering
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// create the image object and load the image
const imageSrc = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const myImage = {
posX: 0, // current position of object
posY: 0,
speed: 3, // speed in pixels per frame (1/60th second)
direction: 0, // direction of movement in radians 0 is at 3oclock
image: (() => { // create and load an image (Image will take time to load
// and may not be ready until after the code has run.
const image = new Image;
image.src = imageSrc;
// to start move the image of the display
image.onload = function(){
const imageDiagonalSize = Math.sqrt(
image.width * image.width + image.height * image.height
)
myImage.posX = (canvas.width / 2) - imageDiagonalSize - Math.cos(myImage.direction) * imageDiagonalSize;
myImage.posX = (canvas.height / 2) - imageDiagonalSize - Math.sin(myImage.direction) * imageDiagonalSize;
}
return image;
})(),
draw(ctx) {
ctx.drawImage(this.image, this.posX, this.posY);
},
update() {
var mx,my; // get movement x and y
this.posX += (mx = Math.cos(this.direction)) * this.speed;
this.posY += (my = Math.sin(this.direction)) * this.speed;
// if the image moves of the screen move it to the other side
if(mx > 0) { // if moving right
if(this.posX > canvas.width){
this.posX = 0-this.image.width;
}
}else if(mx < 0) { // if moving left
if(this.posX + this.image.width < 0){
this.posX = canvas.width;
}
}
if(my > 0) { // if moving down
if(this.posY > canvas.height){
this.posY = 0-this.image.height;
}
}else if(my < 0) { // if moving up
if(this.posY + this.image.height < 0){
this.posY = canvas.height;
}
}
}
}
function mainLoop() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
if(myImage.image.complete) { // wait for image to load
myImage.update();
myImage.draw(ctx);
}else{ // some feedback to say the image is loading
ctx.fillText("Loading image..",canvas.width / 2, canvas.height / 2);
}
// request the next frame
requestAnimationFrame(mainLoop);
}
// request the first frame
requestAnimationFrame(mainLoop);
canvas {
border: 2px solid black;
}
<!-- id's must be unique to the page -->
<canvas id="canvas"></canvas>