Skip to content

HTML5 Canvas

使用 <canvas> 元素不是非常难,但你需要一些基本的HTMLJavaScript知识。除一些过时的浏览器不支持<canvas> 元素外,所有的新版本主流浏览器都支持它。Canvas 的默认大小为 300 像素 ×150 像素(宽 × 高,像素的单位是 px)。但是,可以使用 HTML 的高度和宽度属性来自定义 Canvas 的尺寸。为了在 Canvas 上绘制图形,我们使用一个 JavaScript 上下文对象,它能动态创建图像(creates graphics on the fly)。


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

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

img

1.什么是 canvas?

HTML5 <canvas> 元素用于图形的绘制,通过脚本 (通常是JavaScript)来完成.

<canvas> 标签只是图形容器,您必须使用脚本来绘制图形。

你可以通过多种方法使用 canvas 绘制路径,盒、圆、字符以及添加图像。

2.浏览器支持

表格中的数字表示支持 <canvas> 元素的第一个浏览器版本号。具体参考caniuse

image-20240811174337935

3.创建一个画布(Canvas)

一个画布在网页中是一个矩形框,通过 <canvas> 元素来绘制.

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

<canvas></canvas>简单实例如下:

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

注意: 标签通常需要指定一个id属性 (脚本中经常引用), width 和 height 属性定义的画布的大小.

**提示:**你可以在HTML页面中使用多个 <canvas> 元素.

使用 style 属性来添加边框:

实例

html
<canvas id="myCanvas" width="200" height="100" style="border:1px solid #000000;"> </canvas>

4.使用 JavaScript 来绘制图像

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

实例

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

实例解析

首先,找到 <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属性可以是CSS颜色,渐变,或图案。fillStyle 默认设置是#000000(黑色)。

fillRect(*x,y,width,height*) 方法定义了矩形当前的填充方式。

5.Canvas 坐标

canvas 是一个二维网格。

canvas 的左上角坐标为 (0,0)

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

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

坐标实例

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

鼠标移动到上面查看坐标

6.Canvas - 路径

在Canvas上画线,我们将使用以下两种方法:

  • moveTo(x,y) 定义线条开始坐标
  • lineTo(x,y) 定义线条结束坐标

绘制线条我们必须使用到 "ink" 的方法,就像stroke().

实例(直线)

定义开始坐标(0,0), 和结束坐标 (200,100)。然后使用 stroke() 方法来绘制线条:

JavaScript:

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

在canvas中绘制圆形, 我们将使用以下方法:

js
arc(x,y,r,start,stop)

实际上我们在绘制圆形时使用了 "ink" 的方法, 比如 stroke() 或者 fill().

实例(弧线)

使用 arc() 方法 绘制一个圆:

JavaScript:

js
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d"); 
ctx.beginPath();
ctx.arc(95,50,40,0,2*Math.PI); 
ctx.stroke();

7.Canvas - 文本

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

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

使用 fillText():

实例

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

JavaScript:

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

使用 strokeText():

实例

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

JavaScript:

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

8.Canvas - 渐变

渐变可以填充在矩形, 圆形, 线条, 文本等等, 各种形状可以自己定义不同的颜色。

以下有两种不同的方式来设置Canvas渐变:

  • createLinearGradient(x,y,x1,y1) - 创建线条渐变
  • createRadialGradient(x,y,r,x1,y1,r1) - 创建一个径向/圆渐变

当我们使用渐变对象,必须使用两种或两种以上的停止颜色。

addColorStop()方法指定颜色停止,参数使用坐标来描述,可以是0至1.

使用渐变,设置fillStyle或strokeStyle的值为 渐变,然后绘制形状,如矩形,文本,或一条线。

使用 createLinearGradient():

实例

创建一个线性渐变。使用渐变填充矩形:

JavaScript:

js
var c=document.getElementById("myCanvas"); 
var ctx=c.getContext("2d");  
// 创建渐变 
var grd=ctx.createLinearGradient(0,0,200,0); 
grd.addColorStop(0,"red"); grd.addColorStop(1,"white");  
// 填充渐变 
ctx.fillStyle=grd; ctx.fillRect(10,10,150,80);

实例

使用 createRadialGradient():

创建一个径向/圆渐变。使用渐变填充矩形:

JavaScript:

js
var c=document.getElementById("myCanvas"); 
var ctx=c.getContext("2d");  
// 创建渐变 
var grd=ctx.createRadialGradient(75,50,5,90,60,100);
grd.addColorStop(0,"red"); 
grd.addColorStop(1,"white");  
// 填充渐变 
ctx.fillStyle=grd; 
ctx.fillRect(10,10,150,80);

