HTML5 Canvas

165 阅读9分钟

canvas

<canvas> 标签定义图形,比如图表和其他图像,您必须使用脚本来绘制图形。

在画布上(Canvas)画一个红色矩形,渐变矩形,彩色矩形,和一些彩色的文字。

什么是 Canvas?

HTML5 的 canvas 元素使用 JavaScript 在网页上绘制图像。

画布是一个矩形区域,您可以控制其每一像素。

canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

创建一个画布(Canvas)

向 HTML5 页面添加 canvas 元素。

默认情况下 <canvas> 元素没有边框和内容。

<canvas id="myCanvas" width="200" height="100"></canvas>

通过 JavaScript 来绘制

canvas 元素本身是没有绘图能力的。所有的绘制工作必须在 JavaScript 内部完成:

    var c = document.getElementById("myCanvas");
    var ctx = c.getContext("2d");
    ctx.fillStyle = "#FF0000";
    ctx.fillRect(0, 0, 150, 75);

JavaScript 使用 id 来寻找 canvas 元素:

var c=document.getElementById("myCanvas");

然后,创建 context 对象:

var ctx = c.getContext("2d"); 

getContext("2d") 对象是内建的 HTML5 对象,拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。

下面的两行代码绘制一个红色的矩形:

ctx.fillStyle="#FF0000";
ctx.fillRect(0,0,150,75); 

fillStyle 方法将其染成红色,fillRect 方法规定了形状、位置和尺寸。

理解坐标

上面的 fillRect 方法拥有参数 (0,0,150,75)。

意思是:在画布上绘制 150x75 的矩形,从左上角开始 (0,0)。

如下图所示,画布的 X 和 Y 坐标用于在画布上对绘画进行定位。

Snipaste_2023-03-02_19-44-29.png

更多 Canvas 实例

下面的在 canvas 元素上进行绘画的更多实例:

实例 - 线条

通过指定从何处开始,在何处结束,来绘制一条线: 定义开始坐标(0,0), 和结束坐标 (200,100)。然后使用 stroke() 方法来绘制线条:

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.moveTo(0,0);
ctx.lineTo(200,100);
ctx.stroke();

Snipaste_2023-03-02_19-50-33.png

实例 - 圆形

通过规定尺寸、颜色和位置,来绘制一个圆:

    let canvas = document.querySelector("canvas")
    let ctx = canvas.getContext("2d")
    ctx.beginPath();
    ctx.arc(95, 50, 40, 0, 2 * Math.PI);
    ctx.stroke();

Snipaste_2023-03-02_19-57-32.png

Canvas - 文本

使用 canvas 绘制文本,重要的属性和方法如下:

  • font - 定义字体
  • fillText(text,x,y) - 在 canvas 上绘制实心的文本
  • strokeText(text,x,y) - 在 canvas 上绘制空心的文本

使用 fillText():


let canvas = document.querySelector("canvas")
let ctx = canvas.getContext("2d")

ctx.moveTo(0,100)
ctx.lineTo(600,100)
ctx.stroke()


ctx.font = "30px 楷体"
ctx.textBaseline = "center"
ctx.textBaseline = "center"

ctx.fillText('皮卡丘',100,100)

Snipaste_2023-03-02_20-25-14.png 使用 strokeText():

使用 "Arial" 字体在画布上绘制一个高 30px 的文字(空心):

var c=document.getElementById("canvas");
var ctx=c.getContext("2d");
ctx.font="30px Arial";
ctx.strokeText("Hello World",10,50);

Snipaste_2023-03-02_20-33-28.png

阴影

shadowOffsetX、shadowOffsetY

shadowOffsetX 和 shadowOffsetY 用来设定阴影在 X 和 Y 轴的延伸距离,它们是不受变换矩阵所影响的。负值表示阴影会往上或左延伸,正值则表示会往下或右延伸,它们默认都为 0。

shadowBlur

shadowBlur 用于设定阴影的模糊程度,其数值并不跟像素数量挂钩,也不受变换矩阵的影响,默认为 0。

shadowColor

shadowColor 是标准的 CSS 颜色值,用于设定阴影颜色效果,默认是全透明的黑色。

举个例子看一下:

