木叶科技

木叶科技

木叶科技

菜单导航
木叶科技 > 游戏产业 > 正文

前端js保存页面为图片下载到本地的坑

作者: 橙月 更新时间: 2021年03月31日 13:59:42 游览量: 78

简述:

前端保存页面为图片,最常见的方案都是利用html2canvas来生成图片,同时利用HTML5的新属性download来点击保存到本地,

编者:前端保存页面为图片,最常见的方案都是利用html2canvas来生成图片,同时利用HTML5的新属性download来点击保存到本地,来看看这些地方前端做会有什么坑?

1. 需求 最近丁香医生的产品大佬又双叒叕搞事情,想要在 H5 中做一个医生邀请提问的功能,可以将医生的二维码名片分享出去,支持移动、PC 和微信。之前的图片是由后端生成的,并且会缓存三天,导致信息更新不及时。由前端来做就能避免这些问题。 我一听,这好说,不就是个保存图片的功能么,简单看看需求:

完善卡片信息,分享出去时候信息更加立体

编辑个人资料入口

保存图片入口

可解决医生名片缓存时间问题

长下面这样 ⬇

15fecdcf56afde26

分析下来就两点

html展示实时用户信息

点击保存将当前页面保存成图片至本地,并且不包含功能按钮

2. 方案 因为之前已经听说过有个库能将 HTML 转为 canvas,然后又听说 canvas 能转为图片,然后又听说图片能下载....(开发基本靠听说(搜索),这是废话) 那我的基本方案就是: html -> canvas -> image -> a[download]

html2canvas.js:可将 htmldom 转为 canvas 元素。传送门

canvasAPI:toDataUrl() 可将 canvas 转为 base64 格式

创建 a[download] 标签触发 click 事件实现下载

3. 采坑表演 既然方案定下来了,下面就开始踩坑表演了, 3.1. 原理 官方是这样介绍的: js将遍历加载页面的 DOM 节点,收集所有元素的信息,然后用这些信息来呈现页面。换句话说,实际上这个库并不是真的对页面进行截图,而是基于从 DOM 读取的元素及属性来一点点的绘制 canvas。 因此,它只能正确地呈现它理解的元素和属性,这意味着有许多 CSS 属性不起作用。 // v0.4.1 html2canvas(element, { onrendered: function(canvas) { // 现在你已经拿到了canvas DOM元素 } }); // v0.5.0 html2canvas(element, options).then(canvas => { // 现在你已经拿到了canvas DOM元素 }); 所以基本可以猜到整个工作流程应该是:

递归处理每个节点,记录这个节点应该怎么画。(比如div就画边框和背景,文字就画文字等等)

考虑节点的层级问题。比如很多布局相关样式属性: z-index、float、position 等的影响。

从低层级开始画到 canvas 上,逐渐向上画。层级高的覆盖层级低的(和浏览器本身的渲染流程很像)。

3.2 坑 目前官方提供的版本有很多,正式版本是v0.4.1 - 7.9.2013,最新版本是v0.5.0-beta4,那对于我们开发来说如果不是玩新特性什么的一般还是会选择正式版,结果第一个坑就掉进去爬了半天。 3.2.1 图片模糊 因为开发的时候是用 chrome 模拟器生成 canvas 后没有发现有模糊的地方,但是用 PC 代理手机请求开发资源时,发现画面的模糊感非常明显。 如图:

15fecdcf5686cca

容易想到,可能是移动端像素密度计算的问题。 设备像素比 (简称 dpr) 定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到: 设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向 知道了这个也没用,因为文档中根本没有给出能够配置像素比的地方。。 然而通过研究发现,官方文档其实还是 0.4.1 的,从 0.5.0 版本开始,其实已经偷偷摸摸支持自定义 canvas 作为配置项传入了,它会根据我们传入的 canvas 为基础开始绘制。所以我们在调用 html2canvas 的时候,可以先创建好一个尺寸合适的 canvas,再传进去。 话不多说,首先将库升级到 0.5.0,然后: /** * 根据window.devicePixelRatio获取像素比 */ function DPR() { if (window.devicePixelRatio && window.devicePixelRatio > 1) { return window.devicePixelRatio; } return 1; } /** * 将传入值转为整数 */ function parseValue(value) { return parseInt(value, 10); }; /** * 绘制canvas */ async function drawCanvas(selector) { // 获取想要转换的 DOM 节点 const dom = document.querySelector(selector); const box = window.getComputedStyle(dom); // DOM 节点计算后宽高 const width = parseValue(box.width); const height = parseValue(box.height); // 获取像素比 const scaleBy = DPR(); // 创建自定义 canvas 元素 const canvas = document.createElement('canvas'); // 设定 canvas 元素属性宽高为 DOM 节点宽高 * 像素比 canvas.width = width * scaleBy; canvas.height = height * scaleBy; // 设定 canvas css宽高为 DOM 节点宽高 canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; // 获取画笔 const context = canvas.getContext('2d'); // 将所有绘制内容放大像素比倍 context.scale(scaleBy, scaleBy); // 将自定义 canvas 作为配置项传入,开始绘制 return await html2canvas(dom, {canvas}); } 以上代码先获取设备像素比,并根据比例创建尺寸更大的 canvas。如二倍屏就是二倍,三倍屏就是三倍,八倍镜就是八倍··· 手机端截图,和html展示效果一致,基本看不出来差别。

15fecdcf5660f7e

3.2.2

前端js保存页面为图片下载到本地的坑

图片画出来怎么不见了 PC端截图:

15fecdcf5803550

可能有多种原因,排查后发现是因为 canvas 内的图片跨域了 这里有解释 总而言之,就是:可以在 canvas 中绘制跨域的图片,但此时的 canvas 处于被 「污染」 的状态,而污染状态的 canvas 使用 toDataUrl() 等 API 是会出现问题的。 所以,现在我们需要做两件事:

给 img 元素设置 crossOrigin 属性,值为 anonymous

文章链接:http://www.muyesoft.com//youxi/39933.html

文章标题:前端js保存页面为图片下载到本地的坑