J'Blog

canvas 接口说明

canvas 接口说明

绘制canvas步骤

<canvas id="canvasContainer" width="600" height="400"></canvas>
// canvas通过属性设置宽高,如果通过style样式设置宽高会对画布造成比例压缩

// 1. 获取画布
const canvas = document.getElementById("canvasContainer");

// 2. 获取2d绘制上下文
const ctx = canvas.getContext("2d");

// 3. 开始创作吧

描边和填充

// stroke描边
// 例如画一个长方形
ctx.rect(100, 100, 200, 100);
ctx.stroke();

// fill填充
// 例如画一个填充的长方形
ctx.rect(100, 100, 200, 100);
ctx.fill();

路径

ctx.beginPath();
// 画画... 不会跟外面的影响
ctx.closePath();

清除

// 清除长方形
ctx.clearRect(x, y, width, height);

画圆弧

// 圆心、半径、开始角度、结束角度、默认顺时针false
ctx.arc(x, y, radius, startAngle, endAngle)
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
ctx.stroke();

// 画一个圆
ctx.arc(100, 100, 50, 0, Math.PI * 2);

// 根据三个点画圆切得到的圆弧
ctx.moveTo(x, y);
ctx.arcTo(x1, y1, x2, y2, radius);
ctx.stroke();

画线段

ctx.moveTo(x, y);
ctx.lineTo(x1, y1);
ctx.stroke();

贝塞尔曲线

// 二次贝塞尔曲线需要两个点
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.quadraticCurveTo(230, 30, 50, 100);
ctx.stroke();

// 三次贝塞尔曲线需要三个点
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
ctx.stroke();

封装路径

const path = new Path2D();
path.moveTo(x, y);
// ...画画画
ctx.stroke(path);

绘制颜色

// 描边颜色
ctx.strokeStyle = "red";
ctx.stroke();

// 填充颜色
ctx.fillStyle = "blue";
ctx.fill();

// 线性渐变
const gradient = ctx.createLinearGradient(20, 0, 220, 0);

// 添加三个色标
gradient.addColorStop(0, "green");
gradient.addColorStop(0.5, "cyan");
gradient.addColorStop(1, "green");

// 设置填充样式并绘制矩形
ctx.fillStyle = gradient;
ctx.fillRect(20, 20, 200, 100);

// 径向渐变
const gradient = ctx.createRadialGradient(110, 90, 30, 100, 100, 70);

// 添加三个色标
gradient.addColorStop(0, "pink");
gradient.addColorStop(0.9, "white");
gradient.addColorStop(1, "green");

// 设置填充样式并绘制矩形
ctx.fillStyle = gradient;
ctx.fillRect(20, 20, 160, 160);

// 圆锥渐变
const gradient = ctx.createConicGradient(0, 100, 100); // 角度, x, y

// 添加五个色标
gradient.addColorStop(0, "red");
gradient.addColorStop(0.25, "orange");
gradient.addColorStop(0.5, "yellow");
gradient.addColorStop(0.75, "green");
gradient.addColorStop(1, "blue");

// 设置填充样式并绘制矩形
ctx.fillStyle = gradient;
ctx.fillRect(20, 20, 200, 200);

图案样式

const img = new Image();
img.src = "canvas_createpattern.png";
// 请确保在图像加载完成后再使用
img.onload = () => {
  const pattern = ctx.createPattern(img, "repeat"); // 水平重复:repeat-x;垂直重复:repeat-y;不重复:no-repeat
  ctx.fillStyle = pattern;
  ctx.fillRect(0, 0, 300, 300);
};

线条样式

lineWidth: 线条宽度
lineCap: 线段末端样式
lineJoin: 线段连接样式
miterLimit: 斜接限制比例
setLineDash(segments): 设置虚线长度以及虚线间隔
lineDashOffset: 虚线偏移值

阴影设置

shadowOffsetX: 水平阴影偏移
shadowOffsetY: 垂直阴影偏移
shadowColor: 阴影颜色
shadowBlur: 模糊值

绘制图像和视频

// 图片
const img = new Image();
img.src = "./test.png";
img.onload = function() {
    // 1. 三个参数时,第二第三个是将图片绘制在画布的水平垂直坐标
    drawImage(img, x, y)
    // 2. 5个参数时,第二第三同上,第四第五个是将图片缩放的宽高
    drawImage(img, x, y, w, h)
    // 3. 9个参数时,img对象后四个参数是原始图片裁剪的x,y坐标以及宽高,后四个参数是绘制在画布上的x,y坐标以及缩放的宽高
    drawImage(img, sx, sy, sw, sh, x, y, w, h);
}