// 获取 canvas 元素
var canvas = document.querySelector('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if(canvas.getContext) {
      // 获取绘图上下文
      var ctx = canvas.getContext('2d');
      ctx.font = "50px serif"; // 设置文案大小和字体
      ctx.shadowColor = "#cccccc"; //  设置阴影颜色
      ctx.fillStyle = "#ee7934"; //  设置填充颜色
      ctx.shadowOffsetX = 10; // X轴上的阴影
      ctx.shadowOffsetY = 10; // Y轴上的阴影
      ctx.shadowBlur = 5; // 阴影的模糊程度
      ctx.fillText("Hi Canvas !", 100, 50);
      ctx.fillRect(100, 100, 200, 100);
      ctx.shadowOffsetX = -10;
      ctx.shadowOffsetY = -10;
      ctx.shadowBlur = 5;
      ctx.fillText("Hi Canvas !", 100, 300);
      ctx.fillRect(100, 350, 200, 100);
    }

Snipaste_2023-03-02_20-35-49.png

透明度

除了绘制实色的图形,还可以绘制有透明度的图形。通过设置 globalAlpha 属性或者使用有透明度的样式作为轮廓或填充都可以实现

举个例子看一下:

// 获取 canvas 元素
  var canvas = document.querySelector('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if(canvas.getContext) {
      // 获取绘图上下文
      var ctx = canvas.getContext('2d');
      // 绘制一个矩形
      ctx.beginPath();
      // 指定透明度的填充样式
      ctx.fillStyle = "rgba(0, 255, 0, 0.2)";
      ctx.fillRect(10,10,300,100);
      // 绘制一个矩形边框
      ctx.beginPath();
      // 指定透明度的描边样式
      ctx.strokeStyle = "rgba(255, 0, 0, 0.7)";
      ctx.strokeRect(10, 90, 100, 300);
      // 绘制一个圆
      ctx.beginPath()
      ctx.fillStyle = "rgba(255, 255, 0, 1)";
      // 设置透明度值
      ctx.globalAlpha = 0.5;
      ctx.arc(200, 200, 100, 0, Math.PI*2, true);
      ctx.fill();
    }

Snipaste_2023-03-02_20-40-27.png

渐变

渐变分为两种,分别是线性渐变和径向渐变,在绘图中我们可以用线性或者径向的渐变来填充或描边。

线性渐变

语法: createLinearGradient(x1, y1, x2, y2),参数分别为 起点的坐标和终点的坐标。

在渐变的设置中还需要一个方法来添加渐变的颜色,语法为:gradient.addColorStop(offset, color),其中color就是颜色,offset 则是颜色的偏移值,只为0到1之间的数。

举个例子看一下:

 // 获取 canvas 元素
 var canvas = document.querySelector('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if(canvas.getContext) {
      // 获取绘图上下文
      var ctx = canvas.getContext('2d');
      // 创建渐变
      var gradient1 = ctx.createLinearGradient(10, 10, 400, 10);
      gradient1.addColorStop(0, "#00ff00");
      gradient1.addColorStop(1, "#ff0000");
      var gradient2 = ctx.createLinearGradient(10, 10, 400, 10);
      // 从0.5的位置才开始渐变
      gradient2.addColorStop(0.5, "#00ff00");
      gradient2.addColorStop(1, "#ff0000");
      ctx.beginPath()
      ctx.fillStyle = gradient1;
      ctx.fillRect(10, 10, 400, 100);
      ctx.beginPath();
      ctx.fillStyle = gradient2;
      ctx.fillRect(10, 150, 400, 100);
    }

Snipaste_2023-03-02_20-42-04.png

径向渐变

语法:ctx.createRadialGradient(x0, y0, r0, x1, y1, r1),参数分别为开始圆的坐标和半径以及结束圆的坐标和半径。

举个例子看一下:

