阅读 888

纯前端JS实现图片合并与下载

一、需求场景

1、前端动态二维码组合

对于很多的营销活动,需要提供给用户一张可下载二维码,但是二维码要组合一些特定的业务信息,然后便于用户分享、传播,达到营销效果。这一类的图片下载有一个特点,二维码里蕴含了用户信息,所以是每个人的二维码是不一样的,而业务背景图都是一样的,而且用户下载的最终图片样式是可以确定的,这时候前端则需要自己的能力组合两张图,下载给用户。

2、水印技术

有的网站信息内容专利、防窃取的要求,为了让用户下载分享的内容不被随意对外分享,则需要将用户下载的图片上盖上一层署名信息,从而防止有人下载图片后冒充作者,侵犯作者权益。

3、还有很多场景...

二、解决方案

解决方案的前提是利用客户端的能力,纯前端来解决。作为前端开发者,对于画图来说,Canvas能力是完全足够的,2D的、3D的都是可以完成,我这里的解决方案就是利用Canvas来做的。首先要利用canvas画图能力,前提两张图要合并的相对的位置,大小需要先知道,这需要你先跟视觉同学沟通好,实际涉及到的Canvas的api有如下这三个getContextdrawImagetoDataURL,下面来认识一下这三个API。

1、getContext方法

var ctx = canvas.getContext(contextType, contextAttributes);复制代码
HTMLCanvasElement.getContext() 方法会返回当前canvas的上下文,注意是HTMLCanvasElement元素的方法,可以是页面上的canvas标签,也可以是javascript的createElement方法创建的元素。
看一下getContext方法的参数,contextType是想要在画布上绘制的类型,可以选的值有2d,webgl,webgl2等。

参数值
含义
2d创建 CanvasRenderingContext2D 二维渲染上下文,像图片这种二维空间的选这个类型。
webgl/experimental-webgl创建 WebGLRenderingContext 三维渲染上下文对象,适用于三维动画制作开发。
webgl2/experimental-webgl2创建一个 WebGL2RenderingContext 三维渲染上下文对象,webgl的升级版本。
bitmaprenderer将创建将canvas内容替换为指定ImageBitmap功能的ImageBitmapRenderingContext,canvas与位图的生成,笔者也没接触过。

2、drawImage方法

drawImage方法是上面getContext获取的上下文里的方法,但是它只在contextType为2d的时候才可以被调用,也就是只能在二维图片的时候使用,可以根据其调用入参控制进行画图、组合图、剪切图。

void ctx.drawImage(image, x, y);复制代码

在画布上将image图像定位到x坐标长度与y坐标长度,画上去的图片大小就是图片本身的大小,不能控制。

void ctx.drawImage(image, x, y, width, height);复制代码

在画布上将image图像定位到x坐标长度与y坐标长度,并且指定image图像宽与高。

void ctx.drawImage(image, x, y, width, height, dx, dy, dWidth, dHeight);复制代码

在画布上将image图像定位到x坐标长度与y坐标长度,并且指定image图像宽与高,然后截取出来,放在(dx,dy)的坐标处,并且指定宽高,感觉可以很快做个文字放大镜。


3、toDataURL方法

canvas.toDataURL(type, encoderOptions);复制代码

toDataURL方法也是HTMLCanvasElement元素提供的方法,当调用HTMLCanvasElement.toDataURL() 方法时,会返回一个包含图片展示的data URL,像这样的格式data:[<mediatype>][;base64],<data>

该方法可以接受两个参数。

type: 生成dataURL类型,默认是image/png的的类型,如果需要生成jpg或者其他类型传入即可。

encoderOptions:可选参数,生成的dataURL质量参数,默认是0.92,可以传入0-1之间的小数,越大质量越高,生成的串信息也越多,越长。


三、实现流程

1、生成画布

先生成一块canvas画布,并且把第一张图,一般是背景图画进画布里。这里要注意,drawImage方法的第一个参数是HTMLImageElement元素,你可以通过document.querySelector获取页面上img标签元素,也可以通过createElement('img')去生成,取决于你的图是否在页面上展示着。