9.Canvas - 图像

把一幅图像放置到画布上, 使用以下方法:

  • drawImage(image,x,y)

使用图像:

The Scream

实例

把一幅图像放置到画布上:

js
var c=document.getElementById("myCanvas"); 
var ctx=c.getContext("2d"); 
var img=document.getElementById("scream"); 
ctx.drawImage(img,10,10);

10.使用Canvas绘制背景动图

使用Canvas+ES6的class语法实现背景动图

image-20240918101149695

代码如下

js
var canvas = document.querySelector('#canvas');
var ctx = canvas.getContext('2d');


//定义canvas的最大宽高
let min = 0;
let maxX;
let maxY;

let currentMouseX;
let currentMouseY;

//鼠标半径
let mouseRadius = 150;

//定义四个方向
const DIRECT_TOP = 0;
const DIRECT_RIGHT = 1;
const DIRECT_BOTTOM = 2;
const DIRECT_LEFT = 3;

//初始化 
function init() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    maxX = canvas.width;
    maxY = canvas.height;
}
init()
console.log(canvas.width);
console.log(canvas.height);



canvas.addEventListener('mousemove', function (event) {
    var canvasRect = canvas.getBoundingClientRect();
    var mouseX = event.clientX - canvasRect.left;
    var mouseY = event.clientY - canvasRect.top;

    currentMouseX = mouseX;
    currentMouseY = mouseY;
    // console.log("鼠标在canvas中的坐标:(" + mouseX + ", " + mouseY + ")");
});



/**
 * 随机生成4个方向 
 * @returns 
 */
function randomDirect() {
    let direct = Math.floor(Math.random() * 10) % 4;
    switch (direct) {
        case 0:
            return DIRECT_TOP;
        case 1:
            return DIRECT_RIGHT;
        case 2:
            return DIRECT_BOTTOM;
        case 3:
            return DIRECT_LEFT;
    }
    // return DIRECT_RIGHT;
}



//点的最大位移距离
const maxMoveDest = 100;
// 引力距离
const GravitationalDistance = 200;

/**
 * 定义一个点的类
 */
class Point {
    x;
    y;
    radius;
    srcX;
    srcY;
    direct;

    /**
     * 创建一个对象,初始化条件,坐标(x,y),
     * @param {*} x 
     * @param {*} y 
     * @param {*} radius  圆的半径,默认为3,
     * @param {*} direct  随机初始一个方向(上、下、左、右)
     */
    constructor(x, y, radius = 3, direct = randomDirect()) {
        this.x = this.srcX = x;
        this.y = this.srcY = y;

        this.radius = radius;
        this.direct = direct;
    }


    //绘制一个圆形
    draw() {
        ctx.beginPath()
        ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
        ctx.fillStyle = '#FFF';
        ctx.fill();

    }

    //随机移动,产生动画的前提
    move() {
        //每次移动的距离
        let rndDest = 2;
        switch (this.direct) {
            //上移 y 减小
            case DIRECT_TOP:
                this.y = this.y - rndDest > 0 ? this.y - rndDest : 0;
                break;
            //右移 x 增加
            case DIRECT_RIGHT:
                this.x = this.x + rndDest < canvas.width ? this.x + rndDest : canvas.width;
                break;
            //下移 y 增加
            case DIRECT_BOTTOM:
                this.y = this.y + rndDest < canvas.height ? this.y + rndDest : canvas.height;
                break;
            //左移 x 减小
            case DIRECT_LEFT:
                this.x = this.x - rndDest > 0 ? this.x - Math.random() : 0;
                break;
        }

        // console.log(`point(${this.x},${this.y})`);

        //调整方向
        this.mayBeChangeDirect();

        //根据鼠标距离调整原点的大小
        this.changeRadius();


    }


