张裕 发表于 2024-8-27 22:39:22

前端canvas的学习和将网页生成canvas图片

目的



[*]终极可以实现二维码填充在指定图片位置,并且可以填充文字在图片中
[*]学习条记,个人记录,
[*]学习掘金大佬德育处主任

[*]https://juejin.cn/user/2673620576140030

[*]专栏

[*]https://juejin.cn/column/7113168145912692773

[*]作者堆栈

[*]https://gitee.com/k21vin/thunder-monkey-canvas

第一个canvas

<body>
<canvas
    id="c"
    width="300"
    height="200"
    style="border:1px solid #ccc"
></canvas>
<script>
    //获取canvas元素
    const cnv = document.querySelector('#c');
    //获取canvas上下文环境对象
    const cxt = cnv.getContext('2d');
    //绘制图形
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//重点坐标(x,y)
    cxt.stroke();//将起点和终点链接起来
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/701227bd5022730d88227c14921c8da4.png
不能通过css设置画布的宽高



[*]啊
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
    #c{
      width: 400px;
      height: 400px;
    }
</style>
</head>
<body>
<canvas
    id="c"

    style="border:1px solid #ccc"
></canvas>
<script>
    //获取canvas元素
    const cnv = document.querySelector('#c');
    //获取canvas上下文环境对象
    const cxt = cnv.getContext('2d');
    //绘制图形
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//重点坐标(x,y)
    cxt.stroke();//将起点和终点链接起来
    console.log(cnv.width);//输出300
    console.log(cnv.height);//输出150
</script>
</body>
</html>
canvas 的默认宽度是300px,默认高度是150px。

[*]如果使用 css 修改 canvas 的宽高(比如本例变成 400px * 400px),那宽度就由 300px 拉伸到 400px,高度由 150px 拉伸到 400px。
[*]使用 js 获取 canvas 的宽高,此时返回的是 canvas 的默认值。
坐标系



[*] 这个很重要,不能弄混了
[*] Canvas 使用的是 W3C 坐标系 ,也就是遵循我们屏幕、报纸的阅读风俗,从上往下,从左往右。
https://i-blog.csdnimg.cn/blog_migrate/daf07b4c7706c8d68988a58190b6ca70.png
W3C 坐标系 和 数学直角坐标系 的 X轴 是一样的,只是 Y轴 的反向相反。
W3C 坐标系 的 Y轴 正方向向下
绘制直线



[*]使用moveTo,lineTo,stroke即可绘制出一条直线
<body>
<canvas id="c" style="border:1px solid red"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//下一个点的坐标(x,y)
    cxt.stroke();//链接起来
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/352baf19546a6b3c0735a8a24f1325cb.png


[*]绘制多条直线就调用多次方法即可

[*]如果绘制的坐标点出现小数点,那么将会占据多一些格子,并将颜色平均分布(大概就是这个意思)
[*]https://juejin.cn/post/7115431586857746440

https://i-blog.csdnimg.cn/blog_migrate/d07fc21911a4e529cda4fde3a7f0b3f6.png
设置样式



[*]lineWidth:线的粗细
[*]strokeStyle线的颜色
[*]lineCap:线帽
https://i-blog.csdnimg.cn/blog_migrate/ddca653f2566187b3c969bb075a3c012.png
<body>

<canvas id="c" style="border:1px solid red"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象
    cxt.moveTo(10,10);//起始点坐标(x,y)
    cxt.lineTo(60,60);

    //设置线条的宽度
    cxt.lineWidth = 20;
    //更改线条的颜色
    cxt.strokeStyle = 'green';
    //修改线帽
    cxt.lineCap = 'round';

    cxt.stroke();//链接起来
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/2851127c75fb5eb2e46da73a114de432.png
新开路径



[*]我说怎么画2条线别的一条也变粗了
[*]在绘制多条线段的同时,还要设置线段样式,通常需要开发新路径。要不然样式之间会相互污染。
[*]使用 beginPath() 方法,重新开一个路径

[*]设置新线段的样式(必做项)

[*]否则会出现前面影响背面,大概背面影响前面的环境出现

[*]比如前一个线设置了strokeWidth:20,那么即使开发了新路径,不设置strokeWidth的话第二条路径还是依照strokeWidth为20举行绘制

<body>

