阅读 97

canvas 生成水印图片

不到50行代码实现一个canvas生成水印工具函数,使用时也非常简单,直接调用即可;先贴完整代码,再详解思路:

源码地址

/**  * 给图片添加水印,并返回`base64`  * @param {object} params  * @param {string} params.img 图片地址,支持CDN和base64  * @param {string} params.title 水印标题  * @param {string=} params.desc 水印描述  * @param {"jpeg"|"png"=} params.type 生成图片的类型,默认`"jpeg"`  * @param {string=} params.color 水印颜色,默认`rgba(255, 255, 255, 0.5)`  * @param {number=} params.markSize 水印矩阵宽高(默认150)  * @param {number=} params.fontSize 水印字体大小(默认12)  * @param {number=} params.angle 水印旋转的角度(默认315则-45)  * @returns {Promise<string>}  */ function getWaterMarkImage(params) {   const canvas = document.createElement("canvas");   const context = canvas.getContext("2d");   canvas.style.cssText = "position: fixed; top: -110%; left: -110%; z-index: -10;";   document.body.appendChild(canvas);   const image = new Image();   image.crossOrigin = "Anonymous";   image.src = params.img;   function getDrawMark() {     const mark = document.createElement("canvas");     const markSize = params.markSize || 150;     const radius = markSize / 2;     mark.width = markSize;     mark.height = markSize;     const ctx = mark.getContext("2d");     const title = params.title || "";     const desc = params.desc || "";     const fontSize = params.fontSize || 12;     const angle = params.angle || 315;     ctx.textAlign = "center";     ctx.textBaseline = "middle";     ctx.font = `${fontSize}px Microsoft Yahei`;     ctx.fillStyle = params.color || "rgba(255, 255, 255, 0.5)";     // TODO: 必须要设置完字体样样式大小再获取字体高度     // const titleWidth = ctx.measureText(title).width;     // const descWidth = ctx.measureText(desc).width;     ctx.translate(radius, radius);     ctx.rotate(angle * Math.PI / 180);     ctx.translate(-radius, -radius);     if (desc) {       ctx.fillText(title, radius, radius - fontSize);       ctx.fillText(desc, radius, radius + fontSize * 0.6);     } else {       ctx.fillText(title, radius, radius);     }     return mark;   }   return new Promise(function (resolve) {     image.onload = function () {       canvas.width = image.width;       canvas.height = image.height;       context.drawImage(image, 0, 0);       context.fillStyle = context.createPattern(getDrawMark(), "repeat");       context.fillRect(0, 0, canvas.width, canvas.height);       const base64 = canvas.toDataURL(`image/${params.type || "jpeg"}`, 1);       resolve(base64);       canvas.remove();     }     image.onerror = function () {       resolve("");       canvas.remove();     }   }); } 复制代码

步骤1-处理传入的图片

生成水印原理都知道,就是在图片上层贴一层自定义的标识(牛皮癣),至于怎么个贴法就百花齐放了;所以这里先要加载传入的图片,然后用canvas绘制出来,像下面这样:

方法需要配置的参数参考说明上面,这里不重复了

function getWaterMarkImage(params) {   const canvas = document.createElement("canvas");   const context = canvas.getContext("2d");   canvas.style.cssText = "position: fixed; top: -110%; left: -110%; z-index: -10;";   document.body.appendChild(canvas);   const image = new Image();   image.crossOrigin = "Anonymous";   image.src = params.img;   function getDrawMark() {     ...// 下面分解说明   }   return new Promise(function (resolve) {     image.onload = function () {       canvas.width = image.width;       canvas.height = image.height;       context.drawImage(image, 0, 0);       context.fillStyle = context.createPattern(getDrawMark(), "repeat");       context.fillRect(0, 0, canvas.width, canvas.height);       const base64 = canvas.toDataURL(`image/${params.type || "jpeg"}`, 1);       resolve(base64);       canvas.remove();     }     image.onerror = function () {       resolve("");       canvas.remove();     }   }); } 复制代码

生成图片并绘制的时候,注意要给图片对象添加crossOrigin = "Anonymous",不然非同域的图片会生成失败!接着看context.fillStyle = context.createPattern(getDrawMark(), "repeat");这行代码,与css中的背景图功能类似,不同的在canvas中,它是贴在上面的;所以省去了计算水印并铺满图片的操作,而第一个参数则可以为canvas对象或者图片对象传入生成多个并重复铺满,所以水印的生成只需要绘制一个canvas就可以了。

步骤2-绘制自定义水印文字

因为水印平铺已经一行代码搞定,所以接下来只需要把单个水印绘制出来即可;绘制之前,先单独写一个方法并绘制到浏览器中,方便调试和查看效果,像这样:

function rotateTest() {   const canvas = document.createElement("canvas");   const ctx = canvas.getContext("2d");   const size = 200;   const radius = size / 2;   canvas.width = size;   canvas.height = size;   canvas.style.cssText = "border: 1px solid orange";   ctx.textAlign = "center";   ctx.textBaseline = "middle";   ctx.font = `20px Microsoft Yahei`;   function run() {     ctx.fillText("content", radius, radius);   }   document.body.appendChild(canvas);   run(); } rotateTest(); 复制代码

效果图:

canvas-1.png

使用fillText可以绘制文字,让文字居中显示则使用盒子的半径去作为xy坐标即可,由于水印是带有旋转角度的,接着再加个旋转效果看看:

// 省略上面代码 function run() {   ctx.rotate(30 * Math.PI / 180); // 先旋转,再设置文字,不然不生效   ctx.fillText("content", radius, radius); } 复制代码

效果图:

canvas-2.png

可以看到不是想要的效果,和我理解的rotate不一样,后面去查了一下,原来canvas的旋转是以左上角为中心点进行旋转的,和css中的transform: rotate()不一样。为了方便调试,我把字体先去掉,换成矩阵盒子来做个动态的角度变化,像这样:

let angle = 0; function run() {   ctx.clearRect(0, 0, size, size);//清屏   ctx.save();   ctx.beginPath();   ctx.fillStyle = "orange";   ctx.rotate(angle * Math.PI / 180)//设置矩形旋转   ctx.fillRect(0, 0, radius, radius);//fillrect(矩形宽度的一半,矩形高度的一半,矩形长度,矩形宽度)   ctx.restore();   angle += 10;   requestAnimationFrame(run); } 复制代码

效果图:

step1.gif

有了视觉效果,那么实现基于中心点旋转就好调试了;既然没有相关api设置旋转中心,那么就换一个思路,先将矩阵移动到cavans矩阵的中心点,然后旋转到指定的角度,再设置负数的原来偏移值,最后再绘制文字,这样逻辑上就达到了基于矩阵中心旋转的效果了,改造一下代码之后:

function run() {   ctx.clearRect(0, 0, size, size);//清屏   ctx.save();   ctx.beginPath();   ctx.fillStyle = "orange";   ctx.translate(radius, radius);//围绕矩形的中心点旋转   ctx.rotate(angle * Math.PI / 180)//设置矩形旋转   ctx.translate(-radius, -radius);   ctx.fillText("content", radius, radius);   ctx.strokeRect(0, 0, size, size);   ctx.restore();   angle++;   requestAnimationFrame(run); } 复制代码

效果图:

step2.gif

这样,就可以从外面传入旋转的角度,去自定水印的旋转位置,同时大小也可以根据图片实际大小去设置水印的尺寸和间距,是我想要的,比较完美的最终效果。

最后一步,把思路代码改写到getDrawMark函数中就大功告成了:

function getDrawMark() {   const mark = document.createElement("canvas");   const markSize = params.markSize || 150;   const radius = markSize / 2;   mark.width = markSize;   mark.height = markSize;   const ctx = mark.getContext("2d");   const title = params.title || "";   const desc = params.desc || "";   const fontSize = params.fontSize || 12;   const angle = params.angle || 315;   ctx.textAlign = "center";   ctx.textBaseline = "middle";   ctx.font = `${fontSize}px Microsoft Yahei`;   ctx.fillStyle = params.color || "rgba(255, 255, 255, 0.5)";   // TODO: 必须要设置完字体样样式大小再获取字体高度   // const titleWidth = ctx.measureText(title).width;   // const descWidth = ctx.measureText(desc).width;   ctx.translate(radius, radius);   ctx.rotate(angle * Math.PI / 180);   ctx.translate(-radius, -radius);   if (desc) {     ctx.fillText(title, radius, radius - fontSize);     ctx.fillText(desc, radius, radius + fontSize * 0.6);   } else {     ctx.fillText(title, radius, radius);   }   return mark; }


作者:黄景圣
链接:https://juejin.cn/post/7171759354520010765


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