背景
WARNING
前几天,公司项目中有这么一个需求,说是上传图片给图片加水印的功能,我说那还不简单,因为公司图片存储采用的是七牛云,当然七牛云后台是可以设置图片的水印;
但是我一看需求,好家伙果真不简单,原来不是几个字,而是多个元素叠加在一起的;水印的数据也是动态的,而七牛云后台只能设置一些 静态属性或者图片地址,如果url地址
加一些参数的情况,效果也是不可观的
而通过url
地址拼接参数的情况:/image/<encodedkodocheme>
: 水印的源路径,目前支持 kodo 资源。kodo 资源可由 kodo://<bucketname>/<key>
表示(此时 bucketname 需要与输入源在同一区域),均需要经过 urlsafe_base64_encode。
注意:更换图片水印时,建议更换图片的文件名。
这样的情况目前只支持 kodo
的图片资源,如果使用这样的参数,那么我在上传前就要先上传动态数据的水盈图片,之后在上传要上传的图片;
虽然这样是可以做到,但是对于七牛云的存储容量和流量来讲,消耗是很大的; 所以这样的实现方式是不可取的;
水印效果图:
传统的canvas
也可以采用传统的 canvas 实现,但是将上传的问图片也是可以通过下面的方式转为url
,然后在通过生成canvas
画图功能将上传的图片文件生成 canvas。然后在通过文字的定位定位在图片的左下角;
但是这样的方式就会大量计算文本x, y
在 canvas 中的位置;因为数据是动态的,所以理论上是可以实现的,但是要考虑多行文本的问题; 所以这里不建议采用该方式;
使用 html2canvas
将html
元素组合
大致的思路就是:
- 先将要生成
canvas
的容器在页面可视区域不可见,这里就是将它定位出去的; - 然后上传前,现将图片文件生成地址,赋值给
new Image()
, 在去监听加载完成; 之后就是将定位出去的元素样式化; - 通过
html2canvas
将跟元素生成带水印的canvas
, 接下来就是对canvas
转为文件,从而实现水印图片的上传;
首先,在上传文件前先将图片文件转为对象地址,然后兼容图片的加载完成。
将之前定位出去的元素的图片赋值,设置跟元素的宽度和高度为图片的宽度和高度;
然后在通过html2canvas
插件将跟元素生成canvas
,在将canvas
转化为blob
, 接着new File
将blob
转成文件;
下面就是大概的思路
this.getDom("#file-input").addEventListener("change", function (e) {
// 这里制考虑单个图片上传的情况
const file = e.target.files[0];
const tempImg = new Image();
tempImg.src = URL.createObjectURL(file);
tempImg.addEventListener("load", function () {
// 地址类似于:src="blob:http://localhost:63342/4302cb32-7eba-4a89-bd4a-66649bce781e"
// 获取元素的跟节点以及渲染的图片
const node = _this.getDom("#node");
const imgDom = _this.getDom("img");
// 设置图片的路径,以及跟元素的宽度和高度
imgDom.setAttribute("src", tempImg.src);
node.style.width = tempImg.width + "px";
node.style.height = tempImg.height + "px";
// 通过 html2canvas 将渲染的元素专程canvas
html2canvas &&
html2canvas(node).then(function (canvas) {
// 在将canvas转成文件, 实现文件的上传
canvas.toBlob(blob => {
// 生成的文件,这个就是你要上传的文件
const newFile = new File([blob], file.name, { type: file.type });
console.log(newFile);
// 预览。将生成水印的canvas在生成图片url
let imgSrc = canvas.toDataURL("image/jpeg", 1);
console.log(imgSrc);
_this.getDom(
".temp-container"
).innerHTML = `<img class="show" style="width: ${tempImg.width}px; height: ${tempImg.height}px" src="${imgSrc}" alt="" />`;
});
});
});
});
svg
代码片段
还有一种方式,就是将svg
代码片段,而这代码片段就是左下角的水印元素,将它生成 canvas,再将上传图片生成 canvas;然后俩张canvas
合并;
// svg 片段
let data = `
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<foreignObject width="100%" height="100%">
<div xmlns="http://www.w3.org/1999/xhtml">
<div style="color: red">这里就是你渲染的元素</div>
</div>
</foreignObject>
</svg>
`;
// 将svg生成 image
let imgSvg = new Image();
let blob = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
imgSvg.src = URL.createObjectURL(blob);
// 上传图片和水印合并
function img2Canvas(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
imgSvg.onload = function () {
ctx.drawImage(imgSvg, 20, canvas.height - 100 - 20);
// 摧毁刚刚生产的url
URL.revokeObjectURL(imgSvg.src);
};
imgSvg.src = URL.createObjectURL(blob);
document.body.append(canvas);
return canvas;
}
this.getDom("#file-input2").addEventListener("change", function (e) {
const file = e.target.files[0];
let tempImg = new Image();
tempImg.src = URL.createObjectURL(file);
tempImg.addEventListener("load", () => {
img2Canvas(tempImg).toBlob(blob => {
const newFile = new File([blob], file.name, { type: file.type });
console.log(newFile);
});
});
});