当前位置: 首页 > article >正文

【Canvas入门】从零开始在Canvas上绘制简单的动画

这篇文章是观看HTML5 Canvas Tutorials for Beginners教程做的记录,所以代码和最后的效果比较相似,教程的内容主要关于这四个部分:

  1. 创建并设置尺寸
  2. 添加元素
  3. 让元素动起来
  4. 与元素交互

设置Canvas的大小

获取到canvas并设置尺寸为当前窗口的大小:

var canvas = document.getElementById(canvas_id);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

获取到二维的Context并赋值给变量c,后面对canvas的操作主要都是通过c进行的:

var c = canvas.getContext("2d")

填充矩形

在(x,y)坐标处,填充一个宽为width,高为height的矩形:

c.fillRect(x, y, width, height)

设定填充矩形的颜色:

c.fillStyle = "green";         // 填充的颜色为绿色
c.fillRect(100, 100, 100, 100) // 在(100,100)坐标处填充一个边长为100的正方形

绘制线段

c.beginPath();               // 开始绘制路径
c.moveTo(100, 200);          // 相当于笔尖在空中移动到(x,y)坐标的位置,也就是
c.lineTo(200, 300);          // 让笔尖从之前的坐标到现在的坐标(200,300)画一条线段
c.strokeStyle = "#4455ff";   // 设置线条的颜色
c.stroke();                  // 渲染到Canvas上

绘制圆形

,以(x,y)为圆心,radius为半径画圆,startAngle, endAngle分别为起点角度和终点角度(弧度制),最后一个参数是可选的counterclockwise,true为逆时针,false顺时针,默认为false:

arc(x, y, radius, startAngle, endAngle)
arc(x, y, radius, startAngle, endAngle, counterclockwise)

在 (100, 100) 处画一个半径为30,顺时针的圆:

c.beginPath();
c.arc(100, 100, 30, 0, 2 * Math.PI, false);
c.strokeStyle = "#00ff00";
c.stroke();

使用for循环生成随机位置的圆形

Math.random() 生成0-1之间的随机数,所以要乘上宽度和高度来让它的随机范围扩大到整个屏幕,也就是得到了很多个随机的坐标。

for (var i = 0; i < 100; i++) {
    var x = Math.random() * window.innerWidth;
    var y = Math.random() * window.innerHeight;
    var r = Math.random() * 100;                // 半径也随机生成,范围在0-100
    c.beginPath();
    c.arc(x, y, r, 0, 2 * Math.PI);
    c.strokeStyle = "#0fff00";
    c.stroke();
}

这样就生成了100个位置随机,大小随机的圆形。

加入动画

定义一个函数 animate() 并在内部调用,这样就得到了一个一直重复运行的函数,可以在这个函数里面更新元素的状态。
设置初始坐标为 (200, 200),x方向的速度 v x v_x vx为2,y方向的速度 v y v_y vy为0,在每一次刷新的时候使用速度来更新位置。
我们知道匀速运动时 x = v t x=vt x=vt,所以可以通过速度改变位移。还可以通过加速度 a a a来改变速度 v v v,在Canvas中实现变速运动。

var x = 200, y = 200;
var vx = 2, vy = 0;
function animate() {
    requestAnimationFrame(animate);
    c.clearRect(0, 0, window.innerWidth, window.innerHeight); // 清除掉前一帧绘制的内容,否则会不断的重叠
    c.beginPath();
    c.arc(x, y, 30, 0, 2 * Math.PI);
    c.stroke();
    x += vx;
    y += vy;
}
animate();

更多会动的圆

定义一个类,用来画一个圆心在 (200, 200) 处,半径为30的圆:

class Circle{
    constructor(x, y) { // 在 constructor 中完成一些值的初始化
        this.x = x;
        this.y = y;
    }
    draw() {
        c.beginPath();
        c.arc(this.x, this.y, 30, 0, 2 * Math.PI); 
        c.stroke();
    }
}
var circle = new Circle(200, 200);
// 当调用 new Circle(200, 200); 时,会将类的参数列表传入constructor()的参数列表,并执行constructor()
circle.draw()

接下来把更新 (x, y) 坐标的部分写成Circle类的update()函数,并把 draw() 方法也包含进来:

class Circle{
        constructor(x, y, vx, vy, radius) {
            this.x = x;
            this.y = y;
            this.vx = vx;
            this.vy = vy;
            this.radius = radius;
        }
        draw() { // 根据 x,y 坐标,半径 radius 画一个圆
            c.beginPath();
            c.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
            c.stroke();
        }
        update() { // 更新 x,y 坐标, 将越界时反弹, 更新后绘制圆形
            if (this.x - this.radius < 0 || this.x + this.radius > window.innerWidth)  this.vx = -this.vx;
            if (this.y - this.radius < 0 || this.y + this.radius > window.innerHeight) this.vy = -this.vy;
            this.x += this.vx;
            this.y += this.vy;
            this.draw();
        }
    }

    var circle = new Circle(200, 200, 2, 2, 30);
    function animate() {
        requestAnimationFrame(animate);
        c.clearRect(0, 0, window.innerWidth, window.innerHeight);
        circle.update(); // 这边只需要一次次的调用 update() 即可
    }