// 获取 canvas 元素
 var canvas = document.querySelector('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if(canvas.getContext) {
      // 获取绘图上下文
      var ctx = canvas.getContext('2d');
      // 创建渐变
      // 结束坐标为点
      var gradient1 = ctx.createRadialGradient(100, 100, 100, 100, 100, 0);
      gradient1.addColorStop(0, "#ff770f");
      gradient1.addColorStop(1, "#ffffff");
      // 结束坐标为半径30的圆
      var gradient2 = ctx.createRadialGradient(320, 100, 100, 320, 100, 30); 
      gradient2.addColorStop(0, "#ff770f");
      gradient2.addColorStop(1, "#ffffff");
      // 从0.5的位置才开始渲染
      var gradient3 = ctx.createRadialGradient(100, 320, 100, 100, 320, 0); 
      gradient3.addColorStop(0.5, "#ff770f"); 
      gradient3.addColorStop(1, "#ffffff");
      // 开始坐标和结束坐标不一样
      var gradient4 = ctx.createRadialGradient(320, 320, 100, 250, 250, 0);
      gradient4.addColorStop(0, "#ff770f");
      gradient4.addColorStop(1, "#ffffff");
      ctx.beginPath();
      ctx.fillStyle = gradient1;
      ctx.fillRect(10, 10, 200, 200);
      ctx.beginPath();
      ctx.fillStyle = gradient2;
      ctx.fillRect(220, 10, 200, 200);
      ctx.beginPath();
      ctx.fillStyle = gradient3;
      ctx.fillRect(10, 220, 200, 200);
      ctx.beginPath();
      ctx.fillStyle = gradient4;
      ctx.fillRect(220, 220, 200, 200);
    }

Snipaste_2023-03-02_20-44-07.png

动画

在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常我们仅仅在脚本执行结束后才能看见结果,所以想在 for 循环里面完成动画是不可能的。那么为了实现动画,我们需要一些可以定时执行重绘的方法。

  • setInterval(function, delay) :定时器,当设定好间隔时间后,function 会定期执行。
  • setTimeout(function, delay):延时器,在设定好的时间之后执行函数
  • requestAnimationFrame(callback):告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。

如果不需要与用户互动,可以使用 setInterval() 方法,它可以定期执行指定的代码。如果需要做游戏,可以使用键盘或者鼠标事件配合上 setTimeout() 方法来实现。通过设置事件监听,可以捕捉用户的交互,并执行相应的动作。

下面我们采用 window.requestAnimationFrame()来实现一个动画效果。requestAnimationFrame()方法提供了更加平缓且有效率的方式来执行动画,当系统准备好重绘条件后才会调用绘制动画帧。一般每秒钟回调函数执行 60 次,也有可能会被降低,因为通常情况下requestAnimationFrame()方法会遵循 W3C 的建议,浏览器中的回调函数执行次数通常与浏览器屏幕刷新次数相匹配。还有为了提高性能和电池寿命,通常 requestAnimationFrame() 方法运行在后台标签页或者隐藏在 里时,requestAnimationFrame() 方法会暂停调用以提升性能和电池寿命。

举个例子看一下:

let sun;
let earth;
let moon;
let ctx;
function init(){
    sun = new Image();
    earth = new Image();
    moon = new Image();
    sun.src = "sun.png";
    earth.src = "earth.png";
    moon.src = "moon.png";

    let canvas = document.querySelector("#solar");
    ctx = canvas.getContext("2d");

    sun.onload = function (){
        draw()
    }

}
init();
function draw(){
    ctx.clearRect(0, 0, 300, 300); //清空所有的内容
    /*绘制 太阳*/
    ctx.drawImage(sun, 0, 0, 300, 300);

    ctx.save();
    ctx.translate(150, 150);

    //绘制earth轨道
    ctx.beginPath();
    ctx.strokeStyle = "rgba(255,255,0,0.5)";
    ctx.arc(0, 0, 100, 0, 2 * Math.PI)
    ctx.stroke()

    let time = new Date();
    //绘制地球
    ctx.rotate(2 * Math.PI / 60 * time.getSeconds() + 2 * Math.PI / 60000 * time.getMilliseconds())
    ctx.translate(100, 0);
    ctx.drawImage(earth, -12, -12)

    //绘制月球轨道
    ctx.beginPath();
    ctx.strokeStyle = "rgba(255,255,255,.3)";
    ctx.arc(0, 0, 40, 0, 2 * Math.PI);
    ctx.stroke();

    //绘制月球
    ctx.rotate(2 * Math.PI / 6 * time.getSeconds() + 2 * Math.PI / 6000 * time.getMilliseconds());
    ctx.translate(40, 0);
    ctx.drawImage(moon, -3.5, -3.5);
    ctx.restore();

    requestAnimationFrame(draw);
}