<canvas id="c" style="border:1px solid red"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象
    cxt.moveTo(10,10);//起始点坐标(x,y)
    cxt.lineTo(60,60);
    //设置线条的宽度
    cxt.lineWidth = 20;
    cxt.stroke();//链接起来

    //新开一个路径
    cxt.beginPath();
    //设置新线段的样式
    cxt.lineWidth = 1;
    //更改线条的颜色
    cxt.strokeStyle = 'green';
    //修改线帽
    cxt.lineCap = 'round';
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//下一个点的坐标(x,y)
    cxt.stroke();//链接起来
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/2a3cdc68d158d730aa6d0883b7441e0e.png


[*]在设置 beginPath() 的同时,也各自设置样式。这样就能做到相互不影响了。
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
const cnv = document.getElementById('c')
const cxt = cnv.getContext('2d')

cxt.moveTo(20, 100)
cxt.lineTo(200, 100)
cxt.lineWidth = 10
cxt.strokeStyle = 'pink'
cxt.stroke()

cxt.beginPath() // 重新开启一个路径
cxt.moveTo(20, 120.5)
cxt.lineTo(200, 120.5)
cxt.lineWidth = 4
cxt.strokeStyle = 'red'
cxt.stroke()
</script>

折线(特别的直线)



[*]也是使用方法moveTo,lineTo,stroke即可完成
矩形(rect)



[*]点组成线,线组成面,面构成图形,你可以使用绘制直线的方式去绘制矩形,但是有现成的方法当然有现成的
<body>
<canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象
    cxt.strokeStyle = "green";//必须要写在绘制前面
    cxt.strokeRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/62792866691c1acb48e2243e1feadbf4.png


[*]大概这么理解,直接抄他的,嘻嘻
https://i-blog.csdnimg.cn/blog_migrate/22ab59874263ad1eb8b859814467b674.png
填充矩形



[*]你可以理解为stroke都是在做描边结果的,真正要创建填充的结果还是需要使用fill开头的一些关键字
[*]需要注意的是,fillStyle 必须写在 fillRect() 之前,不然样式不生效。
<body>
<canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象

    cxt.fillStyle = "blue";//必须要写在绘制前面
    cxt.fillRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100

</script>
</body>

https://i-blog.csdnimg.cn/blog_migrate/d524a300968c05ecec77c7fd5b6c7ea9.png


[*]同时使用strokeRect()和fillRect(),则是描边+填充结果
<body>
<canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象

    cxt.fillStyle = "blue";//必须要写在绘制前面
    cxt.fillRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100

    cxt.strokeStyle = "green";
    cxt.strokeRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/7a39f27be0318d4c4162197171ebe8be.png
使用rect()



[*] rect() 和 fillRect() 、strokeRect() 的用法差不多,唯一的区别是:
[*] strokeRect() 和 fillRect() 这两个方法调用后会立刻绘制;rect() 方法被调用后,不会立刻绘制矩形,而是需要调用 stroke() 或 fill() 辅助渲染。
<body>
<canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');

    cxt.strokeStyle= 'pink'
    cxt.fillStyle = 'blue';
   
    cxt.rect(10, 10, 120, 100);

    cxt.stroke();//进行描边操作
    cxt.fill();//进行填充炒作
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/41d62f1187d70867c5df5c8e14e4bc89.png
clearRect()



[*]清空指定区域
clearRect(x, y, width, height)
<body>
<canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
<script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');

    cxt.strokeStyle= 'pink'
    cxt.fillStyle = 'blue';
   
    cxt.rect(10, 10, 120, 100);

    cxt.stroke();//进行描边操作
    cxt.fill();//进行填充炒作
   
    //清空矩形
    cxt.clearRect(20, 20, 100, 80);
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/c109c52e326767b056333d71f23555f0.png


[*]也可以使用clearRect来清空当前矩形
const cnv = document.querySelector('#c');
const cxt = cnv.getContext('2d');
cxt.clearRect(0, 0, cnv.width, cnv.height)
多边形



[*]Canvas 要画多边形,需要使用 moveTo() 、 lineTo() 和 closePath()

[*]需要真正闭合,使用 closePath() 方法。不要本技艺动去连接2点

三角形

<body>
<canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.lineWidth=5;//线条粗细
    ctx.moveTo(10,10);
    ctx.lineTo(100,100);
    ctx.lineTo(300,100);
    ctx.closePath();//闭合路径
    ctx.stroke();//绘制路径
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/c65ed3de40dcfa5da15872d83037017c.png
arc圆

arc(x, y, r, sAngle, eAngle,counterclockwise)


