开发三十个纯前端微信小程序小工具之图片加水印(30/7)
一个开箱即用的图片加水印工具小程序
页面截图

使用canvas来实现给图片加水印,主要对文字位置的计算需要注意,没有其他难点,单个的时候注意下计算文字与边框的距离问题,多个的话就最主要的就是判断循环结束条件 X轴: for(let x = startX; x < imageWidth + spacingX; x += spacingX) Y轴for(let y = startY; y < imageHeight + spacingY; y += spacingY)
下面直接贴一下核心代码:
xml
<canvas type="2d" id="watermarkCanvas" class="hidden-canvas"></canvas>
javascript
generateWatermark() {
if (!this.imagePaths.length) {
uni.showToast({
title: "请选择图片",
icon: "none",
});
return;
}
if (
(this.watermarkType === "text" || this.watermarkType === "text-tile") &&
!this.watermarkText
) {
uni.showToast({
title: "请输入水印文字",
icon: "none",
});
return;
}
if (
(this.watermarkType === "image" ||
this.watermarkType === "image-tile") &&
!this.watermarkImagePath
) {
uni.showToast({
title: "请选择水印图片",
icon: "none",
});
return;
}
this.resultPaths = [];
let processed = 0;
uni.showLoading({ title: "生成中..." });
const processOne = (imgPath, idx) => {
uni.getImageInfo({
src: imgPath,
success: (res) => {
const { width, height } = res;
const query = uni.createSelectorQuery().in(this);
query
.select("#watermarkCanvas")
.fields({ node: true, size: true })
.exec((canvasRes) => {
if (canvasRes[0]) {
const canvas = canvasRes[0].node;
const ctx = canvas.getContext("2d");
const dpr = uni.getSystemInfoSync().pixelRatio;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
const img = canvas.createImage();
img.onload = () => {
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0, width, height);
ctx.globalAlpha = this.opacity / 100;
if (
this.watermarkType === "text" ||
this.watermarkType === "text-tile"
) {
ctx.font = `${this.fontSize}px Arial`;
ctx.fillStyle = this.textColor;
if (this.watermarkType === "text-tile") {
this.drawTextTile(ctx, width, height);
} else {
const position = this.calculatePosition(width, height);
const textMetrics = ctx.measureText(this.watermarkText);
const textWidth = textMetrics.width;
// 如果不是左侧位置,x坐标减去文本宽度
if (
![
"top-left",
"middle-left",
"bottom-left",
"center",
].includes(this.position)
) {
position.x -= textWidth;
}
ctx.fillText(
this.watermarkText,
position.x,
position.y
);
}
uni.canvasToTempFilePath(
{
canvas: canvas,
success: (res) => {
this.$set(this.resultPaths, idx, res.tempFilePath);
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
uni.showToast({
title: "全部生成成功",
icon: "success",
});
}
},
fail: () => {
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
}
},
},
this
);
} else {
// 固定高度为watermarkFixedHeight,宽度等比缩放
const watermarkImg = canvas.createImage();
watermarkImg.onload = () => {
if (this.watermarkType === "image-tile") {
this.drawImageTileFixedHeight(
ctx,
watermarkImg,
width,
height
);
} else {
const fixedHeight = this.watermarkFixedHeight;
const scale = fixedHeight / watermarkImg.height;
const userScale = this.watermarkSize / 100;
const watermarkWidth =
watermarkImg.width * scale * userScale;
const watermarkHeight = fixedHeight * userScale;
const position = this.calculatePosition(
width,
height
);
// 仅左侧和居中不需要偏移,其他都要减去宽高
let drawX = position.x;
let drawY = position.y;
// 右上、右中、右下:drawX -= watermarkWidth,其它 drawX -= watermarkWidth / 3
if (
[
"top-right",
"middle-right",
"bottom-right",
].includes(this.position)
) {
drawX -= watermarkWidth / 1.5;
} else if (
["top-center", "center", "bottom-center"].includes(
this.position
)
) {
drawX -= watermarkWidth / 2;
} else {
drawX -= watermarkWidth / 3;
}
if (
[
"top-right",
"top-center",
"top-left",
].includes(this.position)
) {
drawY -= watermarkHeight;
} else if (
["bottom-right", "bottom-left", "bottom-center"].includes(
this.position
)){
drawY -= watermarkHeight / 1.5;
}else {
drawY -= watermarkHeight / 2;
}
ctx.drawImage(
watermarkImg,
drawX,
drawY,
watermarkWidth,
watermarkHeight
);
}
uni.canvasToTempFilePath(
{
canvas: canvas,
success: (res) => {
this.$set(
this.resultPaths,
idx,
res.tempFilePath
);
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
uni.showToast({
title: "全部生成成功",
icon: "success",
});
}
},
fail: () => {
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
}
},
},
this
);
};
watermarkImg.onerror = () => {
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
}
};
watermarkImg.src = this.watermarkImagePath;
}
};
img.onerror = () => {
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
}
};
img.src = imgPath;
} else {
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
}
}
});
},
fail: () => {
processed++;
if (processed === this.imagePaths.length) {
uni.hideLoading();
}
},
});
};
this.imagePaths.forEach((imgPath, idx) => processOne(imgPath, idx));
},
calculatePosition(imageWidth, imageHeight) {
const margin = 20;
let x, y;
switch (this.position) {
case "top-left":
x = margin;
y = margin + this.fontSize;
break;
case "top-center":
x = imageWidth / 2;
y = margin + this.fontSize;
break;
case "top-right":
x = imageWidth - margin;
y = margin + this.fontSize;
break;
case "middle-left":
x = margin;
y = imageHeight / 2;
break;
case "center":
x = imageWidth / 2;
y = imageHeight / 2;
break;
case "middle-right":
x = imageWidth - margin;
y = imageHeight / 2;
break;
case "bottom-left":
x = margin;
y = imageHeight - margin;
break;
case "bottom-center":
x = imageWidth / 2;
y = imageHeight - margin;
break;
case "bottom-right":
x = imageWidth - margin;
y = imageHeight - margin;
break;
}
return { x, y };
},
drawTextTile(ctx, imageWidth, imageHeight) {
ctx.save();
const textMetrics = ctx.measureText(this.watermarkText);
const textWidth = textMetrics.width;
const textHeight = this.fontSize;
const spacingX = this.tileSpacing;
const spacingY = this.tileSpacing;
const angle = (this.tileAngle * Math.PI) / 180;
const startX = -spacingX;
const startY = -spacingY;
for (let x = startX; x < imageWidth + spacingX; x += spacingX) {
for (let y = startY; y < imageHeight + spacingY; y += spacingY) {
ctx.save();
ctx.translate(x + textWidth / 2, y + textHeight / 2);
ctx.rotate(angle);
ctx.translate(-textWidth / 2, -textHeight / 2);
ctx.fillText(this.watermarkText, 0, textHeight);
ctx.restore();
}
}
ctx.restore();
},
drawImageTile(ctx, watermarkImg, imageWidth, imageHeight) {
ctx.save();
const watermarkWidth = (imageWidth * this.watermarkSize) / 100;
const watermarkHeight = (imageHeight * this.watermarkSize) / 100;
const spacingX = this.tileSpacing;
const spacingY = this.tileSpacing;
const angle = (this.tileAngle * Math.PI) / 180;
const startX = -spacingX;
const startY = -spacingY;
for (let x = startX; x < imageWidth + spacingX; x += spacingX) {
for (let y = startY; y < imageHeight + spacingY; y += spacingY) {
ctx.save();
ctx.translate(x + watermarkWidth / 2, y + watermarkHeight / 2);
ctx.rotate(angle);
ctx.translate(-watermarkWidth / 2, -watermarkHeight / 2);
ctx.drawImage(watermarkImg, 0, 0, watermarkWidth, watermarkHeight);
ctx.restore();
}
}
ctx.restore();
},
// 新增方法,支持图片平铺水印时固定高度
drawImageTileFixedHeight(ctx, watermarkImg, imageWidth, imageHeight) {
ctx.save();
const fixedHeight = this.watermarkFixedHeight;
const scale = fixedHeight / watermarkImg.height;
const userScale = this.watermarkSize / 100;
const watermarkWidth = watermarkImg.width * scale * userScale;
const watermarkHeight = fixedHeight * userScale;
const spacingX = this.tileSpacing;
const spacingY = this.tileSpacing;
const angle = (this.tileAngle * Math.PI) / 180;
const startX = -spacingX;
const startY = -spacingY;
for (let x = startX; x < imageWidth + spacingX; x += spacingX) {
for (let y = startY; y < imageHeight + spacingY; y += spacingY) {
ctx.save();
ctx.translate(x + watermarkWidth / 2, y + watermarkHeight / 2);
ctx.rotate(angle);
ctx.translate(-watermarkWidth / 2, -watermarkHeight / 2);
ctx.drawImage(watermarkImg, 0, 0, watermarkWidth, watermarkHeight);
ctx.restore();
}
}
ctx.restore();
},
打代码不易,希望对你有帮助
有兴趣的同学可以在线体验一下小程序【口袋工具包】