有了Circle类,就可以方便的创建很多个Circle对象,并更新它们的位置。

var circleArray = [];
for (var i = 0; i < 50; i++) {
    var x = Math.random() * window.innerWidth;
    var y = Math.random() * window.innerHeight;
    var vx = (Math.random() - 0.5) * 3;
    var vy = (Math.random() - 0.5) * 3;
    var radius = Math.random() * 100;
    circleArray.push(new Circle(x, y, vx, vy, radius))
}

function animate() {
    requestAnimationFrame(animate);
    c.clearRect(0, 0, WIDTH, HEIGHT);
    for (var i = 0; i < circleArray.length; i++) {
        circleArray[i].update();
    }
}
animate();

与鼠标交互

给移动的小球增加与鼠标交互的动画,当鼠标靠近时增大小球的半径,当鼠标远离时,再让它缩小,需要监听mousemove事件,并更新mouse.x和mouse.y的值:

window.addEventListener('mousemove', function (event) {
    mouse.x = event.clientX;
    mouse.y = event.clientY;
})

然后在update()里面:

if (Math.sqrt((this.x - mouse.x) ** 2 + (this.y - mouse.y) ** 2) < 100) {
    if (this.radius < maxRadius)
        this.radius += 1;
    } else if (this.radius > minRadius) {
        this.radius -= 1;
    }

为了方便使用,可以提前设定好最小半径和最大半径:

const maxRadius = 40, minRadius = 2;

适配不同尺寸的屏幕

当窗口大小发生变化时,会触发resize事件,可以通过监听这个事件,来重新初始化Canvas中的小球。

window.addEventListener('resize', function (event) {
    init();
})

如果小球的数量是固定的,那么较小的屏幕上会太密集,而较大的屏幕上看起来又会太稀疏,所以可以根据屏幕的大小来设置小球的个数,我这里的做法是将窗口的面积除以5000并向下取整来计算出小球的个数:

var circleArray = [];
function init() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    var ballNumber = Math.floor((window.innerWidth * window.innerHeight) / 5000);
    circleArray = [];
    for (var i = 0; i < ballNumber; i++) {
        var x = Math.random() * window.innerWidth;
        var y = Math.random() * window.innerHeight;
        var vx = (Math.random() - 0.5) * 3;
        var vy = (Math.random() - 0.5) * 3;
        var radius = Math.random() * minRadius + 1;//Math.random() * 20;
        circleArray.push(new Circle(x, y, vx, vy, radius))
    }
}

为了让小球更好看一些,可以为它们填充不同的颜色:

const colorArray = ["#AFD3E2", "#F2B6A0", "#FC4F00", "#FFEEB3", "#A5C0DD", "#FF6969", "#ECC9EE", "#00FFCA"];

constructor() 中设置当前这个小球的填充颜色:

this.color = colorArray[Math.floor(Math.random() * colorArray.length)]

在填充之前设定一下颜色:

c.fillStyle = this.color;

到这里,这个简单的项目就结束了,各个颜色的小球会按照随机的速度飘动,鼠标附近的小球会逐渐变大:
请添加图片描述


http://www.kler.cn/a/16414.html

相关文章:

  • UniApp 应用、页面与组件的生命周期详解
  • C++《继承》
  • SHELL脚本(Linux)
  • 【软件工程】一篇入门UML建模图(类图)
  • 灰狼优化算法
  • 应用程序部署(IIS的相关使用,sql server的相关使用)
  • 高性能定时器介绍及代码逐行解析--时间堆
  • 走进小程序【十一】微信小程序【使用Echarts 和 腾讯地图】
  • R语言 | 数据框
  • MySQL数据库——MySQL修改视图(ALTER VIEW)
  • vim 常用操作(vimtutor阅读笔记)
  • 移动宽带安装说明一(刘欣)
  • 【第十一届泰迪杯B题】产品订单的数据分析与需求预测
  • Netty小白入门教程
  • tensorflow中Keras ---图像预处理----tf.keras.preprocessing.image.ImageDataGenerator 类
  • P1915 [NOI2010] 成长快乐
  • 三元操作 三元操作符 if-else / ? :
  • 进程控制下篇
  • [LeetCode]1033. 移动石子直到连续
  • 《基于光电容积法和机器学习的冠状动脉疾病患者出血风险预测》阅读笔记
  • 【Python2.x与Python3.x的区别】
  • 进程相关(创建-回收-exec-守护进程)
  • 【华为OD机试 2023最新 】任务总执行时长(C语言题解 100%)
  • BPMN2.0 任务-服务任务
  • LVS负载均衡集群--DR模式
  • Chapter1:控制系统数学模型(下)