最近写项目有用到 html2canvas.JS, 可以实现页面的截图功能, 但遭遇了许多的坑, 特此写一篇随笔记录一下.
在使用 html2canvas 时可能会遇到诸如只能截取可视化界面, 截图没有背景色, svg 标签无法截取等问题, 下面详细的说明一下.
一, 导入 html2canvas.JS
这个不需要多说, 可以从 GitHub 上获取: https://github.com/niklasvh/html2canvas
也可以直接导入链接: <script src="https://cdn.bootCSS.com/html2canvas/0.5.0-beta4/html2canvas.js"></script>
使用起来也非常简单, 具体的 API 可以去网上查找, 生成 PNG 图片使用 "image/png" 即可.
其中 $("#xxx") 为你想要截取的 div, 外面可以通过 jQuery 获取它, 当然 document 获取也是可以的.
- html2canvas($("#xxx"), {
- onrendered: function (canvas) {
- var url = canvas.toDataURL("image/png");
- Windows.location.href = url;
- }
- });
其它类型的图片如 jpg, 为 image/jpeg 等等, 可自行查询 API.
到这里其实简单的截图已经完成了, 如果界面稍微复杂一点的话, 可能就会出现各种坑, 下面一个一个解决.
二, svg 无法截取的问题
当我们截取一个 div 时, 如果这个 div 中存在 svg 标签, 一般情况下是截取不到的, 比如截取一个流程图, 得到的是下面这个样子:
可以看到, 流程图的线没有截取到, 也就是 svg 没有截取到, 这时的解决方法是把 svg 转换成 canvas 再进行截图即可, 直接上代码.
这里的 each 循环是循环所有的 svg 标签, 将它们全部转换为 canvas
- if (typeof html2canvas !== 'undefined') {
- // 以下是对 svg 的处理
- var nodesToRecover = [];
- var nodesToRemove = [];
- var svgElem = cloneDom.find('svg');
- svgElem.each(function (index, node) {
- var parentNode = node.parentNode;
- var svg = node.outerHTML.trim();
- var canvas = document.createElement('canvas');
- canvas.width = 650;
- canvas.height = 798;
- canvg(canvas, svg);
- if (node.style.position) {
- canvas.style.position += node.style.position;
- canvas.style.left += node.style.left;
- canvas.style.top += node.style.top;
- }
- nodesToRecover.push({
- parent: parentNode,
- child: node
- });
- parentNode.removeChild(node);
- nodesToRemove.push({
- parent: parentNode,
- child: canvas
- });
- parentNode.appendChild(canvas);
- });
- }
这里需要用到 canvg.JS, 以及它的依赖文件 rgbcolor.JS, 网上可以直接下载, 也可以直接导入.
三, 背景透明的问题
这个其实很简单, 因为它默认是透明的, html2canvas 中有一个参数 background 就可以添加背景色, 如下:
- html2canvas(cloneDom, {
- onrendered: function(canvas) {
- var url =canvas.toDataURL("image/png");
- },
- background:"#fafafa"
- });
四, 只能截取可视部分的问题
如果需要截取的 div 超出了界面, 可能会遇到截取不全的问题, 如上图, 只有一半的内容, 这是因为看不到的部分被隐藏了, 而 html2canvas 是无法截取隐藏的 dom 的.
所以此时的解决办法是使用克隆, 将需要截取的部分克隆一份放在页面底层, 再使用 html2canvas 截取这个完整的 div, 截取完成后再 remove 这部分内容即可, 完整代码如下:
- function showQRCode() {
- scrollTo(0, 0);
- // 克隆节点, 默认为 false, 即不复制方法属性, 为 true 是全部复制.
- var cloneDom = $("#d1").clone(true);
- // 设置克隆节点的 z-index 属性, 只要比被克隆的节点层级低即可.
- cloneDom.CSS({
- "background-color": "#fafafa",
- "position": "absolute",
- "top": "0px",
- "z-index": "-1",
- "height": 798,
- "width": 650
- });
- if (typeof html2canvas !== 'undefined') {
- // 以下是对 svg 的处理
- var nodesToRecover = [];
- var nodesToRemove = [];
- var svgElem = cloneDom.find('svg');//divReport 为需要截取成图片的 dom 的 id
- svgElem.each(function (index, node) {
- var parentNode = node.parentNode;
- var svg = node.outerHTML.trim();
- var canvas = document.createElement('canvas');
- canvas.width = 650;
- canvas.height = 798;
- canvg(canvas, svg);
- if (node.style.position) {
- canvas.style.position += node.style.position;
- canvas.style.left += node.style.left;
- canvas.style.top += node.style.top;
- }
- nodesToRecover.push({
- parent: parentNode,
- child: node
- });
- parentNode.removeChild(node);
- nodesToRemove.push({
- parent: parentNode,
- child: canvas
- });
- parentNode.appendChild(canvas);
- });
- // 将克隆节点动态追加到 body 后面.
- $("body").append(cloneDom);
- html2canvas(cloneDom, {
- onrendered: function(canvas) {
- var url =canvas.toDataURL("image/png");
- Windows.location.href = url ;
- cloneDom.remove(); // 清空克隆的内容
- },
- background:"#fafafa"
- });
- }
- }
这里外面首先将要截取的 div 克隆一份, 并将 z-index 设置为最小, 避免引起界面的不美观, 然后是对 svg 进行的处理, 上面已经分析过了, 最后将克隆节点追加到 body 后面即可.
在 onrendered 中, 我们可以直接使用 location.href 跳转查看图片, 可以进行保存操作, 也可以将 url 写入 img 的 src 中显示在界面上, 如 $('#imgId').attr('src',url);
最后可以在界面展示刚刚截取到的图片:
五, 上传图片保存到数据库, 并在界面中获取该图片显示
现在得到 url 了, 需要上传到后端, 并存到数据库中, 再另一个展示的界面中加载该图片. 我一般习惯于使用 url 来存储图片路径, 而不是用 blob 存储.
因为需要在另一个界面中获取图片, 所以我把图片存在了与 webapp 同级的一个 resource 目录下, 代码如下:
- // 存储图片并返回图片路径
- BASE64Decoder decoder = new BASE64Decoder();
- byte[] b = decoder.decodeBuffer(product.getProPic().substring("data:image/png;base64,".length()));
- ByteArrayInputStream bais = new ByteArrayInputStream(b);
- BufferedImage bi1 = ImageIO.read(bais);
- String url = "user_resource" + File.separator + "img" + File.separator + "product_"+UUID.randomUUID().toString().replace("-", "")+".png";
- String totalUrl = System.getProperty("root") + url;
- File w2 = new File(totalUrl);
- ImageIO.write(bi1, "png", w2);
- product.setProPic(url); // 将图片的相对路径存储到数据库中
- int res = productMapper.insertSelective(product); // 添加到数据库
这里因为涉及到其它逻辑, 所以只放一部分代码.
这里使用的是 BASE64Decoder 来存储图片, 我们获取到图片后, 需要使用 substring 将 "data:image/png;base64," 的内容截取掉, 因为 "," 后面才是图片的 url, url.substring("data:image/png;base64,".length()) .
对于路径, 上面代码中的 url 是我存储到数据库中的内容, 而 totalUrl 就是实际进行 ImageIO 的 write 操作时存储的真实路径, getProperty() 方法获取的项目的根目录, 可以在 Web.xml 中配置如下内容, 然后 System.getProperty("root") 即可.
- <!-- 配置系统获得项目根目录 -->
- <context-param>
- <param-name>webAppRootKey</param-name>
- <param-value>root</param-value>
- </context-param>
- <listener>
- <listener-class>
- org.springframework.Web.util.WebAppRootListener
- </listener-class>
- </listener>
现在图片的 url 就存到数据库里了, 而图片本身就存储在 tomcat 下该项目的这个目录下.
最后外面在界面上获取, 只需要在当前的 url 前面加上项目名即可 <imgclass="depot-img"src="<%=request.getContextPath()%>/`+e.proPic+`"> .
然后就可以看到界面上显示的图片了:
来源: https://www.cnblogs.com/adamjwh/p/10127666.html