JS前端基于canvas给图片添加水印,并下载带有水印的图片

实现效果图

在这里插入图片描述

图片添加水印的步骤

  1. 获取图片路径,将图片转换为canvas
  2. canvas画布上绘制文字水印
  3. 预览:水印绘制完成后,将canvas转换为图片格式
  4. 下载:水印绘制完成后,将canvas下载为图片

1.获取图片路径,将图片转换为canvas

/**
 * 图片路径转成canvas
 * @param {图片url} url
 */
async function imgToCanvas(url) {
    
    
  // 创建img元素
  const img = document.createElement("img");
  img.src = url;
  img.setAttribute("crossOrigin", "anonymous"); // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  await new Promise((resolve) => (img.onload = resolve));
  // 创建canvas DOM元素,并设置其宽高和图片一样
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
  canvas.getContext("2d").drawImage(img, 0, 0);
  return canvas;
}

2.canvas画布上绘制文字水印

/**
 * 画布添加水印
 */
const drawWaterMark = ({
    
     canvas, textArray, fontFamily = "microsoft yahei", fontSize, fontcolor = "#dadbdc", rotate = 30, textAlign = "left", density = 2.0 }) => {
    
    
  const ctx = canvas.getContext("2d");
  let imgWidth = canvas.width;
  let imgHeight = canvas.height;
  ctx.font = `${
      
      fontSize}px ${
      
      fontFamily}`;
  ctx.lineWidth = 1;
  ctx.fillStyle = fontcolor;
  ctx.textAlign = textAlign;
  ctx.textBaseline = "middle";
  // //文字坐标
  const maxPx = Math.max(imgWidth, imgHeight);
  const stepPx = Math.floor(maxPx / density);
  let arrayX = [0]; //初始水印位置 canvas坐标 0 0 点
  while (arrayX[arrayX.length - 1] < maxPx / 2) {
    
    
    arrayX.push(arrayX[arrayX.length - 1] + stepPx);
  }
  arrayX.push(
    ...arrayX.slice(1, arrayX.length).map((el) => {
    
    
      return -el;
    })
  );

  for (let i = 0; i < arrayX.length; i++) {
    
    
    for (let j = 0; j < arrayX.length; j++) {
    
    
      ctx.save();
      ctx.translate(imgWidth / 2, imgHeight / 2); ///画布旋转原点 移到 图片中心
      // ctx.rotate(-Math.PI / 5);
      ctx.rotate((Math.PI / 120) * -rotate);
      if (textArray.length > 3) {
    
    
        //最多显示三行水印,也可以根据需要自定义
        textArray = textArray.slice(0, 3);
      }
      textArray.forEach((el, index) => {
    
    
        let offsetY = fontSize * index + 2;
        ctx.fillText(el, arrayX[i], arrayX[j] + offsetY);
      });
      ctx.restore();
    }
  }
};

3.水印绘制完成后,将canvas转换为图片格式

/**
 * canvas转成img
 * @param {canvas对象} canvas
 */
export function canvasToImg({
    
    canvas, maxWidth = "100%", maxHeight = "100%"}) {
    
    
  // 新建Image对象,可以理解为DOM
  var image = new Image();
  // canvas.toDataURL 返回的是一串Base64编码的URL
  // 指定格式 PNG
  image.src = canvas.toDataURL("image/png");
  image.style.maxHeight = maxHeight;
  image.style.maxWidth = maxWidth;
  return image;
}

4.水印绘制完成后,将canvas下载为图片

/**
 * 下载canves为图片
 * @param {canvas对象} canvas
 * @param {文件名} filename
 */
export function downloadCanves(canvas, filename = "下载") {
    
    
  let base64 = canvas.toDataURL("image/png");
  let blob = dataURItoBlob(base64);
  const a = document.createElement("a"); // 动态创建a标签,防止下载大文件时,用户没看到下载提示连续点击
  const url = window.URL.createObjectURL(blob);
  a.href = url;
  a.download = filename;
  a.click();
  window.URL.revokeObjectURL(url);
}

完整代码总结

1、在utils.js 封装添加水印的通用方法,将以上方法export导出

/**
 * 图片路径转成canvas
 * @param {图片url} url
 */