// 视频
const video = document.getElementById("video");
const btn = document.getElementById("playBtn");
btn.onclick = function() {
    if(video.paused) {
	video.play();
	render();
    } else video.pause();
}

function render() {
    ctx.drawImage(video, x, y, w, h);
    requestAnimationFrame(render)
}

绘制文字

font: 字体
strokeText/fillText: 填充文字,参数:文本,x, y, w, h
textAlign: 对齐方式
textBaseline: 文本基线设置
direction: 方向设置
measureText: 预测文本信息

变换

// 位移
translate(x, y)

// 缩放
scale(x, y)

// 旋转
rotate(angle)

// 变换矩阵
transform: 六个参数

合成图像实现刮刮乐

<div id="ggk">中奖啦</div>
<canvas id="c1"></div>

const img = new Image();
img.src = "./bg.png";
img.onload = function() {
    ctx.drawImage(img, 0, 0, 600, 400);
}

let isDraw = false;

canvas.onmousedown = function() {
    isDraw = true;
}

canvas.onmousedown = function() {
    isDraw = false;
}

canvas.onmousemove = function(e) {
    if(isDraw) {
	const x = e.pageX;
	const y = e.pageY;

	ctx.globalCompositeOperation = "destination-out";

	ctx.arc(x, y, 20, 0, Math.PI * 2);
	ctx.fill();
    }
}

裁剪路径

clip()
clip(path)

状态保存和回复

// 存栈
save: 保存
restore: 回复

像素操作

// 获取图片像素信息
ctx.getImageData(x, y, w, h);

// 循环修改数据
for (let i = 0; i < imageData.data.length; i++) {
    imageData.data[i] = 255 - imageData.data[i];
    imageData.data[i + 1] = 255 - imageData.data[i + 1];
    imageData.data[i + 2] = 255- imageData.data[i + 2];
    imageData.data[i + 3] = 255 - imageData.data[i + 3];
}

// 重新渲染修改后的图片
ctx.putImageData(imageData, 0, 0)

封装绘制元素

class Heart {
    constructor(x, y) {
	this.x = x;
	this.y = y;
	this.color = "red";
	this.heartPath = new Path2D();
	this.heartPath.moveTo(this.x, this.y);
	this.heartPath.bezierCurveTo();

	// 监听鼠标移动
	c1.onmousemove = (e) => {
	    let x = e.offsetX;
	    let y = e.offsetY;
	    // 判断是否在当前元素上
	    let isIn = ctx.isPointInPath(this.heartPath, x, y);
	    if (isIn) {
		this.color = "blue";
	    } else {
		this.color = "red";
	    }
	}
    }

    draw() {
	ctx.save();
	ctx.fillStyle = this.color;
	ctx.fill(this.heartPath);

	ctx.restore();
    }


    let heart = new Heart(100, 100);
    function render() {
	ctx.clearRect(0, 0, c1.widht, c1.height);
	heart.draw();
	requestAninmationFrame(render);
    }

    render()
}

canvas实现在线画笔

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas实现在线画笔</title>
    <style>
        canvas {
            border: 1px solid black;
        }

        button.active {
            color: white;
            background-color: orange;
        }
    </style>