// html
<img id="img" />
// javascript
class Canvas {
  // 创建canvas
  constructor (config = {}) {
    this.canvas = document.createElement('canvas');
    this.canvas.width = config.bgWidth;
    this.canvas.height = config.bgHeight;
    this.ctx = this.canvas.getContext('2d');
  }
  // 画图到画布上
  async run(config) {
    // images是HTMLImageElement元素,而非链接地址
    const image = await getImage(config.src);
    this.ctx.drawImage(image, config.x, config.y, config.width, config.height);
  }
  // 验证使用,将生产的图呈现到页面上
  print() {
    document.querySelector('#img').setAttribute('src', this.canvas.toDataURL());
  }
}
// 调用
const mycanvas = new Canvas({
   bgWidth: 500,
   bgHeight: 600
})
mycanvas.run({
  src: 'https://n.sinaimg.cn/ent/transform/460/w630h630/20180824/Zaob-hicsiaw3749625.jpg',
  x: 0,
  y: 0,
  width: 250,
  height: 600
}).then(() => {
  // 画到页面上
  mycanvas.print()
})复制代码

2、组装

在同一个ctx上进行两次将两张图进行合并drawImage,进行合并完成。

// html<img id="img" />
// javascript
// canvas合成操作
class Canvas {  
  ...  
  async run2(img1, img2) {    
    const images = [img1, img2];
    const imgSrcs = images.map(({ src }) => getImage(src));    
    const imgEles = await Promise.all(imgSrcs);    
    imgEles.map((ele, i) => {      
       if (ele) {        
          const { x = 0, y = 0, width = 0, height = 0 } = images[i];        
          this.ctx.drawImage(ele, x, y, width, height);
      }
    });
  }
  ...
}
// 调用
const mycanvas = new Canvas({  
  bgWidth: 500,
  bgHeight: 600
})
mycanvas.run({
  src: 'https://n.sinaimg.cn/ent/transform/460/w630h630/20180824/Zaob-hicsiaw3749625.jpg',
  x: 0,
  y: 0,
  width: 250,
  height: 600
}, {
  src: 'https://n.sinaimg.cn/ent/transform/460/w630h630/20180824/Zaob-hicsiaw3749625.jpg',
  x: 250,
  y: 0,
  width: 250,
  height: 600
}).then(() => {
  // 画到页面上
  mycanvas.print()
})复制代码

3、调用结果如下

4、实现下载方法

class Canvas {
  ...
  download() {
    // 文件流式下载
    this.canvas.toBlob(blob => {
      const a = document.createElement('a');
      a.download = 'image.png';
      a.style.display = 'none';
      a.href = URL.createObjectURL(blob);
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
    });
  }
  ...
}复制代码

四、发布至NPM,开源共享

目前我已经将上述组合图片的功能做成了开源package,上传至npm源上,如果有有需要的可以使用以下命令直接使用。

npm包地址
https://www.npmjs.com/package/composite-image

issue地址

https://github.com/FantasyGao/composite-image/issues


// 直接安装使用npm/cnpm i --save composite-image
// 也可以在html文件直接引入
<script src='node_module/composite-image/composite-image.js'></script>复制代码


欢迎各位使用与提一些建议,感谢。


招聘

前端工程师:

  • 2 年以上工作经验,本科及以上学历。

  • 具备良好的 HTML/CSS/JS 前端基础,有 react / vue 等主流框架开发经验,并深入理解其原理,熟悉 webpack,Rollup打包工具 。

  • 对互联网产品和 Web 技术有浓厚兴趣,有强大的自我驱动能力、学习能力和强烈的进取心。

  • 计算机网络基础知识具备良好基础。

  • (加分)有服务端的开发经验。

  • (加分)需熟练掌握移动端 H5 / hybrid 开发;

  • (加分)对移动前端性能优化有充足的实践和方法;

  • (加分)熟悉小程序开发经历。

  • (加分)熟悉 node.js 异步编程,了解nodejs基本运行原理。

  • (加分)有金融基础知识,了解支付领域相关技术更佳。


JAVA工程师(金融领域)


  • 参与研发蚂蚁新型金融产品,与现有数字金融各板块整合,打造更加灵活、普惠的支付生活体验;

  • 参与相关应用、功能模块的设计和研发实现;

  • 能够用创新的方式,解决上述领域的技术或业务难题。

  • 具备3年以上大中型互联网公司分布式系统的研发经验;

  • 有前端能技能更佳;

  • 思路清晰敏捷,沟通协调能力及决断能力强。

  • 有创业精神,能在面临较多不确定性和变化的环境中,始终保持工作激情和责任心。


实习生

即将毕业的应届生们,没有实际要求,只要你已经具备一定的计算机领域知识,有一定实力,就可以加我微信,把你的简历发给我,帮你评估,如果合适就会帮你投递。

以上岗位,P6-P8均有职位,base地点可以是杭州也可以是北京,欢迎加我好友投递简历哦~