export async function imgToCanvas(url) {
    
    
  // 创建img元素
  const img = document.createElement("img");
  img.src = url;
  img.setAttribute("crossOrigin", "anonymous"); // 防止跨域引起的 Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
  await new Promise((resolve) => (img.onload = resolve));
  // 创建canvas DOM元素,并设置其宽高和图片一样
  const canvas = document.createElement("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
  canvas.getContext("2d").drawImage(img, 0, 0);
  return canvas;
}
/**
 * 画布添加水印
 */
export const drawWaterMark = ({
    
     canvas, textArray, fontFamily = "microsoft yahei", fontSize, fontcolor = "#dadbdc", rotate = 30, textAlign = "left", density = 2.0 }) => {
    
    
  const ctx = canvas.getContext("2d");
  let imgWidth = canvas.width;
  let imgHeight = canvas.height;
  ctx.font = `${
      
      fontSize}px ${
      
      fontFamily}`;
  ctx.lineWidth = 1;
  ctx.fillStyle = fontcolor;
  ctx.textAlign = textAlign;
  ctx.textBaseline = "middle";
  // //文字坐标
  const maxPx = Math.max(imgWidth, imgHeight);
  const stepPx = Math.floor(maxPx / density);
  let arrayX = [0]; //初始水印位置 canvas坐标 0 0 点
  while (arrayX[arrayX.length - 1] < maxPx / 2) {
    
    
    arrayX.push(arrayX[arrayX.length - 1] + stepPx);
  }
  arrayX.push(
    ...arrayX.slice(1, arrayX.length).map((el) => {
    
    
      return -el;
    })
  );

  for (let i = 0; i < arrayX.length; i++) {
    
    
    for (let j = 0; j < arrayX.length; j++) {
    
    
      ctx.save();
      ctx.translate(imgWidth / 2, imgHeight / 2); ///画布旋转原点 移到 图片中心
      // ctx.rotate(-Math.PI / 5);
      ctx.rotate((Math.PI / 120) * -rotate);
      if (textArray.length > 3) {
    
    
        //最多显示三行水印,也可以根据需要自定义
        textArray = textArray.slice(0, 3);
      }
      textArray.forEach((el, index) => {
    
    
        let offsetY = fontSize * index + 2;
        ctx.fillText(el, arrayX[i], arrayX[j] + offsetY);
      });
      ctx.restore();
    }
  }
};

/**
 * canvas转成img
 * @param {canvas对象} canvas
 */
export function canvasToImg({
    
    canvas, maxWidth = "100%", maxHeight = "100%"}) {
    
    
  // 新建Image对象,可以理解为DOM
  var image = new Image();
  // 指定格式 PNG; canvas.toDataURL 返回的是一串Base64编码的URL
  image.src = canvas.toDataURL("image/png");
  image.style.maxHeight = maxHeight;
  image.style.maxWidth = maxWidth;
  return image;
}
/**
 * 下载canves为图片
 * @param {canvas对象} canvas
 * @param {文件名} filename
 */
export function downloadCanves(canvas, filename = "下载") {
    
    
  let base64 = canvas.toDataURL("image/png");
  let blob = dataURItoBlob(base64);
  const a = document.createElement("a"); // 动态创建a标签,防止下载大文件时,用户没看到下载提示连续点击
  const url = window.URL.createObjectURL(blob);
  a.href = url;
  a.download = filename;
  a.click();
  window.URL.revokeObjectURL(url);
}

2、在页面import 导入方法并调用

<template>
  <!-- 水印图片预览 -->
    <div class="content">
      <div class="imageBox" id="imageBox"></div>
      <el-button type="primary" @click="downFile" style="margin-top: 10px; width: 100px">下 载</el-button>
    </div>
</template>

<script>
import {
    
      drawWaterMark, imgToCanvas, canvasToImg, downloadCanves } from "@/utils/fileUtils.js";
export default {
    
    
  data() {
    
    
    return {
    
    
      fileUrl:"XXXX.jpg",
      fontSize: 50,
      objmsg: {
    
    
        canvas: null, //canvas  [必传]
        textArray: ["水印图片", "2022-01-01"], // /水印文字 数组类型  最大三行  [必传]
        fontFamily: "fangsong", // 默认 microsoft yahei
        fontSize: 30, //字体大小 默认 #dadbdc
        fontcolor: "#fff", //字体颜色   默认 #dadbdc
        rotate: 30, //旋转角度   数字类型
        textAlign: "center", //水印文字居中方式:left center right  默认 left
        density: 3.0, // 稠密度
      },
    };
  },
  mounted() {
    
    
    this.drawWaterMarkFn();// 添加水印
  },
  methods: {
    
    
    // 图片添加水印
    async drawWaterMarkFn() {
    
    
      // 1.图片路径转成canvas
      const tempCanvas = await imgToCanvas(this.fileUrl);
      this.objmsg.canvas = tempCanvas 
      // 2.canvas添加水印
      drawWaterMark(this.objmsg);
      // 3.canvas转成img
      const waterImage = await canvasToImg({
    
    
        canvas: tempCanvas,
        maxWidth: "650px", // 最大宽度
        maxHeight: "650px", // 最大高度
      });
      // 查看效果
      var Box = document.getElementById("imageBox");
      Box.appendChild(waterImage);
    },
    // 下载添加水印的图片
    async downFile() {
    
    
      // 1.图片路径转成canvas
      const tempCanvas = await imgToCanvas(this.fileUrl);
      this.objmsg.canvas = tempCanvas 
      // 2.canvas添加水印
      await drawWaterMark(this.objmsg);
      // 3.下载加水印后的图片
      downloadCanves(tempCanvas,  "水印图片下载");
    },
  },
};
</script>

<style lang="scss" scoped>
.waterMakerinfo {
    
    
  .content {
    
    
    text-align: center;
    ::v-deep .imageBox {
    
    
      max-height: 650px;
      min-height: 500px;
      .img {
    
    
        width: 100%;
        height: 100%;
      }
    }
  }

  ::v-deep {
    
    
    .el-dialog__footer {
    
    
      text-align: right !important;
      position: absolute;
      width: 200px;
      top: unset;
      right: 20px;
      bottom: 0px;
      padding: 10px !important;
      padding-left: 20px !important;
      background-color: transparent !important;
      z-index: 99;
    }
  }
}
</style>

猜你喜欢

转载自blog.csdn.net/qq_37831545/article/details/128846647
今日推荐