</head>
<body>
    <canvas id="c1" width="800" height="600"></canvas>
    <hr>
    <button id="blodBtn" type="button">粗线条</button>
    <button id="thinBtn" type="button">细线条</button>
    <button id="saveBtn" type="button">保存签名</button>
    <input type="color" id="color">
    <button id="clearBtn" type="button">橡皮擦</button>
    <button id="nullBtn" type="button">清空画布</button>

    <script>
        // 获取canvas元素和context
        const canvas = document.querySelector("#c1");
        const ctx = c1.getContext("2d");
        // 设置连接圆润
        ctx.lineJoin = "round";
        // 设置末端圆润
        ctx.lineCap = "round";
        // 获取输入框和按钮
        const colorInput = document.querySelector("#color");
        const blodBtn = document.querySelector("#blodBtn");
        const thinBtn = document.querySelector("#thinBtn");
        const saveBtn = document.querySelector("#saveBtn");
        const clearBtn = document.querySelector("#clearBtn");
        const nullBtn = document.querySelector("#nullBtn");

        // 设置允许绘制的变量
        let isDraw = false;
        
        canvas.onmousedown = function(e){
            // 设置允许绘制
            isDraw = true;
            ctx.beginPath();
            const x = e.pageX - canvas.offsetLeft;
            const y = e.pageY - canvas.offsetTop;
            ctx.moveTo(x, y);
        }

        canvas.onmouseup = function(e){
            isDraw = false;
            ctx.closePath();
        }

        canvas.onmouseleave = function(e){
            isDraw = false;
            ctx.closePath();
        }

        canvas.onmousemove = function(e){
            if(isDraw){
                const x = e.pageX - canvas.offsetLeft;
                const y = e.pageY - canvas.offsetTop;
                ctx.lineTo(x, y);
                ctx.stroke();
            }
        }

        blodBtn.onclick = function() {
            ctx.globalCompositeOperation = "source-over";
            ctx.lineWidth = 20;
            blodBtn.classList.add("active");
            thinBtn.classList.remove("active");
            clearBtn.classList.remove("active");
        }

        thinBtn.onclick = function() {
            ctx.globalCompositeOperation = "source-over";
            ctx.lineWidth = 2;
            thinBtn.classList.add("active");
            blodBtn.classList.remove("active");
            clearBtn.classList.remove("active");
        }

        clearBtn.onclick = function() {
            ctx.globalCompositeOperation = "destination-out";
            ctx.lineWidth = 30;
            clearBtn.classList.add("active");
            thinBtn.classList.remove("active");
            blodBtn.classList.remove("active");
        }

        nullBtn.onclick = function() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }

        saveBtn.onclick = function() {
            const dataUrl = canvas.toDataURL();
            const a = document.createElement("a");
            a.href = dataUrl;
            a.download = "signature.png";
            a.click();
            a.remove();
        }

        colorInput.onchange = function(e) {
            ctx.strokeStyle = e.target.value;
        }
    </script>
</body>
</html>

canvas绘制时钟

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>canvas绘制时钟</title>
</head>

<body>
    <canvas id="c1" width="800" height="600"></canvas>

    <script>
        // 获取canvas元素
        const canvas = document.getElementById('c1');
        const ctx = canvas.getContext('2d');

        function render() {
            // 清除
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            // 保存当前坐标位置和上下文对象的状态
            ctx.save();
            // 移动原点
            ctx.translate(canvas.width / 2, canvas.height / 2);
            ctx.rotate(-Math.PI / 2)

            ctx.save();
            for (let i = 0; i < 12; i++) {
                // 绘制小时刻度
                ctx.beginPath();
                ctx.moveTo(170, 0);
                ctx.lineTo(190, 0);
                ctx.lineWidth = 8;
                ctx.strokeStyle = 'gray';
                ctx.stroke();
                ctx.closePath();
                ctx.rotate(Math.PI * 2 / 12);
            }

            ctx.restore();
            ctx.save();
            for (let i = 0; i < 60; i++) {
                // 绘制小时刻度
                ctx.beginPath();
                ctx.moveTo(180, 0);
                ctx.lineTo(190, 0);
                ctx.lineWidth = 2;
                ctx.strokeStyle = 'gray';
                ctx.stroke();
                ctx.closePath();
                ctx.rotate(Math.PI * 2 / 60);
            }

            ctx.restore();
            ctx.save();

            // 获取当前时间
            const date = new Date();
            let hour = date.getHours();
            let minute = date.getMinutes();
            let second = date.getSeconds();
            hour = hour > 12 ? hour - 12 : hour;

            // 绘制秒针
            ctx.rotate(Math.PI * 2 / 60 * second);
            ctx.beginPath();
            ctx.moveTo(-30, 0);
            ctx.lineTo(190, 0);
            ctx.lineWidth = 2;
            ctx.strokeStyle = 'red';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();
            ctx.save();

            // 绘制分针
            ctx.rotate(Math.PI * 2 / 60 * minute + 2 * Math.PI / 60 / 60 * second);
            ctx.beginPath();
            ctx.moveTo(-20, 0);
            ctx.lineTo(130, 0);
            ctx.lineWidth = 4;
            ctx.strokeStyle = '#888';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();
            ctx.save();

            // 绘制时针
            ctx.rotate(Math.PI * 2 / 12 * hour + 2 * Math.PI / 12 / 60 * minute + 2 * Math.PI / 12 / 60 / 60 * second);
            ctx.beginPath();
            ctx.moveTo(-15, 0);
            ctx.lineTo(110, 0);
            ctx.lineWidth = 8;
            ctx.strokeStyle = '#333';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();

            ctx.restore();
            requestAnimationFrame(render);
        }

        render();
    </script>
</body>

</html>