有段时间没写vue了,有点生疏了......
//utils/canvas.ts
const fs = uni.getFileSystemManager()
// 将Base64写入本地文件
const base64WriteFile = (filePath : string, data : string) => {
return new Promise((resolve, reject) => {
fs.writeFile({
filePath,
data,
encoding: 'base64',
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
},
});
});
};
// 参数的类型校验
type GetImgBase64Type = {
src : string;//图片地址(本地/在线地址)
canvasWidth : number;//画布宽度
filePath : string//临时路径
}
// 加载图片地址,生成base64并写入临时路径中
export const getImgBase64 = async (params : GetImgBase64Type) => {
const { src, canvasWidth, filePath } = params
try {
// 获取图片信息:地址、宽高
const imgInfo = await uni.getImageInfo({
src,
});
// 计算图片在画布中的宽度
const imageWidth = canvasWidth * 0.8;//随便定的,多少px都行
// // 根据比例计算图片在画布中的高度
const scaleFactor = Number((imageWidth / imgInfo.width).toFixed(2));
// 根据比例计算图片高度
const imageHeight = imgInfo.height * scaleFactor;
// 生成base64
const base64 : any = fs.readFileSync(imgInfo.path, 'base64')
// 写入本地
await base64WriteFile(filePath, base64)
const currentImgInfo = await uni.getImageInfo({
src: filePath,
});
return {
imageWidth,
imageHeight,
imgUrl: currentImgInfo.path
}
} catch (err) {
console.log('err', err);
}
};
type DrawRoundedRectParamsType = {
leftTop ?: boolean;
leftBottom ?: boolean;
rightTop ?: boolean;
rightBottom ?: boolean;
fillColor ?: string;
r ?: number;
};
// canvas 绘制自定义圆角矩形
export const drawRoundedRect = (
ctx : any,
x : number,
y : number,
w : number,
h : number,
params ?: DrawRoundedRectParamsType,
) => {
const {
leftTop = false,
leftBottom = false,
rightTop = false,
rightBottom = false,
fillColor = 'transparent',
r = 0,
} = params || {};
ctx.save(); // 保存当前绘图状态 防止虚线影响其他图形
ctx.beginPath();
ctx.setFillStyle(fillColor);
ctx.setStrokeStyle('transparent');
ctx.moveTo(x + r, y);
// 绘制上边线和左上角圆弧
if (leftTop) {
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
ctx.lineTo(x, y);
} else {
ctx.moveTo(x, y + r);
ctx.lineTo(x, y);
ctx.lineTo(x + r, y);
}
ctx.lineTo(x + w - r, y);
// 绘制上边线和右上角圆弧
if (rightTop) {
ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
} else {
ctx.lineTo(x + w - r, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + r);
}
ctx.lineTo(x + w, y + h - r);
// 绘制下边线和右下角圆弧
if (rightBottom) {
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);
} else {
ctx.lineTo(x + w, y + h - r);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x + w - r, y + h);
}
ctx.lineTo(x + r, y + h);
// 绘制下边线和左下角圆弧
if (leftBottom) {
ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);
} else {
ctx.lineTo(x + r, y + h);
ctx.lineTo(x, y + h);
ctx.lineTo(x, y + h - r);
}
ctx.lineTo(x, y + r);
// 绘制左边线和左上角圆弧
if (leftTop) {
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
ctx.moveTo(x + r, y);
} else {
ctx.moveTo(x, y + r);
ctx.lineTo(x, y);
ctx.lineTo(x + r, y);
}
ctx.fill();
ctx.closePath();
ctx.stroke();
ctx.restore(); // 恢复之前的绘图状态
};
type DrawTextConfigType = {
ctx : any;
fillStyle : string;//填充颜色
fontSize : number//文字大小
text : string;//在画布上输出的文本
x : number;//绘制文本的左上角x坐标位置
y : number//绘制文本的左上角y坐标位置
center ?: boolean
}
// 绘制文本
export const drawText = (config : DrawTextConfigType) => {
const { fillStyle, fontSize, x, y, text, ctx, center = false } = config
ctx.setFillStyle(fillStyle);
ctx.setFontSize(fontSize);
if (center) {
ctx.textAlign = 'center';//文字水平居中
}
ctx.fillText(text, x, y);
}
// 获取当前设备信息
export const getSystemInfo = () => {
return new Promise((resolve) => {
uni.getSystemInfo({
success(res) {
resolve(res)
},
})
})
}
//utils/index.ts
// 获取用户授权
type GetAuthorizeType = {
title ?: string;//授权弹框描述
callback ?: () => void//成功的回调
}
export const getAuthorize = (scope : string, params : GetAuthorizeType) => {
const { title = '请开启授权', callback } = params
return new Promise(() => {
uni.authorize({
scope,
success: () => {
callback?.()
},
fail: () => {
// 如果用户点了拒绝,需要弹框提示再次授权
uni.showModal({
title,
success() {
uni.openSetting();
},
});
}
})
})
}
// ./utils/index.ts
export type ImageListType = {
id : number;
name : string
desc : string
imageSrc : string
bgColor : string
pageColor : string
}
export const imageList : ImageListType[] = [
{
id: 0,
name: '那维莱特',
desc: '潮水啊,我已归来!',
imageSrc: '../../static/那维莱特.jpg',
bgColor: '#b2d4ff',
pageColor: '#d9e9ff',
},
{
id: 1,
name: '东方镜',
desc: '太阳之下,诸世皆影!',
imageSrc: '../../static/镜.jpg',
bgColor: '#ffdecd',
pageColor: '#fff3ed',
},
{
id: 2,
name: '魈',
desc: '你去吧,我会在这里等你。',
imageSrc: '../../static/魈.png',
bgColor: '#f1ddff',
pageColor: '#fbf4ff',
},
{
id: 3,
name: '琴团长',
desc: '我以此剑起誓,必将胜利献给你!',
imageSrc: '../../static/琴.jpg',
bgColor: '#e6e4ff',
pageColor: '#f7f6ff',
},
]
根据各自框架添加授权即可,比如uniapp在manifest.json下
"mp-weixin" : {
"appid" : "你的微信appid",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
/* 授权 */
"permission": {
"scope.writePhotosAlbum": {
"desc": "请授权保存到相册"
}
}
},
我的项目地址,点击跳转
将所有用到的图片转 base64 展示,参考上面工具函数中的 getImgBase64()
参考下面地址 使用canvas画布时多行文本应该怎么换行? | 微信开放社区
比如以弹框的形式多次点击生成等情况,首先要确保每个canvas-idID的实例不能重复。可以参考我上面标题1中的代码。
由于 canvas 的层级比较高,做预览的时候会遮住其他的view等标签。而且样式或拖拽等也不好处理,花费时间肯定更多一点,这个时候需要用
改写ctx.draw()为如下:
ctx.draw(
false,
setTimeout(async () => {
//在这里生成临时路径
const { tempFilePath } = await uni.canvasToTempFilePath({
canvasId: canvasId.value,
});
console.log('tempFilePath', tempFilePath);
await uni.saveImageToPhotosAlbum({
filePath: tempFilePath
})
}, 100),
);
由于绘制可能需要更长的时间,通过延时器即可解决。