目标
- 最终可以实现二维码填充在指定图片位置,并且可以填充文字在图片中
- 学习笔记,个人记录,
- 学习掘金大佬
德育处主任
- 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>
不能通过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 坐标系 ,也就是遵循我们屏幕、报纸的阅读习惯,从上往下,从左往右。
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://juejin.cn/post/7115431586857746440
设置样式
lineWidth
:线的粗细strokeStyle
线的颜色lineCap
:线帽
<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>
新开路径
- 我说怎么画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>
- 在设置
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>
- 大概这么理解,直接抄他的,嘻嘻
填充矩形
- 你可以理解为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>
- 同时使用
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>
使用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>
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>
- 也可以使用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>
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>
-
在实际开发中,为了让自己或者别的开发者更容易看懂弧度的数值,1°应该写成
Math.PI / 180
。(说的很好)- 100°:
100 * Math.PI / 180
- 110°:
110 * Math.PI / 180
- 241°:
241 * Math.PI / 180
- 100°:
-
半圆
- 结束角度为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>
弧线
- 调用
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()` 提供。
- (x1, y1)
-
绘制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)
样式设置
stroke(描边)
- 绘制描边线条
lineWidth(设置线条宽度)
- lineWidth = 值 + 单位
- 设置绘制的线条宽度,默认单位为px,默认值为1
strokeStyle(描边颜色)
- strokeStyle = 颜色值
lineCap(设置线帽)
-
lineCap = 值
-
butt
: 默认值,无线帽 -
square
: 方形线帽 -
round
: 圆形线帽
lineJoin(拐角样式)
- lineJoin = 值
miter
: 默认值,尖角round
: 圆角bevel
: 斜角
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([10])
ctx.stroke();
ctx.beginPath();
//设置线条长度为10px,空白值为5px
ctx.setLineDash([10, 5])
ctx.moveTo(10, 40);
ctx.lineTo(290, 40);
ctx.stroke();
ctx.beginPath();
//设置线条长度为10px,空白值为5px,线条长度为20px,空白值为30px,线条长度为40px,空白值为50px
ctx.setLineDash([10, 5, 20, 30, 40, 50]);
ctx.moveTo(10, 70);
ctx.lineTo(290, 70);
ctx.stroke();
</script>
</body>
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>
非零环绕填充
-
如果需要判断某一个区域是否需要填充颜色. 就从该区域中随机的选取一个点。从这个点拉一条直线出来, 一定要拉到图形的外面. 此时以该点为圆心。看穿过拉出的直线的线段. 如果是顺时针方向就记为 +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>
- 大的正方形绘制的方向是顺时针,小的正发形绘制的方向是逆时针(因为没有调用beginPath())
- 小的从内部出来一根线,自身为-1,外界为1相加为0,所以不填充,而大的从内部出来一根线,自身为1,无相交,相加为1,所以填充
- 可以看下面图像
- 1处:出来一条线,顺时针,没有相交,相加为1,所以填充了颜色
- 2处:出来2条线,逆时针,相加-2,不为0,所以填充
- 3处:出来2条线,-1+1等于0,为0,所以不填充
- 更详细可以看这个博客
- 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>
- 当然,你也可以设置描边颜色
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>
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>
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
也一样。这是需要注意的地方。
textBaseline 垂直对齐方式
-
使用
textBaseline
属性可以设置文字的垂直对齐方式。 -
在使用
textBaseline
前,需要自行了解css
的文本基线。
textBaseline
可选属性:alphabetic
: 默认。文本基线是普通的字母基线。top
: 文本基线是em
方框的顶端。bottom
: 文本基线是em
方框的底端。middle
: 文本基线是em
方框的正中。hanging
: 文本基线是悬挂基线。
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>
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>
设置图片宽高
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>
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);
});
使用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[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { 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>
动态生成分享图片
- 常见的方法使用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端,移动端最常用了
- 以下面这幅图为例子
微信小程序生成-使用snapshot绘制
- snapshot绘制需要Skyline模式下运行
- 代码片段https://developers.weixin.qq.com/s/XOgihzmf7kPR
- 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[0].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 [open,setOpen] = 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 [info, setInfo] = 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;
- 这里为了方便截图,就用手机端进行操作了
基础知识点
- 如果不在
canvas
上设置宽高,那canvas
元素的默认宽度是300px,默认高度是150px。 - 线条的默认宽度是
1px
,默认颜色是黑色。- 但由于默认情况下
canvas
会将线条的中心点和像素的底部对齐,所以会导致显示效果是2px
和非纯黑色问题。
- 但由于默认情况下
- IE兼容问题
- 暂时只有
IE 9
以上才支持canvas
。但好消息是IE
已经有自己的墓碑了。 - 如需兼容
IE 7 和 8
,可以使用 ExplorerCanvas 。但即使是使用了ExplorerCanvas
仍然会有所限制,比如无法使用fillText()
方法等。
- 暂时只有