移动端js模拟截屏生成图片并下载功能的实现方案+踩坑过程

一. 项目中有需求以下:

将营业日报生成图片下载至用户手机保存html

二. 踩坑思路:

  1. 首先,由于用的是第三方的app(钉钉)内嵌webview开发,因此没法拿到截屏的api(并且须要生成的日报超出一个屏幕范围,截屏也麻烦)
  2. 因此,天然想到了使用第三方工具canvas2html,将页面中指定范围的dom转换为canvas
  3. 随后使用canvas的apitoDataUrl得到base64格式的图片数据
  4. 此时试着直接用a标签下载
`<a href="base64Url" download="name.jpg"></a>`
复制代码
  1. 实际证实该方法移动端失效,提示图片下载失败,由于移动端没法直接下载base64格式的图片(听说pc端,chrome能够直接下载,其余浏览器貌似也有兼容写法,有意者可自行验证)
  2. 如今只能先将图片传输至后端保存,随后使用服务器地址下载
  3. 为了方便后端处理,咱们这里不直接上传base64格式的数据,先将base64转换成blob,再模拟一个表单对象,将blob放进去,使用post提交给后端
  4. 拿到服务器地址后,再来尝试a标签下载
  5. 这里分两种状况,若是图片地址和项目同源,根据网上的说法,点击a标签应该能直接下载成功了,这里没有验证过
  6. 我这里由于图片存到了非同源的服务器,因此点击a标签后,没法自动下载,会转跳到默认浏览器打开图片,随后能够长按下载,这里体验就很差了
  7. 因此,最后放弃了a标签的方案,变为添加一个弹出层,展现该图片,提示用户长按下载,至此比较完美的实现了该功能

三. 实现流程:

html2canvas将页面转换为canvas -> canvas转换dataURL -> base64ToBlob(dataUrl转换为2进制文件流) -> new FormData,将blob放置入该表单对象 -> post请求发送至后端保存图片 -> 后端返回图片地址 -> 使用线上地址展现图片 -> 用户长按保存图片vue

四. 下面逐步说明:

4.1 html2canvas
// 安装
npm install html2canvas --save

// 引入
import Html2canvas from 'html2canvas';
Vue.prototype.html2canvas = Html2canvas;

// 使用
this.html2canvas(document.querySelector('#id'))
  .then((canvas) => {
    // todo...
  })
复制代码
4.2 canvas转换dataURL
let dataUrl = canvas.toDataURL('image/jpeg');
复制代码
4.3 base64ToBlob
/**
 * base64转blob
 * @param {String} code base64个数数据
 * @return {undefined}
 * @author xxx
 */
base64ToBlob (code) {
  let parts = code.split(';base64,');
  let contentType = parts[0].split(':')[1];
  let raw = window.atob(parts[1]);
  let rawLength = raw.length;
  let uInt8Array = new Uint8Array(rawLength);
  for (let i = 0; i < rawLength; ++i) {
    uInt8Array[i] = raw.charCodeAt(i);
  }
  return new window.Blob([uInt8Array], {type: contentType, name: 'file_' + new Date().getTime() + '.jpg'});
}

let blob = base64ToBlob(dataUrl);
复制代码
4.3 模拟formData并提交
// 新建formData
let formData = new FormData();
// 将blob存入
formData.append('file', blob);
复制代码

此处由于使用的是vue,因此用的axios,提交的时候要注意content-typeandroid

注意:建立请求的时候须要新建axios实例去请求,不要使用import进来的axios,否则content-type修改会不成功ios

import axios from 'axios';
// 建立新的axios实例
let instance = axios.create({});
// 设置content-type为false就好了
instance.defaults.headers.post['Content-Type'] = false;
// 这里特别注意,建立请求的时候,使用这个新建立的实例去进行请求,而不要使用原来的axios
instance({
  method: 'POST',
  data: formData
})
  .then(res => {
    // todo...
  })
复制代码
4.4 使用返回的服务器地址展现图片,让用户长按保存

这里就不用说什么了,本身建立一个遮罩层去实现就好了git

五. 优化点

  1. 由于canvas2html -> 发起请求之间须要走过的步骤比较多,并且若是转换的dom比较复杂,中间的处理事件会比较久,因此在这段时间中要作好相应的loading处理;否则用户点击后,可能会看到1s左右的空窗期,而页面什么提示也没有
  2. 生成图片的按钮要作好连续点击限制,避免用户频繁触发

以上,功能就所有实现,虽然没法实现点击直接下载,可是也在条件容许的范围内,实现比较好的用户体验了github


六. 最后说一下在ios上面碰到的状况

这个方案在android上面一直测试的比较顺利,可是在ios上面出现过一些疑问web

主要就是canvas.toDataUrl()这个api失效chrome

当时android顺利调通,在ios上测试的时候,发现js运行到canvas.toDataUrl()就中止了,没有返回值也没有什么错误提示npm

最后发现是本身添加的水印效果致使在ios执行canvas.toDataUrl()的时候无响应(canvas画出水印,转换base64,添加到父级backgroung-image的实现方式),改变了水印实现方式就正常运行了canvas

下面把整整一天的踩坑过程写下,给各位参考:

刚开始怀疑html2canvas转化出来的canvas有问题,网上看了不少提问,包括github上面html2canvas的issue,有说多是html2canvas版本问题的,有说多是canvas画出的图片过大,致使canvas.toDataUrl()在ios上运行被系统强行阻止的各类说法,通过测试,都没法解决如今的问题

虽然最后证明了不是上述的问题,但仍是将测试结果写下

1. html2canvas版本问题:

npm默认安装的是"html2canvas": "^1.0.0-alpha.12"这个alpha版本

随后我测试过最后的正式release版本,v0.4.1,证明能够正常运行,可是碰到的没法转换超出屏幕部分的dom,和转换的图片模糊的问题要花太多精力去解决,并且做者说了在旧版本有太多的bug,建议使用新的版本,因此最后放弃了旧版本的尝试

2. canvas画出的图片过大,致使canvas.toDataUrl()在ios上运行被系统强行阻止

我转换出来的图片大小在200k左右(用的'image/jpeg'的类型),没查到网上说的这个极限到底在哪里

当时用本身新建的canvas从新压缩了图片,压缩到只有1kb的时候都如法正常运行canvas.toDataUrl(),就基本排除这个问题了

相关文章
相关标签/搜索