[*]x 和 y: 圆心坐标
[*]r: 半径
[*]sAngle: 开始角度
[*]eAngle: 结束角度
[*]counterclockwise: 绘制方向(true: 逆时针; false: 顺时针),默认 false
[*]绘制圆形之前,必须先调用 beginPath() 方法!!! 在绘制完成之后,还需要调用 closePath() 方法!!!
[*]大佬的图也通俗易懂
<body>
<canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(100,100,50,0,Math.PI * 2);
    ctx.stroke();
    ctx.closePath();
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/ad89499867338c85ae033111fc26407d.png
https://i-blog.csdnimg.cn/blog_migrate/01d20217fe68056c416a652868aa0f73.png


[*] 在实际开发中,为了让本身大概别的开发者更轻易看懂弧度的数值,1°应该写成 Math.PI / 180。(说的很好)

[*]100°: 100 * Math.PI / 180
[*]110°: 110 * Math.PI / 180
[*]241°: 241 * Math.PI / 180

[*] 半圆

[*]结束角度为180度就是半圆了

<body>
<canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(100,100,50,0,Math.PI );
    ctx.closePath();
    ctx.stroke();
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/6d8c61c2562233e9fccae9d37b348712.png
弧线



[*]调用arc()方法不调用closePath()方法所画出的图像就是一条弧线
[*]可用arc()大概arcTo()绘制弧线
[*]arcTo语法

[*]arcTo() 方法利用 开始点、控制点和结束点形成的夹角,绘制一段与夹角的两边相切并且半径为 radius 的圆弧。

arcTo(cx, cy, x2, y2, radius)
cx: 两切线交点的横坐标
cy: 两切线交点的纵坐标
x2: 结束点的横坐标
y2: 结束点的纵坐标
radius: 半径


[*] 其中,(cx, cy) 也叫控制点,(x2, y2) 也叫结束点。
[*] 是不是有点奇怪,为什么没有 x1 和 y1 ?