    /**
     * 判断点的移动方向是否需要改变
     * 1 移动过程中可能  不应该一直一个方向,移动超过 maxMoveDest 最大位移则随机调整方向
     * 2 如果到达边界 则相反反向调整,  
     *   2.1 上移  且 y <= 0 则方向 向下
     *   2.2 下移  且 y >= canvas的高度  则方向 向上
     *   2.3 左移  且 x <= 0 则方向 向右
     *   2.4 右移  且 x >= canvas的宽度  则方向 向左
     */
    mayBeChangeDirect() {
        //移动到一定距离之后 随机换方向,并记录开始移动的坐标,为了下一次计算移动距离
        if (Math.abs(this.x - this.srcX) > maxMoveDest ||
            Math.abs(this.y - this.srcY) > maxMoveDest) {
            this.direct = randomDirect();
            this.resetSrc();

        }

        // console.log(`this.x:${this.x},this.direct:${this.direct}, DIRECT_LEFT:${DIRECT_LEFT}`);

        //到达左侧 向右
        if (this.x <= 0 && this.direct == DIRECT_LEFT) {
            this.direct = DIRECT_RIGHT
            this.resetSrc();
        }

        //到达顶部,向下
        if (this.y <= 0 && this.direct == DIRECT_TOP) {
            this.direct = DIRECT_BOTTOM
            this.resetSrc();
        }

        //到达右侧,向左
        if (this.x >= canvas.width && this.direct == DIRECT_RIGHT) {
            this.direct = DIRECT_LEFT;
            this.resetSrc();

        }
        //到达底部,向上
        if (this.y >= canvas.height && this.direct == DIRECT_BOTTOM) {
            this.direct = DIRECT_TOP
            this.resetSrc();
        }

    }

    //改变方向之后重新计算  开始移动的原点 
    resetSrc() {
        this.srcX = this.x;
        this.srcY = this.y
    }


    /**
     * 根据鼠标距离调整原点的大小
     */
    changeRadius() {
        let mouseDest = Math.sqrt((this.x - currentMouseX) ** 2 + (this.y - currentMouseY) ** 2);

        if (mouseDest <= mouseRadius) {
            this.radius = 10;
        } else {
            this.radius = 3;
        }
    }

    /**
     * 随机成成 点
     * @param {*} x  点的当前x
     * @param {*} y  点的当前y
     * @param {*} radius  点的半径 
     * @param {*} direct  点的方向 
     * @returns 
     */
    static newPoint(x = Math.floor(Math.random() * (maxX - min) + min),
        y = Math.floor(Math.random() * (maxY - min) + min), radius = 3, direct = randomDirect()) {
        return new Point(x, y, radius, direct)
    }
}


/**
 * 图形类
 */
class Graph {
    points = [];

    /**
     * 
     * @param {Number} nums 初始点数 
     * @param {Number} maxDigs 最大连线距离 
     */
    constructor(nums = 80, maxDigs = 120) {
        this.maxDigs = maxDigs;

        //初始 num个点
        for (let i = 0; i < nums; i++) {
            this.points.push(Point.newPoint());
        }
    }

    /**
     * 绘制点 和 点与点之间的连线
     */
    draw() {

        //绘制鼠标半径
        ctx.beginPath()
        ctx.arc(currentMouseX, currentMouseY, mouseRadius, 0, 2 * Math.PI)
        ctx.strokeStyle = '#FFF';
        ctx.stroke();
        ctx.closePath();

        for (let i = 0; i < this.points.length; i++) {
            let startPoint = this.points[i];
            startPoint.draw();

            for (let j = i + 1; j < this.points.length; j++) {
                let endPoint = this.points[j];
                let dest = Math.sqrt((endPoint.x - startPoint.x) ** 2 + (endPoint.y - startPoint.y) ** 2);
                // if(dest <this.maxDigs){

                // 0  1
                // x  ?
                // 300 0
                let alpha = 1 - dest / this.maxDigs;
                ctx.beginPath();
                let rgba = 'rgba(200,200,200,' + alpha + ')';
                // console.log(`dest:${dest} , this.maxDigs:${this.maxDigs}  ,  rgbg:${rgba}`)
                ctx.strokeStyle = rgba;
                ctx.moveTo(startPoint.x, startPoint.y);
                ctx.lineTo(endPoint.x, endPoint.y);
                ctx.stroke();
                ctx.closePath();
                // }


            }
        }
    }

    /**
     * 移动所有的点位
     * 清空画布
     * 重新绘制点位 和连线
     */
    move() {
        for (let i = 0; i < this.points.length; i++) {
            let point = this.points[i];
            point.move();
        }
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        this.draw();
    }


    startAnimation() {
        setInterval(() => {
            this.move();
        }, 100)
    }
}


const graph = new Graph();
graph.draw();
graph.startAnimation();


// Math.PI
html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
   <style>
    *{
        margin: 0;
        padding: 0;
        
    }
   </style>
  
</head>
<body>


    <div style="background-color:grey;position: fixed;width: 100vw;height: 100vh;">
        <canvas id="canvas" width="100vw" height="100vw">

        </canvas>


    </div>
    <script src="./js/canvas.js"></script>
</body>
</html>

Released under the MIT License.