65211642 (1).jpg

总结一下绘制动画的基本步骤

  • 清空 canvas:除非接下来要画的内容会完全充满 canvas(例如背景图),否则需要清空所有。最简单的做法就是用 clearRect 方法。
  • 保存 canvas 状态:如果要改变 canvas 状态的设置(样式,变形之类的),之后又要在每画一帧之时都是原始状态的情况时,需要先保存一下。
  • 绘制动画图形(animated shapes)
  • 恢复 canvas 状态:如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。

高级动画

高级动画就是在初级动画的基础上加上一些符合物理的运动,这样就能使我们的动画更生动而不是那么的呆板。 下面我们一步步来实现一个小球的自由落体的运动。

绘制小球

首先我们先绘制一个小球,直接上代码:

// 获取 canvas 元素
    var canvas = document.getElementById('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if (canvas.getContext) {
        // 获取绘图上下文
        var ctx = canvas.getContext('2d');
        var ball = {
            x: 100,
            y: 100,
            radius: 25,
            color: 'blue',
            draw: function () {
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
                ctx.closePath();
                ctx.fillStyle = this.color;
                ctx.fill();
            }
        };
        ball.draw();
    }

速率

我们通过给小球添加速率矢量进行移动。这个依旧用requestAnimationFrame() 方法来实现,在每一帧里面,依旧用clear 清理掉之前帧里旧的圆形。

    // 获取 canvas 元素
    var canvas = document.getElementById('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if (canvas.getContext) {
        // 获取绘图上下文
        var ctx = canvas.getContext('2d');
        var ball = {
            x: 100,
            y: 100,
            vx: 1,
            vy: 3,
            radius: 25,
            color: 'blue',
            draw: function () {
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
                ctx.closePath();
                ctx.fillStyle = this.color;
                ctx.fill();
            }
        };
        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ball.draw();
            // 添加速率
            ball.x += ball.vx;
            ball.y += ball.vy;
            window.requestAnimationFrame(draw);
        }
        window.requestAnimationFrame(draw);
        ball.draw();
    }

球.gif

边界

想让小球反弹那么我们就需要添加边界

   // 获取 canvas 元素
     var canvas = document.getElementById('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if(canvas.getContext) {
      // 获取绘图上下文
      var ctx = canvas.getContext('2d');
      var ball = {
        x: 100,
        y: 100,
        vx: 1,
        vy: 3,
        radius: 25,
        color: 'blue',
        draw: function() {
          ctx.beginPath();
          ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
          ctx.closePath();
          ctx.fillStyle = this.color;
          ctx.fill();
        }
      };
      function draw() {
        ctx.clearRect(0,0, canvas.width, canvas.height);
        ball.draw();
        // 添加速率
        ball.x += ball.vx;
        ball.y += ball.vy;
        // 添加边界
        if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
          ball.vy = -ball.vy;
        }
        if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
          ball.vx = -ball.vx;
        }
       window.requestAnimationFrame(draw);
      }
      window.requestAnimationFrame(draw);
      ball.draw();
    }

球2.gif

加速度

为了让动作更真实,我们还需要加入加速度的处理。

  // 获取 canvas 元素
     var canvas = document.getElementById('canvas');
    // 通过判断getContext方法是否存在来判断浏览器的支持性
    if(canvas.getContext) {
      // 获取绘图上下文
      var ctx = canvas.getContext('2d');
      var ball = {
        x: 100,
        y: 100,
        vx: 1,
        vy: 3,
        radius: 25,
        color: 'blue',
        draw: function() {
          ctx.beginPath();
          ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
          ctx.closePath();
          ctx.fillStyle = this.color;
          ctx.fill();
        }
      };
      function draw() {
        ctx.clearRect(0,0, canvas.width, canvas.height);
        ball.draw();
        // 添加加速度
        ball.vy *= .99;
        ball.vy += .25;
        // 添加速率
        ball.x += ball.vx;
        ball.y += ball.vy;
        // 添加边界
        if (ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0) {
          ball.vy = -ball.vy;
        }
        if (ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0) {
          ball.vx = -ball.vx;
        }
       window.requestAnimationFrame(draw);
      }
      window.requestAnimationFrame(draw);
      ball.draw();
    }

弹.gif

参考

案例+图解带你一文读懂Canvas