[*](x1, y1)是开始点,通常是由moveTo()大概lineTo()` 提供。

[*] 绘制30度的弧线
<body>
<canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(100, 100, 50, 0, 30 * Math.PI / 180, false);
    ctx.stroke();
</script>
</body>


[*]下面用arcTo方法绘制的不知道多少度,可以用数学算算
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
const cnv = document.getElementById('c')
const cxt = cnv.getContext('2d')

cxt.moveTo(40, 40)
cxt.arcTo(120, 40, 120, 120, 80)

cxt.stroke()
</script>


[*]开始点即为(40,40)
https://i-blog.csdnimg.cn/blog_migrate/7f558be7198fc705dcce0b4782022796.png
样式设置

stroke(描边)



[*]绘制描边线条
lineWidth(设置线条宽度)



[*]lineWidth = 值 + 单位
[*]设置绘制的线条宽度,默认单位为px,默认值为1
strokeStyle(描边颜色)



[*]strokeStyle = 颜色值
lineCap(设置线帽)



[*] lineCap = 值
[*] butt: 默认值,无线帽
[*] square: 方形线帽
[*] round: 圆形线帽
lineJoin(拐角样式)



[*]lineJoin = 值


[*]miter: 默认值,尖角
[*]round: 圆角
[*]bevel: 斜角
https://i-blog.csdnimg.cn/blog_migrate/eff695061f1e1d3e879480b61ad5739b.png
setLineDash(设置描边虚线)



[*]setLineDash([])传入数组,且元素是数值型
[*]只传1个值代表空缺值(单位为px)
[*]有2个值代表线条值,空缺值,
[*]有3个以上的值线条值,空缺值,线条值依次轮的去

<body>
<canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    //基础样式
    ctx.strokeStyle = 'blue';
    ctx.lineWidth = 10;

    ctx.moveTo(10, 10);
    ctx.lineTo(290, 10);

    //设置空白值为10(也就是间隔10px)
    ctx.setLineDash()
    ctx.stroke();

    ctx.beginPath();
    //设置线条长度为10px,空白值为5px
    ctx.setLineDash()
    ctx.moveTo(10, 40);
    ctx.lineTo(290, 40);
    ctx.stroke();

    ctx.beginPath();
    //设置线条长度为10px,空白值为5px,线条长度为20px,空白值为30px,线条长度为40px,空白值为50px
    ctx.setLineDash();
    ctx.moveTo(10, 70);
    ctx.lineTo(290, 70);
    ctx.stroke();
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/75b38062247fb5c1d35d9ccc45efa34c.png
fill(填充)



[*]使用 fill() 可以填充图形
[*]可以使用 fillStyle 设置填充颜色,默认是黑色。
<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
const cnv = document.getElementById('c')
const cxt = cnv.getContext('2d')

cxt.fillStyle = 'pink'

cxt.rect(50, 50, 200, 100)

cxt.fill()
</script>
https://i-blog.csdnimg.cn/blog_migrate/e5d8d85db125a0aba480cde65b382860.png
非零环绕填充



[*] 如果需要判定某一个区域是否需要填充颜色. 就从该区域中随机的选取一个点。从这个点拉一条直线出来, 肯定要拉到图形的外貌. 此时以该点为圆心。看穿过拉出的直线的线段. 如果是顺时针方向就记为 +1, 如果是 逆时针方向,就记为 -1. 终极看求和的结果. 如果是 0 就不填充. 如果是 非零 就填充(注意黑白0,而不是负数)
[*] 代码
<body>
<canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.moveTo(100, 100)
    ctx.lineTo(300, 100)
    ctx.lineTo(300, 300)
    ctx.lineTo(100, 300)
    ctx.closePath()

    //内部的
    ctx.moveTo(150, 150)
    ctx.lineTo(150, 250)
    ctx.lineTo(250, 250)
    ctx.lineTo(250, 150)
    ctx.closePath()
    ctx.fill();
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/6fa368ec8b9e795841629b83f6658029.png


[*]大的正方形绘制的方向是顺时针,小的正发形绘制的方向是逆时针(因为没有调用beginPath())
[*]小的从内部出来一根线,自身为-1,外界为1相加为0,所以不填充,而大的从内部出来一根线,自身为1,无相交,相加为1,所以填充
https://i-blog.csdnimg.cn/blog_migrate/7e4254b0d1975e206d1747077d3a3c6c.png


[*]可以看下面图像

[*]1处:出来一条线,顺时针,没有相交,相加为1,所以填充了颜色
[*]2处:出来2条线,逆时针,相加-2,不为0,所以填充
[*]3处:出来2条线,-1+1即是0,为0,所以不填充

https://i-blog.csdnimg.cn/blog_migrate/79456e532d1c0acf7fbc09865bff01ae.png


[*]更详细可以看这个博客

[*]https://www.cnblogs.com/youthBlog/p/10019537.html
[*]https://blog.csdn.net/weixin_44823731/article/details/106008247

文本

strokeText()描边文本和设置文本样式



[*]和 CSS 设置 font 差不多,Canvas 也可以通过 font 设置样式。
cxt.font = 'font-style font-variant font-weight font-size/line-height font-family'
如果需要设置字号 font-size,需要同时设置 font-family。

cxt.font = '30px 宋体'
<body>
<canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeText("你好,世界", 10, 100);
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/60c48a7a3372092a371f1dc16c084a1b.png


[*]当然,你也可以设置描边颜色strokeStyle
<body>
<canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeStyle = 'blue';
    ctx.strokeText("你好,世界", 10, 100);
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/16f9d22a029fb37b2a710c8c86a73c66.png
fillText-填充文本和fillStyle-填充颜色

<body>
<canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeStyle = 'blue';
    // ctx.strokeText("你好,世界", 10, 100);
    ctx.fillStyle = 'red';
    ctx.fillText('你好,世界', 10, 100);
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/33104d0ea89ea663307f34ca790a37d5.png
measureText() - 获取文本信息

<body>
<canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeStyle = 'blue';
    // ctx.strokeText("你好,世界", 10, 100);
    ctx.fillStyle = 'red';
    let text = '你好,世界';
    ctx.fillText(text, 10, 100);
    console.log(ctx.measureText(text));
</script>
</body>
{
    "actualBoundingBoxAscent": 49,
    "actualBoundingBoxDescent": 7,
    "actualBoundingBoxLeft": -2,
    "actualBoundingBoxRight": 268,
    "alphabeticBaseline": 0,
    "fontBoundingBoxAscent": 52,
    "fontBoundingBoxDescent": 8,
    "hangingBaseline": 41.6,
    "ideographicBaseline": -8,
    "width": 270
}
textAlign 程度对齐方式

使用 textAlign 属性可以设置文字的程度对齐方式,一共有5个值可选


[*]start: 默认。在指定位置的横坐标开始。
[*]end: 在指定坐标的横坐标结束。
[*]left: 左对齐。
[*]right: 右对齐。
[*]center: 居中对齐。


[*]从上面的例子看,start 和 left 的结果似乎是一样的,end 和 right 也似乎是一样的。
[*]在大多数环境下,它们的确一样。但在某些国家大概某些场合(比如阿拉伯),阅读文字的风俗是 从右往左 时,start 就和 right 一样了,end 和 left 也一样。这是需要注意的地方。
https://i-blog.csdnimg.cn/blog_migrate/a4d7dc8f473b3a66e300f5e50ce9bc76.png
textBaseline 垂直对齐方式



[*] 使用 textBaseline 属性可以设置文字的垂直对齐方式。
[*] 在使用 textBaseline 前,需要自行相识 css 的文本基线。
https://i-blog.csdnimg.cn/blog_migrate/1d628d19542d4cb56966403eaebd6150.png


[*]textBaseline 可选属性:

[*]alphabetic: 默认。文本基线是普通的字母基线。
[*]top: 文本基线是 em 方框的顶端。
[*]bottom: 文本基线是 em 方框的底端。
[*]middle: 文本基线是 em 方框的正中。
[*]hanging: 文本基线是悬挂基线。

https://i-blog.csdnimg.cn/blog_migrate/9ac01f2b8acadc7e70c717fdf0557e27.png
drawImage-渲染图片



[*]渲染图片的方式有2中,一种是在JS里加载图片再渲染,另一种是把DOM里的图片拿到 canvas 里渲染。
drawImage(image,dx,dy,dw,dh);


[*]image: 要渲染的图片对象。
[*]dx: image 的左上角在目的画布上 X 轴坐标
[*]dy: image 的左上角在目的画布上 Y 轴坐标。
[*]dw 用来界说图片的宽度。(不填则默认图片宽度)
[*]dh 界说图片的高度。(不填则默认图片高度)
js方式



[*]在 JS 里加载图片并渲染,有以下几个步调:

[*]创建 Image 对象
[*]引入图片
[*]等待图片加载完成(必须)
[*]使用 drawImage() 方法渲染图片
<body>
<canvas id="canvas" width="800" height="500" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = new Image();
    image.src = 'https://s2.loli.net/2024/03/08/FmvSfs5TeZh4Bcq.png';
    image.onload = () => {
      //等待图片加载完成
      ctx.drawImage(image,30,30)
    }
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/18020c926d8512423e13cf050b6ede52.png
DOM方式

<body>
<img src="https://s2.loli.net/2024/03/08/FmvSfs5TeZh4Bcq.png" id="cimg"/>
<canvas id="canvas" width="800" height="500" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const cimgDOM = document.getElementById('cimg');
    ctx.drawImage(cimgDOM,30,30)
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/306ee5ceb5708c2f33827e157d2f6777.png
设置图片宽高

drawImage(image, dx, dy, dw, dh)
image、 dx、 dy 的用法和前面一样。
dw 用来界说图片的宽度,dh 界说图片的高度。
截取图片



[*]又多了参数…
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)


[*]image: 图片对象
[*]dx: 开始截取的横坐标
[*]dy: 开始截取的纵坐标
[*]dw: 截取的宽度
[*]dh: 截取的高度
[*]sx: 图片左上角的横坐标位置
[*]sy: 图片左上角的纵坐标位置
[*]sw: 图片宽度
[*]sh: 图片高度
<body>
<canvas id="canvas" width="400" height="400" style="border: 1px solid red;"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = new Image();
    image.src = 'https://s2.loli.net/2024/03/08/FmvSfs5TeZh4Bcq.png';
    image.onload = () => {
      //从图像的 (10,10) 位置开始剪切,剪切的大小为 120x300,然后在画布的 (20,30) 位置放置图像,缩放图像的大小为 100x200。
      ctx.drawImage(image, 10,10,120,300,20,30,100,200)
    }
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/2cf489ea5684eec0380d12ca2732f4a0.png
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const image = document.getElementById("source");

image.addEventListener("load", (e) => {
ctx.drawImage(image, 33, 71, 104, 124, 21, 20, 87, 104);
});
https://i-blog.csdnimg.cn/blog_migrate/5c5b15f259c201bac0800597a12ae003.png
使用html2canvas



[*]很多环境下我们需要动态生成分享图片,很多环境下我们使用的是这个html2canvas的库
图片为空缺



[*] 空缺大部分环境是下面几种缘故原由

[*]跨域

[*]解决,后端解决

[*]使用的是网络图片

[*]解决办法看下面

[*]图片未加载完成绩调用了方法

[*]解决,等待图片加载完成后调用

[*]滚动条的一些问题啥的

[*]解决:略


[*] 一般环境下,如果是本地引入的图片,不依赖于网络,是可以正常加载的
[*] 但是大部分的时候,我们使用的图片都是网络图片,也就是http大概https开头的图片,会出现图片为空缺的环境
[*] 也就是将proxy设置为和图片一样的地址
终极解决-后端设置答应跨域



[*] 后端开启跨域,然后前端html2canvas配置参数中的useCORS设置为true,大概你可以试试看html2canvas配置项的proxy功能

[*] 如果是需要使用html2canvas的话,必须要后端设置答应跨域
[*] 下面代码图床设置为了跨域,所以canvas渲染没问题

<body>
<div id="main" style="width: 500px;height: 500px;display: flex;border: 1px solid red;">
    <img style="width: 90%;height: 90%;" src="https://oss.dreamlove.top/i/2024/03/09/hg7kn6.jpg"/>
    <div style="font-size: 20px;">大家好,我是文字</div>
</div>
<button id="clickme">点击我</button>
<script type="module">
    import html2canvas from 'https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.esm.min.js';
    document.getElementById('clickme').addEventListener('click', () => {
      html2canvas(document.querySelector('#main'),{
      useCORS: true // 【重要】开启跨域配置
      }).then(function (canvas) {
      document.body.append(canvas)
      });
    })
</script>
</body>
练习



[*]学习文章

[*]https://cloud.tencent.com/developer/article/1356175


<body>
<div id="wrapper"
    style="position: relative;width: 600px;height: 500px;background-color: red;background-image: url('./image/bg.jpg');">
    <span id="time"
      style="color: blue;position: absolute;bottom: 0;font-size: 30px;left: 50%;transform: translateX(-50%);"></span>
</div>
<button id="btnDown">下载</button>
<script type="module">
    import html2canvas from "https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.esm.min.js";
    function dataURLtoBlob(dataurl) {
      let arr = dataurl.split(','),
      mime = arr.match(/:(.*?);/),
      bstr = atob(arr),
      n = bstr.length,
      u8arr = new Uint8Array(n)
      while (n--) {
      u8arr = bstr.charCodeAt(n)
      }
      return new Blob(, { type: mime })
    }
    function downFile (url) {
      const a = document.createElement('a');
      a.style.display = 'none';
      a.download = 'xx';
      a.href = url;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      /*
      * download: HTML5新增的属性
      * url: 属性的地址必须是非跨域的地址
       */
    };
    window.onload = () => {
      const timeDOM = document.querySelector('#time');
      timeDOM.textContent = new Date().toLocaleString();
    }

    document.querySelector('#btnDown').addEventListener('click', () => {
      const shareContent = document.getElementById('wrapper');//需要截图的包裹的(原生的)DOM 对象
      const width = shareContent.offsetWidth; //获取dom 宽度
      const height = shareContent.offsetHeight; //获取dom 高度
      const canvas = document.createElement("canvas"); //创建一个canvas节点

      const scale = 1; //定义任意放大倍数 支持小数
      canvas.width = width * scale; //定义canvas 宽度 * 缩放
      canvas.height = height * scale; //定义canvas高度 *缩放
      canvas.getContext("2d").scale(scale, scale); //获取context,设置scale

      // var rect = shareContent.getBoundingClientRect();//获取元素相对于视察的偏移量
      // canvas.getContext("2d").translate(-rect.left,-rect.top);//设置context位置,值为相对于视窗的偏移量负值,让图片复位
      const opts = {
      scale: scale, // 添加的scale 参数
      canvas: canvas, //自定义 canvas
      logging: true, //日志开关
      width: width, //dom 原始宽度
      height: height, //dom 原始高度
      backgroundColor: 'transparent',
      };
      html2canvas(shareContent, opts).then((canvas) => {
      const base64 = canvas.toDataURL();
      const blob = dataURLtoBlob(base64)
      const href = window.URL.createObjectURL(blob)
      downFile(href,'test.png')
      })
    })
</script>
</body>
https://i-blog.csdnimg.cn/blog_migrate/ca66935c29e6d5f39be887f0131a1f8a.png
动态生成分享图片



[*]常见的方法使用canvas绘制全部图像,举行布局(大部分时候是小程序,似乎是因为内部哀求图片方式不同)

[*]这里推荐几个社区看的库
[*]小程序:https://github.com/Kujiale-Mobile/Painter
[*]uniapp:https://ext.dcloud.net.cn/plugin?id=13451
[*]大概直接使用微信小程序官方推出的新api名叫Snapshot(2024年3月09日-现在仅在 Skyline 渲染引擎 下支持)

[*]https://developers.weixin.qq.com/miniprogram/dev/api/skyline/Snapshot.html
[*]https://developers.weixin.qq.com/miniprogram/dev/component/snapshot.html
[*]https://mp.weixin.qq.com/s/GOzwCBpnzn51R-TBDbf2Ag

[*]大概使用微信小程序的canvas手动画

[*]https://developers.weixin.qq.com/miniprogram/dev/framework/ability/canvas.html


[*]另有的可能先写好html代码结构,后使用html2canvas举行转图片

[*]pc端,移动端最常用了

[*]以下面这幅图为例子
https://i-blog.csdnimg.cn/blog_migrate/63662a9dcc37be20700a8da2990fb050.png
微信小程序生成-使用snapshot绘制



[*]snapshot绘制需要Skyline模式下运行
[*]代码片段https://developers.weixin.qq.com/s/XOgihzmf7kPR
https://i-blog.csdnimg.cn/blog_migrate/8db0943828a585e77c33ad0319e9ba1f.gif


[*]wxml
<navigation-bar title="Weixin" back="{{false}}" color="black" background="#FFF"></navigation-bar>
<van-popup show="{{ show }}" bind:close="onClose">
<snapshot class="share" id="downloadWrapper">
    <!-- 用户基本信息 -->
    <view class="share_info">
      <image class="avatar" src="{{info.avatar}}" mode="aspectFill"></image>
      <view class="desc">
      <view class="name">{{info.name}}</view>
      <view class="text">{{info.description}}</view>
      </view>
    </view>
    <!-- 分享背景 -->
    <view class="share_bg">
      <image class="pic" src="{{info.bgURL}}" mode="aspectFill"></image>
    </view>
    <!-- 二维码和价格 -->
    <view class="share_code">
      <view class="price">{{'$' + info.price}}</view>
      <view class="code">
      <image class="pic" src="{{info.codeURL}}" mode="aspectFill"></image>
      </view>
    </view>
</snapshot>
<view style="text-align:center;">
    <van-button type="primary" bind:tap="handleDownload">点击下载</van-button>
</view>
</van-popup>
<van-button type="primary" bind:click="showPopup">点击我生成海报</van-button>


[*]index.js
const app = getApp()

Page({
data: {
    show:false,
    info:{
      name: "梦洁",//用户名称
      description: "给你推荐了一个好东西",//描述
      avatar: "https://s2.loli.net/2024/03/09/8tey3JKxCIpg4c7.png",//头像
      codeURL: "https://s2.loli.net/2024/03/09/924jbZViXRUngOh.png",//二维码
      bgURL: "https://s2.loli.net/2024/03/09/QhupvOzgwGmcY58.jpg",//背景图
      price: "29.99"
    }
},
onLoad() {
    console.log('代码片段是一种迷你、可分享的小程序或小游戏项目,可用于分享小程序和小游戏的开发经验、展示组件和 API 的使用、复现开发问题和 Bug 等。可点击以下链接查看代码片段的详细文档:')
    console.log('https://developers.weixin.qq.com/miniprogram/dev/devtools/minicode.html')
},
showPopup() {
    this.setData({ show: true });
},

onClose() {
    this.setData({ show: false });
},
//点击下载
handleDownload(){
    this.createSelectorQuery().select('#downloadWrapper').node().exec(res => {
      const node = res.node;
      //保存海报
      node.takeSnapshot({
      type:'arraybuffer',
      format:"png",
      success:(res) => {
          //不让背景透明,就简单点改了下扩展名
          const filePath = `${wx.env.USER_DATA_PATH}/生成的图片${Math.random()}.jpg`
          const fs = wx.getFileSystemManager();

          //将海报数据写入本地文件
          fs.writeFileSync(filePath,res.data,'binary')
         
          //保存到本地
          wx.saveImageToPhotosAlbum({
            filePath,
          })
      },
      error:(e) => {
          console.log(`出错了${e}`);
      }
      })
    })
}
})



[*]index.wxss

[*]样式就迁就点吧

/* page {
display: flex;
flex-direction: column;
height: 100vh;
} */

.scroll-area {
flex: 1;
overflow-y: hidden;
}

.intro {
padding: 30rpx;
text-align: center;
}

.share {
border: 1rpx solid red;
width: 100vw;
height: 60vh;
display: flex;
flex-direction: column;
}

.share_info {
display: flex;
align-items: center;
}

.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
}

.desc {
font-size: 32rpx;
margin-left: 40rpx;
flex: 1;
}

.name {
font-weight: bold;
}

.text {
color: gray;
}

.share_bg {
flex: 1;
}

.share_code {
display: flex;
align-items: center;
justify-content: space-between;
}

.code {
text-align: right;
}

.code .pic {
width: 160rpx;
height: 160rpx;
}

使用html2canvas举行转图片



[*]先写好html代码结构
[*]必须要答应跨域
[*]以react函数式组件为例
[*]主入口
import React, {useState } from "react";
import { Button } from "@mui/material";
import Share from "./component/share";
const Index = () => {
const = useState(false);
return (
    <div>
      <Button onClick={() => setOpen(true)}>点击我分享</Button>
      {/*分享组件 */}
      { open && <Share close={() => setOpen(false)}/> }
    </div>
);
};

export default Index;



[*]share.jsx组件
import React, { useRef, useState } from "react";
import { Button, Modal } from 'antd';
import html2canvas from 'html2canvas';
import "./share.less";
const Share = ({ close }) => {
const = useState({
    name: "梦洁",//用户名称
    description: "给你推荐了一个好东西",//描述
    avatar: "https://s2.loli.net/2024/03/09/8tey3JKxCIpg4c7.png",//头像
    codeURL: "https://s2.loli.net/2024/03/09/924jbZViXRUngOh.png",//二维码
    bgURL: "https://s2.loli.net/2024/03/09/QhupvOzgwGmcY58.jpg",//背景图
    price: "29.99"
});
const wrapperRef = useRef();
//点击下载
const handleDownload = () => {
    html2canvas(wrapperRef.current,{
      useCORS:true,//确保可以下载网络图片
    }).then((canvas) => {
      canvas.toBlob((data) => {
      const url = URL.createObjectURL(data);
      const ADOM = document.createElement("a");
      ADOM.href = url;
      ADOM.style.display = "none";
      ADOM.download = "";//避免在当前窗口打开
      document.body.appendChild(ADOM);
      ADOM.click();
      document.body.removeChild(ADOM);
      });

    })
}
return (
    <Modal width={"375px"} open={true} footer={null} onCancel={close}>
      <div ref={wrapperRef} className="share">
      {/* 用户基本信息 */}
      <div className="share_info">
          <img className="avatar" src={info.avatar} alt="" />
          <div className="desc">
            <div className="name">{info.name}</div>
            <div className="text">{info.description}</div>
          </div>
      </div>
      {/* 分享背景 */}
      <div className="share_bg">
          <img alt="" src={info.bgURL} />
      </div>
      {/* 二维码和价格 */}
      <div className="share_code">
          <div className="price">{ "$" + info.price }</div>
          <div className='code'>
            <img alt='' src={info.codeURL}/>
          </div>
      </div>
      </div>
      <div style={{textAlign:'center'}}>
      <Button type={'primary'} onClick={handleDownload}>点击下载</Button>
      </div>
    </Modal>
);
};

export default Share;



[*]这里为了方便截图,就用手机端举行操作了
https://i-blog.csdnimg.cn/blog_migrate/a54646305c22d4932d4ca8deb6c2fe52.gif
基础知识点



[*]如果不在 canvas 上设置宽高,那 canvas 元素的默认宽度是300px,默认高度是150px。
[*]线条的默认宽度是 1px ,默认颜色是黑色。

[*]但由于默认环境下 canvas 会将线条的中心点和像素的底部对齐,所以会导致表现结果是 2px 和非纯黑色问题。

[*]IE兼容问题

[*]暂时只有 IE 9 以上才支持 canvas 。但好消息是 IE 已经有本身的墓碑了。
[*]如需兼容 IE 7 和 8 ,可以使用 ExplorerCanvas 。但即使是使用了 ExplorerCanvas仍然会有所限制,比如无法使用 fillText() 方法等。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 前端canvas的学习和将网页生成canvas图片