分享海报核心功能
该功能封装成一个hook,返回一个画图功能的函数,函数实现的思路
- 接收参数
- 拿到参数并绘制到
canvas上
- 全部绘制完抛出最终图
定义接收的参数
width 画布的宽度
height 画布的高度
dpr 绘画时的尺寸放大比例
nodes 要画的内容节点列表
onFinished 全部画完后执行的函数
处理绘画的逻辑(此处只写函数体的内容)
- 得到画布,定义画布的宽高(乘以放大比例),也是最终成图的宽高
const canvas: HTMLCanvasElement = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.height = height * dpr;
canvas.width = width * dpr;
- 定义绘制节点的函数,该函数接收一个参数
nodes下标
const canvasRadiusImg = (ctx, img, x, y, w, h, r = 0) => {
if (r !== 0) {
ctx.save();
ctx.roundRect(x, y, w, h, r);
ctx.clip();
ctx.drawImage(img, x, y, w, h);
ctx.restore();
} else {
ctx.drawImage(img, x, y, w, h);
}
};
function fishihed(cb) {
idx++;
if (idx >= nodes.length) {
if (typeof onFinished === 'function') {
onFinished(canvas.toDataURL());
}
} else {
cb(idx);
}
}
function drawNode(i) {
const {
type,
value,
dx,
dy,
dw,
dh,
dr,
fontSize,
fontFamily = 'sans-serif',
textBaseline,
fillStyle,
fontType = 'fill',
fontWeight = 'normal',
strokeStyle,
} = nodes[i] || {};
if (type === 2) {
const img = new Image();
img.setAttribute('src', value);
img.setAttribute('crossOrigin', 'anonymous');
if (img.complete || img.width) {
canvasRadiusImg(ctx, img, dx * dpr, dy * dpr, dw * dpr, dh * dpr, dr * dpr);
fishihed(drawNode);
} else {
img.onload = () => {
canvasRadiusImg(ctx, img, dx * dpr, dy * dpr, dw * dpr, dh * dpr, dr * dpr);
fishihed(drawNode);
};
}
img.onerror = (err) => {
console.error(err);
fishihed(drawNode);
};
} else if (type === 1) {
ctx.beginPath();
ctx.font = `${fontWeight} ${fontSize * dpr}px ${fontFamily}`;
ctx.textBaseline = textBaseline as any;
if (fontType === 'fill') {
ctx.fillStyle = fillStyle;
ctx.fillText(value, dx * dpr, dy * dpr, dw * dpr);
} else {
ctx.strokeStyle = strokeStyle;
ctx.strokeText(value, dx * dpr, dy * dpr, dw * dpr);
}
ctx.closePath();
fishihed(drawNode);
}
}
drawNode(idx);
demo
import { useDrawNodes } from 'hook文件地址';
import { useState } from 'react';
const src3 = '图片地址';
export default () => {
const [url, setUrl] = useState(null);
const [onDrawed] = useDrawNodes();
const onClick = () => {
onDrawed({
width: 292,
height: 175,
nodes: [
{
type: 2,
value: src3,
order: 1,
dx: 0,
dy: 0,
dw: 292,
dh: 175,
dr: 20,
},
{
type: 1,
value: 'hello word!',
order: 2,
dx: 18,
dy: 30,
dw: 126,
dh: 0,
dr: 0,
fontSize: 24,
textBaseline: 'center',
fontType: 'fill',
fillStyle: 'blue',
},
],
onFinished: (u) => {
setUrl(u);
},
});
};
return (
<div>
<img style={{ width: '300px' }} src={src3} />
{url && <img style={{ width: '300px' }} src={url} />}
<div onClick={onClick}>
画图
</div>
</div>
);
};
总结
- 该方法绘制阶段使用了递归的思想,其实和链表也接近,只是数据定义时用了数组的结构
- 如果偶尔出现图片空白的情况,可以在图片url上拼接一个时间戳解决
注意点