文件则须求再信任FileSaver.js插件,如若上传也能

2019-11-26 16:21栏目:前端开发
TAG:

动用 canvas 实现数据压缩

2016/03/15 · HTML5 · 1 评论 · Canvas

原来的书文出处: EtherDream   

前面一个完成 SVG 转 PNG

2015/11/16 · JavaScript · PNG, SVG

原稿出处: 百度FEX - zhangbobell   

将HTML导出生成word文书档案,

 

前言

HTTP 援救 GZip 压缩,可节约不知凡几传输能源。但可惜的是,唯有下载才有,上传并不扶植。

比如上传也能减弱,那就完美了。特别符合大量文书提交的场子,譬如新浪,便是很好的例证。

虽说标准不扶持「上传压缩」,但仍是可以够自个儿来促成。

前言

svg 是风流罗曼蒂克种矢量图形,在 web 上利用很分布,不过洋洋时候是因为选择的气象,日常供给将 svg 转为 png 格式,下载到本地等。随着浏览器对 HTML 5 的帮忙度更高,大家得以把 svg 转为 png 的干活付出浏览器来达成。

前言:

品种开采中相见了亟待将HTML页面的原委导出为三个word文书档案,所以有了那边小说。

道理当然是那样的,项目支付又时间稍稍热切,第不常间想到的是用插件,所以百度了下。上边就介绍三个导出word文书档案的不二法门。

Flash

首选方案当然是 Flash,究竟它提供了压缩 API。除了 zip 格式,还援助 lzma 这种一流压缩。

因为是原生接口,所以质量相当的高。并且对应的 swf 文件,也超级小。

经常方法

  1. 创建 imageimage,src = xxx.svg;
  2. 创设 canvas,dragImage 将图纸贴到 canvas 上;
  3. 采用 toDataUrl 函数,将 canvas 的代表为 url;
  4. new image, src = url, download = download.png;

然则,在转移的时候有时有时会遇上如下的如下的七个难点:

法一:通过jquery.wordexport.js导出word

备注:兼容IE9以上

大概浏览了下jquery.wordexport.js插件的代码,理解到了经过该插件能够导出文本和图表,而图片首先通过canvas的款型

制图,文本则须求再信赖FileSaver.js插件,FileSaver.js插件则首要透过H5的文本操作新天性new Blob()和new FileReader()

来得以达成公文的导出。

插件源码:

FileSaver.js

图片 1 1 /* FileSaver.js 2 * A saveAs() FileSaver implementation. 3 * 1.3.2 4 * 2016-06-16 18:25:19 5 * 6 * By Eli Grey, 7 * License: MIT 8 * See 9 */ 10 11 /*global self */ 12 /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ 13 14 /*! @source */ 15 16 var saveAs = saveAs || (function(view) { 17 "use strict"; 18 // IE <10 is explicitly unsupported 19 if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]./.test(navigator.userAgent)) { 20 return; 21 } 22 var 23 doc = view.document 24 // only get URL when necessary in case Blob.js hasn't overridden it yet 25 , get_URL = function() { 26 return view.URL || view.webkitURL || view; 27 } 28 , save_link = doc.createElementNS("", "a") 29 , can_use_save_link = "download" in save_link 30 , click = function(node) { 31 var event = new MouseEvent("click"); 32 node.dispatchEvent(event); 33 } 34 , is_safari = /constructor/i.test(view.HTMLElement) 35 , is_chrome_ios =/CriOS/[d]+/.test(navigator.userAgent) 36 , throw_outside = function(ex) { 37 (view.setImmediate || view.setTimeout)(function() { 38 throw ex; 39 }, 0); 40 } 41 , force_saveable_type = "application/octet-stream" 42 // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to 43 , arbitrary_revoke_timeout = 1000 * 40 // in ms 44 , revoke = function(file) { 45 var revoker = function() { 46 if (typeof file === "string") { // file is an object URL 47 get_URL().revokeObjectURL(file); 48 } else { // file is a File 49 file.remove(); 50 } 51 }; 52 setTimeout(revoker, arbitrary_revoke_timeout); 53 } 54 , dispatch = function(filesaver, event_types, event) { 55 event_types = [].concat(event_types); 56 var i = event_types.length; 57 while (i--) { 58 var listener = filesaver["on" + event_types[i]]; 59 if (typeof listener === "function") { 60 try { 61 listener.call(filesaver, event || filesaver); 62 } catch (ex) { 63 throw_outside(ex); 64 } 65 } 66 } 67 } 68 , auto_bom = function(blob) { 69 // prepend BOM for UTF-8 XML and text/* types (including HTML) 70 // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF 71 if (/^s*(?:text/S*|application/xml|S*/S*+xml)s*;.*charsets*=s*utf-8/i.test(blob.type)) { 72 return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type}); 73 } 74 return blob; 75 } 76 , FileSaver = function(blob, name, no_auto_bom) { 77 if (!no_auto_bom) { 78 blob = auto_bom(blob); 79 } 80 // First try a.download, then web filesystem, then object URLs 81 var 82 filesaver = this 83 , type = blob.type 84 , force = type === force_saveable_type 85 , object_url 86 , dispatch_all = function() { 87 dispatch(filesaver, "writestart progress write writeend".split(" ")); 88 } 89 // on any filesys errors revert to saving with object URLs 90 , fs_error = function() { 91 if ((is_chrome_ios || (force && is_safari)) && view.FileReader) { 92 // Safari doesn't allow downloading of blob urls 93 var reader = new FileReader(); 94 reader.onloadend = function() { 95 var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;'); 96 var popup = view.open(url, '_blank'); 97 if(!popup) view.location.href = url; 98 url=undefined; // release reference before dispatching 99 filesaver.readyState = filesaver.DONE; 100 dispatch_all(); 101 }; 102 reader.readAsDataURL(blob); 103 filesaver.readyState = filesaver.INIT; 104 return; 105 } 106 // don't create more object URLs than needed 107 if (!object_url) { 108 object_url = get_URL().createObjectURL(blob); 109 } 110 if (force) { 111 view.location.href = object_url; 112 } else { 113 var opened = view.open(object_url, "_blank"); 114 if (!opened) { 115 // Apple does not allow window.open, see 116 view.location.href = object_url; 117 } 118 } 119 filesaver.readyState = filesaver.DONE; 120 dispatch_all(); 121 revoke(object_url); 122 } 123 ; 124 filesaver.readyState = filesaver.INIT; 125 126 if (can_use_save_link) { 127 object_url = get_URL().createObjectURL(blob); 128 setTimeout(function() { 129 save_link.href = object_url; 130 save_link.download = name; 131 click(save_link); 132 dispatch_all(); 133 revoke(object_url); 134 filesaver.readyState = filesaver.DONE; 135 }); 136 return; 137 } 138 139 fs_error(); 140 } 141 , FS_proto = FileSaver.prototype 142 , saveAs = function(blob, name, no_auto_bom) { 143 return new FileSaver(blob, name || blob.name || "download", no_auto_bom); 144 } 145 ; 146 // IE 10+ (native saveAs) 147 if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) { 148 return function(blob, name, no_auto_bom) { 149 name = name || blob.name || "download"; 150 151 if (!no_auto_bom) { 152 blob = auto_bom(blob); 153 } 154 return navigator.msSaveOrOpenBlob(blob, name); 155 }; 156 } 157 158 FS_proto.abort = function(){}; 159 FS_proto.readyState = FS_proto.INIT = 0; 160 FS_proto.WRITING = 1; 161 FS_proto.DONE = 2; 162 163 FS_proto.error = 164 FS_proto.onwritestart = 165 FS_proto.onprogress = 166 FS_proto.onwrite = 167 FS_proto.onabort = 168 FS_proto.onerror = 169 FS_proto.onwriteend = 170 null; 171 172 return saveAs; 173 }( 174 typeof self !== "undefined" && self 175 || typeof window !== "undefined" && window 176 || this.content 177 )); 178 // `self` is undefined in Firefox for Android content script context 179 // while `this` is nsIContentFrameMessageManager 180 // with an attribute `content` that corresponds to the window 181 182 if (typeof module !== "undefined" && module.exports) { 183 module.exports.saveAs = saveAs; 184 } else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) { 185 define([], function() { 186 return saveAs; 187 }); 188 } View Code

jquery.wordexport.js

图片 2 1 if (typeof jQuery !== "undefined" && typeof saveAs !== "undefined") { 2 (function($) { 3 $.fn.wordExport = function(fileName) { 4 fileName = typeof fileName !== 'undefined' ? fileName : "jQuery-Word-Export"; 5 var static = { 6 mhtml: { 7 top: "Mime-Version: 1.0nContent-Base: " + location.href + "nContent-Type: Multipart/related; boundary="NEXT.ITEM-BOUNDARY";type="text/html"nn--NEXT.ITEM-BOUNDARYnContent-Type: text/html; charset="utf-8"nContent-Location: " + location.href + "nn<!DOCTYPE html>n<html>n_html_</html>", 8 head: "<head>n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">n<style>n_styles_n</style>n</head>n", 9 body: "<body>_body_</body>" 10 } 11 }; 12 var options = { 13 maxWidth: 624 14 }; 15 // Clone selected element before manipulating it 16 var markup = $(this).clone(); 17 18 // Remove hidden elements from the output 19 markup.each(function() { 20 var self = $(this); 21 if (self.is(':hidden')) 22 self.remove(); 23 }); 24 25 // Embed all images using Data URLs 26 var images = Array(); 27 var img = markup.find('img'); 28 for (var i = 0; i < img.length; i++) { 29 // Calculate dimensions of output image 30 var w = Math.min(img[i].width, options.maxWidth); 31 var h = img[i].height * (w / img[i].width); 32 // Create canvas for converting image to data URL 33 var canvas = document.createElement("CANVAS"); 34 canvas.width = w; 35 canvas.height = h; 36 // Draw image to canvas 37 var context = canvas.getContext('2d'); 38 context.drawImage(img[i], 0, 0, w, h); 39 // Get data URL encoding of image 40 var uri = canvas.toDataURL("image/png/jpg"); 41 $(img[i]).attr("src", img[i].src); 42 img[i].width = w; 43 img[i].height = h; 44 // Save encoded image to array 45 images[i] = { 46 type: uri.substring(uri.indexOf(":") + 1, uri.indexOf(";")), 47 encoding: uri.substring(uri.indexOf(";") + 1, uri.indexOf(",")), 48 location: $(img[i]).attr("src"), 49 data: uri.substring(uri.indexOf(",") + 1) 50 }; 51 } 52 53 // Prepare bottom of mhtml file with image data 54 var mhtmlBottom = "n"; 55 for (var i = 0; i < images.length; i++) { 56 mhtmlBottom += "--NEXT.ITEM-BOUNDARYn"; 57 mhtmlBottom += "Content-Location: " + images[i].location + "n"; 58 mhtmlBottom += "Content-Type: " + images[i].type + "n"; 59 mhtmlBottom += "Content-Transfer-Encoding: " + images[i].encoding + "nn"; 60 mhtmlBottom += images[i].data + "nn"; 61 } 62 mhtmlBottom += "--NEXT.ITEM-BOUNDARY--"; 63 64 //TODO: load css from included stylesheet 65 66 //var styles=' /* Font Definitions */@font-face{font-family:宋体;panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-alt:SimSun;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 680460288 22 0 262145 0;} @font-face{font-family:"Cambria Math";panose-1:2 4 5 3 5 4 6 3 2 4;mso-font-charset:1;mso-generic-font-family:roman;mso-font-format:other;mso-font-pitch:variable;mso-font-signature:0 0 0 0 0 0;} @font-face{font-family:"@宋体";panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 680460288 22 0 262145 0;}/* Style Definitions */p.MsoNormal, li.MsoNormal, div.MsoNormal{mso-style-unhide:no;mso-style-qformat:yes;mso-style-parent:"";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;font-size:14.0pt;font-family:大篆;mso-bidi-font-family:陶文;}p.MsoHeader, li.MsoHeader, div.MsoHeader{mso-style-noshow:yes;mso-style-priority:99;mso-style-link:"页眉 Char";margin:0cm;margin-bottom:.0001pt;text-align:center;mso-pagination:widow-orphan;layout-grid-mode:char;font-size:9.0pt;font-family:仿宋;mso-bidi-font-family:小篆;}p.MsoFooter, li.MsoFooter, div.MsoFooter{mso-style-noshow:yes;mso-style-priority:99;mso-style-link:"页脚 Char";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;layout-grid-mode:char;font-size:9.0pt;font-family:黑体;mso-bidi-font-family:燕书;}p.MsoAcetate, li.MsoAcetate, div.MsoAcetate{mso-style-noshow:yes;mso-style-priority:99;mso-style-link:"解说框文本 Char";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;font-size:9.0pt;font-family:金鼎文;mso-bidi-font-family:小篆;}span.Char{mso-style-name:"页眉 Char";mso-style-noshow:yes;mso-style-priority:99;mso-style-unhide:no;mso-style-locked:yes;mso-style-link:页眉;font-family:燕体;mso-ascii-font-family:草书;mso-fareast-font-family:钟鼓文;mso-hansi-font-family:草书;}span.Char0{mso-style-name:"页脚 Char";mso-style-noshow:yes;mso-style-priority:99;mso-style-unhide:no;mso-style-locked:yes;mso-style-link:页脚;font-family:燕书;mso-ascii-font-family:燕书;mso-fareast-font-family:黑体;mso-hansi-font-family:燕书;}span.Char1{mso-style-name:"解说框文本 Char";mso-style-noshow:yes;mso-style-priority:99;mso-style-unhide:no;mso-style-locked:yes;mso-style-link:传授框文本;font-family:金鼎文;mso-ascii-font-family:燕书;mso-fareast-font-family:宋体;mso-hansi-font-family:大篆;}p.msochpdefault, li.msochpdefault, div.msochpdefault{mso-style-name:msochpdefault;mso-style-unhide:no;mso-margin-top-alt:auto;margin-right:0cm;mso-margin-bottom-alt:auto;margin-left:0cm;mso-pagination:widow-orphan;font-size:10.0pt;font-family:草书;mso-bidi-font-family:隶书;}span.msonormal0{mso-style-name:msonormal;mso-style-unhide:no;}.MsoChpDefault{mso-style-type:export-only;mso-default-props:yes;font-size:10.0pt;mso-ansi-font-size:10.0pt;mso-bidi-font-size:10.0pt;mso-ascii-font-family:"Times New 罗曼";mso-hansi-font-family:"Times New 罗曼";mso-font-kerning:0pt;}/* Page Definitions */ @page WordSection1{size:595.3pt 841.9pt;margin:72.0pt 90.0pt 72.0pt 90.0pt;mso-header-margin:42.55pt;mso-footer-margin:49.6pt;mso-paper-source:0;}div.WordSection1{page:WordSection1;}'; 67 68 var styles=""; 69 70 // Aggregate parts of the file together 71 var fileContent = static.mhtml.top.replace("_html_", static.mhtml.head.replace("_styles_", styles) + static.mhtml.body.replace("_body_", markup.html())) + mhtmlBottom; 72 73 // Create a Blob with the file contents 74 var blob = new Blob([fileContent], { 75 type: "application/msword;charset=utf-8" 76 }); 77 saveAs(blob, fileName + ".doc"); 78 }; 79 })(jQuery); 80 } else { 81 if (typeof jQuery === "undefined") { 82 console.error("jQuery Word Export: missing dependency (jQuery)"); 83 } 84 if (typeof saveAs === "undefined") { 85 console.error("jQuery Word Export: missing dependency (FileSaver.js)"); 86 } 87 } View Code

插件调用:

 1 <!DOCTYPE html>
 2 <html>
 3 <head lang="en">
 4     <meta charset="UTF-8">
 5     <title>生成word文档</title>
 6 </head>
 7 <body lang=ZH-CN style='tab-interval:21.0pt'>
 8 <div class="word">
 9     <p align="center">10 </div>
11 <input type="button" value="导出word">
12 <script src="http://www.qdhsdzy.com/uploads/allimg/191126/162133I00-2.jpg"></script>
13 <script type="text/javascript" src="js/FileSaver.js"></script>
14 <script type="text/javascript" src="js/jquery.wordexport.js"></script>
15 <script>
16     $(function(){
17         $("input[type='button']").click(function(event) {
18             $(".word").wordExport('生成word文档');
19         });
20     })
21 </script>
22 </body>
23 </html>

直白调用wordExport()接口就足以导出word文书档案,传的参数为导出的word文件名。

补充:

透过大家健康写的外联样式设置样式是低效的,通过个人的施行发掘要求写内联样式工夫奏效,而单位也急需固守word的安排

单位pt设置。

而jquery.wordexport.js插件是要配置了个style样式让大家补充样式设置的:

图片 3

不过个人试行了下,设置的样式却力无法支生效,只可以通过内联设置才生效。

截图:

图片 4图片 5

JavaScript

Flash 渐渐淘汰,但代替的 HTML5,却未曾提供压缩 API。只可以和谐用 JS 达成。

这就算低价,但运营速度就慢多了,何况相应的 JS 也超级大。

若果代码有 50kb,而数据压缩后只小 10kb,那就不足了。除非量大,才有含义。

主题材料 1 :浏览器对 canvas 节制

Canvas 的 W3C 的正统上向来不谈起 canvas 的最大高/宽度和面积,可是各类厂家的浏览器出于浏览器品质的酌量,在分歧的平台上安装了最大的高/宽度或然是渲染面积,超越了那些阈值渲染的结果会是赤手。测量试验了三种浏览器的 canvas 品质如下:

  • chrome (版本 46.0.2490.80 (64-bit))
    • 最大范围:268, 435, 456 px^2 = 16, 384 px * 16, 384 px
    • 最大宽/高:32, 767 px
  • firefox (版本 42.0)
    • 最大规模:32, 767 px * 16, 384 px
    • 最大宽/高:32, 767px
  • safari (版本 9.0.1 (11601.2.7.2))
    • 最大规模: 268, 435, 456 px^2 = 16, 384 px * 16, 384 px
  • ie 10(版本 10.0.9200.17414)
    • 最大宽/高: 8, 192px * 8, 192px

在相同的 web 应用中,可能超少会超越那一个限定。但是,若是越过了那个节制,则 会招致导出为空白恐怕出于内部存款和储蓄器走漏以致浏览器崩溃。

同一时间从生龙活虎边来讲, 导出 png 也是后生可畏项很开销内部存款和储蓄器的操作,粗略估摸一下,导出 16, 384 px * 16, 384 px 的 svg 会消耗 16384 * 16384 * 4 / 1024 / 1024 = 1024 M 的内部存款和储蓄器。所以,在看似那几个极限值的时候,浏览器也会 反应变慢,能不可能导出成功也跟系统的可用内部存款和储蓄器大小等等都有关联。

对此那些标题,犹如下二种减轻方法:

  1. 将数据发送给后端,在后端实现 调换;
  2. 前端将 svg 切分成三个图片导出;

率先种艺术能够利用 PhantomJS、inkscape、ImageMagick 等工具,相对来讲比较容易,这里大家最主要研究第三种缓慢解决方法。

法二:通过百度js模板引擎生成word文书档案

至关重借使因而js模板设置相应的竹签,然后XDoc.to(baidu.template())导出word,而通过百度js模板引擎的好处是也得以导出PDF文件。

完整demo:

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <meta charset="UTF-8">
 5     <script type="text/javascript" src="http://www.xdocin.com/xdoc.js"></script>
 6     <script type="text/javascript" src="http://www.xdocin.com/baiduTemplate.js"></script>
 7     <style>
 8         .head{
 9             font-size: 29px;
10             display: block;
11         }
12         .content{
13             display: block;
14         }
15     </style>
16 </head>
17 <body>
18 <input type="button" onclick="gen('pdf')" value="生成PDF"/>
19 <input type="button" onclick="gen('docx')" value="生成Word"/>
20 <br/>
21 <script id="tmpl" type="text/html">
22     <xdoc version="A.3.0">
23         <body>
24         <para heading="1" lineSpacing="28">
25             <text class="head" valign="center" fontName="标宋" fontSize="29"><%=title%></text>
26         </para>
27         <para>
28             <img  src="<%=img%>" sizeType="autosize"/>
29         </para>
30         <para lineSpacing="9">
31             <text class="content" fontName="仿宋" fontSize="18"><%=content%></text>
32         </para>
33         </body>
34     </xdoc>
35 </script>
36 <script src="http://www.qdhsdzy.com/uploads/allimg/191126/162133I00-2.jpg"></script>
37 <script type="text/javascript">
38     var type="docx";//pdf
39     var data = {
40         title: "导出"+type+"文件",
41         img: "http://www.wordlm.com/uploads/allimg/130101/1_130101000405_1.jpg",
42         content: "我这样就可以导出"+type+"格式的文件了,是不是很方便",
43     };
44     function renderTemplate(){
45         var template=$("#tmpl").html();
46         var html=template.replace(/<%=title%>/,data.title)
47                 .replace(/<%=img%>/,data.img)
48                 .replace(/<%=content%>/,data.content);
49         $("body").append(html);
50     }
51     renderTemplate();
52     function gen(type) {
53         XDoc.to(baidu.template('tmpl', data), type, {}, "_blank");
54     }
55     console.log('http://www.xdocin.com/xml.html');
56 </script>
57 </body>
58 </html>  

那边本人通过renderTemplate函数叫js模板渲染到HTML中,落成了文本的突显和导出内容的三结合。而因为那边导出的word文书档案是内需专门设置样式的,所以在页面样式展示下大家得以由此加多.class的不二等秘书诀设置。

附部分导出word文书档案样式设置:

图片 6

 

截图:

图片 7图片 8

 

更加多参考:

FileSave.js:

百度导出文书档案模板:

 

前言: 项目开辟中遇见了特殊供给将HTML页面包车型大巴源委导出为叁个word文书档案,所以有了此处小说。 当然,项目支出又时间有...

其他

可不可以不要 JS,而是利用一些接口,直接达成减弱?

骨子里,在 HTML5 刚面世时,就潜心到了一个成效:canvas 导出图片。能够调换jpg、png 等格式。

假若在思考的话,相信你也想开了。对的,正是 png —— 它是无损压缩的。

我们把平日数据当成像素点,画到 canvas 上,然后导出成 png,正是两个非正规的滑坡包了~


上面带头研究。。。

svg 切分成多少个图片导出

思路:浏览器纵然对 canvas 有尺寸和面积的限量,然而对于 image 成分并未有鲜明的限制,相当于率先步生成的 image 其实显示是常规的,大家要做的只是在其次步 dragImage 的时候分多次将 image 成分切分并贴到 canvas 上然后下载下来。 同一时候,应小心到 image 的载入是三个异步的进度。

最紧要代码

JavaScript

// 构造 svg Url,此处省略将 svg 经字符过滤后转为 url 的经过。 var svgUrl = DomU途达L.createObjectUPAJEROL(blob); var svgWidth = document.querySelector('#kity_svg').getAttribute('width'); var svgHeight = document.querySelector('#kity_svg').getAttribute('height'); // 分片的增加率和冲天,可依附浏览器做适配 var w0 = 8192; var h0 = 8192; // 每行和每列能宽容的分片数 var M = Math.ceil(svgWidth / w0); var N = Math.ceil(svgHeight / h0); var idx = 0; loadImage(svgUrl).then(function(img) { while(idx < M * N) { // 要分开的面片在 image 上的坐标和尺寸 var targetX = idx % M * w0, targetY = idx / M * h0, targetW = (idx + 1) % M ? w0 : (svgWidth - (M - 1) * w0), targetH = idx >= (N - 1) * M ? (svgHeight - (N - 1) * h0) : h0; var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'); canvas.width = targetW; canvas.height = targetH; ctx.drawImage(img, targetX, targetY, targetW, targetH, 0, 0, targetW, targetH); console.log('now it is ' + idx); // 盘算在前端下载 var a = document.createElement('a'); a.download = 'naotu-' + idx + '.png'; a.href = canvas.toDataU陆风X8L('image/png'); var clickEvent = new Mouse伊芙nt('click', { 'view': window, 'bubbles': true, 'cancelable': false }); a.dispatchEvent(click伊夫nt); idx++; } }, function(err) { console.log(err); }); // 加载 image function loadImage(url) { return new Promise(function(resolve, reject) { var image = new Image(); image.src = url; image.crossOrigin = 'Anonymous'; image.onload = function() { resolve(this); }; image.onerror = function(err) { reject(err); }; }); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 构造 svg Url,此处省略将 svg 经字符过滤后转为 url 的过程。
var svgUrl = DomURL.createObjectURL(blob);
var svgWidth = document.querySelector('#kity_svg').getAttribute('width');
var svgHeight = document.querySelector('#kity_svg').getAttribute('height');
 
// 分片的宽度和高度,可根据浏览器做适配
var w0 = 8192;
var h0 = 8192;
 
// 每行和每列能容纳的分片数
var M = Math.ceil(svgWidth / w0);
var N = Math.ceil(svgHeight / h0);
 
var idx = 0;
loadImage(svgUrl).then(function(img) {
 
    while(idx < M * N) {
        // 要分割的面片在 image 上的坐标和尺寸
        var targetX = idx % M * w0,
            targetY = idx / M * h0,
            targetW = (idx + 1) % M ? w0 : (svgWidth - (M - 1) * w0),
            targetH = idx >= (N - 1) * M ? (svgHeight - (N - 1) * h0) : h0;
 
        var canvas = document.createElement('canvas'),
            ctx = canvas.getContext('2d');
 
            canvas.width = targetW;
            canvas.height = targetH;
 
            ctx.drawImage(img, targetX, targetY, targetW, targetH, 0, 0, targetW, targetH);
 
            console.log('now it is ' + idx);
 
            // 准备在前端下载
            var a = document.createElement('a');
            a.download = 'naotu-' + idx + '.png';
            a.href = canvas.toDataURL('image/png');
 
            var clickEvent = new MouseEvent('click', {
                'view': window,
                'bubbles': true,
                'cancelable': false
            });
 
            a.dispatchEvent(clickEvent);
 
        idx++;
    }
 
}, function(err) {
    console.log(err);
});
 
// 加载 image
function loadImage(url) {
    return new Promise(function(resolve, reject) {
        var image = new Image();
 
        image.src = url;
        image.crossOrigin = 'Anonymous';
        image.onload = function() {
            resolve(this);
        };
 
        image.onerror = function(err) {
            reject(err);
        };
    });
}

说明:

  1. 是因为在后面一个下载有浏览器宽容性、客商体验等主题材料,在实际上中,大概要求将调换后的多寡发送到后端,并作为一个精减包下载。
  2. 分片的尺寸这里运用的是 8192 * 9192,在事实上中,为了抓牢宽容性和体验,能够依据浏览器和平台做适配,举例在 iOS 下的 safari 的最大规模是 4096 *4096。

多少转变

数量转像素,并不麻烦。1 个像素能够包容 4 个字节:

R = bytes[0] G = bytes[1] B = bytes[2] A = bytes[3]

1
2
3
4
R = bytes[0]
G = bytes[1]
B = bytes[2]
A = bytes[3]

实际上有现存的办法,可批量将数据填充成像素:

img = new ImageData(bytes, w, h); context.putImageData(img, w, h)

1
2
img = new ImageData(bytes, w, h);
context.putImageData(img, w, h)

而是,图片的宽高怎么样设定?

标题 2 :导出包括图表的 svg

在导出的时候,还也许会遇见另一个标题:若是 svg 里面含有图表,你会意识经过上述措施导出的 png 里面,原本的图片是不显示的。平日认为是 svg 里面含有的图纸跨域了,但是借使你把这一个图形换花费域的图样,依旧会鬼使神差这种场所。图片 9

图表中上局地是导出前的 svg,下图是导出后的 png。svg 中的图片是本域的,在导出后不显得。

尺寸设定

最简便易行的,就是用 1px 的万丈。比方有 1000 个像素,则填在 1000 x 1 的图形里。

但假如有 10000 像素,就不可行了。因为 canvas 的尺码,是有限量的。

不等的浏览器,最大尺寸不风流洒脱致。有 4096 的,也有 32767 的。。。

以最大 4096 为例,倘若老是都用这些升幅,明显不客观。

比如有 n = 4100 个像素,大家采取 4096 x 2 的尺码:

| 1 | 2 | 3 | 4 | ... | 4095 | 4096 | | 4097 | 4098 | 4099 | 4100 | ...... 未利用 ......

1
2
| 1    | 2    | 3    | 4    | ...  | 4095 | 4096 |
| 4097 | 4098 | 4099 | 4100 | ...... 未利用 ......

第二行只用到 4 个,剩下的 4092 个都空着了。

但 4100 = 41 * 100。假使用这些尺寸,就不会有浪费。

故而,得对 n 分解因数:

n = w * h

1
n = w * h

这般就能够将 n 个像素,偏巧填满 w x h 的图片。

但 n 是质数的话,就无解了。这个时候浪费就不可幸免了,只是,怎么样能力浪费起码?

于是乎就形成这样一个难点:

怎样用 n + m 个点,拼成多个 w x h 的矩形(0

思量到 MAX 相当的小,穷举就能够。

咱俩遍历 h,总结相应的 w = ceil(n / h), 然后寻找最周边 n 的 w * h。

var beg = Math.ceil(n / MAX); var end = Math.ceil(Math.sqrt(n)); var minSize = 9e9; var bestH = 0, // 最终结出 bestW = 0; for (h = beg; h end; h++) { var w = Math.ceil(n / h); var size = w * h; if (size minSize) { minSize = size; bestW = w; bestH = h; } if (size == n) { break; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var beg = Math.ceil(n / MAX);
var end = Math.ceil(Math.sqrt(n));
 
var minSize = 9e9;
 
var bestH = 0,          // 最终结果
    bestW = 0;
 
for (h = beg; h  end; h++) {
    var w = Math.ceil(n / h);
    var size = w * h;
 
    if (size  minSize) {
        minSize = size;
        bestW = w;
        bestH = h;
    }
    if (size == n) {
        break;
    }
}

因为 w * h 和 h * w 是千篇一律的,所以只需遍历到 sqrt(n) 就足以。

平等,也无需从 1 开端,从 n / MAX 就能够。

那般,我们就能够找到最相符的图片尺寸。

当然,连续的空白像素,最后减掉后会极小。这一步其实并不超重大。

主题材料来自

大家依照小说最早始建议的步子,稳步逐个审查,会发觉在率先步的时候,svg 中的图片就不显得了。也正是,当 image 成分的 src 为五个 svg,况兼 svg 里面满含图表,那么被含有的图片是不会来得的,就算这几个图片是本域的。

W3C 关于这一个标题并不曾 做表明,最终在  找到了关于这些主题素材的证实。 意思是:禁绝这么做是由于安全着想,svg 里面引用的兼具 外表财富 包涵image, stylesheet, script 等都会被阻挡。

个中还举了二个例证:假使未有这几个节制,假诺五个论坛允许顾客上传那样的 svg 作为头像,就有望现身如此的光景,一人黑客上传 svg 作为头像,里面包蕴代码:<image xlink:href="http://evilhacker.com/myimage.png">(假若那位黑客具有对于 evil红客.com 的调整权卡塔尔,那么那位黑客就完全能快刀斩乱丝上边包车型地铁业务:

  • 若果有人查看她的材质,evil骇客.com 就能够吸收接纳到一回 ping 的央浼(进而能够获得查看者的 ip卡塔尔;
  • 能够做到对于分歧的 ip 地址的人显得不相符的头像;
  • 能够随即改换头像的外观(而不用经过论坛管理员的稽审卡塔 尔(阿拉伯语:قطر‎。

观察这里,大约就领悟了全体难题的事由了,当然还会有某个缘由大概是制止图像递归。

渲染难点

定下尺寸,大家就足以「渲染数据」了。

但是现实中,总有个别意外的坑。canvas 也不例外:

<canvas id="canvas" width="100" heigth="100"></canvas> <script> var ctx = canvas.getContext('2d'); // 写入的数码 var bytes = [100, 101, 102, 103]; var buf = new Uint8ClampedArray(bytes); var img = new ImageData(buf, 1, 1); ctx.putImageData(img, 0, 0); // 读取的数目 img = ctx.getImageData(0, 0, 1, 1); console.log(img.data); // chrome [99, 102, 102, 103] // firefox [101, 101, 103, 103] // ... </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<canvas id="canvas" width="100" heigth="100"></canvas>
<script>
  var ctx = canvas.getContext('2d');
 
  // 写入的数据
  var bytes = [100, 101, 102, 103];
 
  var buf = new Uint8ClampedArray(bytes);
  var img = new ImageData(buf, 1, 1);
  ctx.putImageData(img, 0, 0);
 
  // 读取的数据
  img = ctx.getImageData(0, 0, 1, 1);
  console.log(img.data);
  // chrome  [99,  102, 102, 103]
  // firefox [101, 101, 103, 103]
  // ...
</script>

读取的像素,居然和写入的有偏差!况且分裂的浏览器,偏差还差别等。

原先,浏览器为了巩固渲染品质,有一个 Premultiplied Alpha 的机制。然而,那会捐躯局地精度!

纵然视觉上并不显明,但用于数据存储,就不符合规律了。

如何禁止使用它?风流倜傥番尝试都没得逞。于是,只好从数据上雕刻了。

假如不接受 Alpha 通道,又会怎么样?

// 写入的数量 var bytes = [100, 101, 102, 255]; ... console.log(img.data); // [100, 101, 102, 255]

1
2
3
4
  // 写入的数据
  var bytes = [100, 101, 102, 255];
  ...
  console.log(img.data);  // [100, 101, 102, 255]

与此相类似,倒是避开了难点。

如上所述,只好从数额上早先,跳过 Alpha 通道:

// pixel 1 new_bytes[0] = bytes[0] // R new_bytes[1] = bytes[1] // G new_bytes[2] = bytes[2] // B new_bytes[3] = 255 // A // pixel 2 new_bytes[4] = bytes[3] // R new_bytes[5] = bytes[4] // G new_bytes[6] = bytes[5] // B new_bytes[7] = 255 // A ...

1
2
3
4
5
6
7
8
9
10
11
12
13
// pixel 1
new_bytes[0] = bytes[0]     // R
new_bytes[1] = bytes[1]     // G
new_bytes[2] = bytes[2]     // B
new_bytes[3] = 255          // A
 
// pixel 2
new_bytes[4] = bytes[3]     // R
new_bytes[5] = bytes[4]     // G
new_bytes[6] = bytes[5]     // B
new_bytes[7] = 255          // A
 
...

这儿,就不受 Premultiplied Alpha 的熏陶了。

鉴于轻易,也得以 1 像素存 1 字节:

// pixel 1 new_bytes[0] = bytes[0] new_bytes[1] = 255 new_bytes[2] = 255 new_bytes[3] = 255 // pixel 2 new_bytes[4] = bytes[1] new_bytes[5] = 255 new_bytes[6] = 255 new_bytes[7] = 255 ...

1
2
3
4
5
6
7
8
9
10
11
12
13
// pixel 1
new_bytes[0] = bytes[0]
new_bytes[1] = 255
new_bytes[2] = 255
new_bytes[3] = 255
 
// pixel 2
new_bytes[4] = bytes[1]
new_bytes[5] = 255
new_bytes[6] = 255
new_bytes[7] = 255
 
...

那般,整个图片最多唯有 256 色。假诺能导出成「索引型 PNG」的话,也是足以尝尝的。

消逝办法

思路:由于安全因素,其实首先步的时候,图片已经展现不出去了。那么大家今后设想的方法是在第一步之后遍历 svg 的构造,将具有的 image 元素的 url、地点和尺寸保存下来。在第三步之后,按梯次贴到 canvas 上。这样,最终导出的 png 图片就能有 svg 里面包车型地铁 image。器重代码

JavaScript

// 此处略去变通 svg url 的经过 var svgUrl = DomUCRUISERL.createObjectU君越L(blob); var svgWidth = document.querySelector('#kity_svg').getAttribute('width'); var svgHeight = document.querySelector('#kity_svg').getAttribute('height'); var embededImages = document.querySelectorAll('#kity_svg image'); // 由 nodeList 转为 array embededImages = Array.prototype.slice.call(embededImages); // 加载底层的图 loadImage(svgUrl).then(function(img) { var canvas = document.createElement('canvas'), ctx = canvas.getContext("2d"); canvas.width = svgWidth; canvas.height = svgHeight; ctx.drawImage(img, 0, 0); // 遍历 svg 里面有着的 image 元素embededImages.reduce(function(sequence, svgImg){ return sequence.then(function() { var url = svgImg.getAttribute('xlink:href') + 'abc', dX = svgImg.getAttribute('x'), dY = svgImg.getAttribute('y'), dWidth = svgImg.getAttribute('width'), dHeight = svgImg.getAttribute('height'); return loadImage(url).then(function( sImg) { ctx.drawImage(sImg, 0, 0, sImg.width, sImg.height, dX, dY, dWidth, dHeight); }, function(err) { console.log(err); }); }, function(err) { console.log(err); }); }, Promise.resolve()).then(function() { // 考虑在前者下载 var a = document.createElement("a"); a.download = 'download.png'; a.href = canvas.toDataUCR-VL("image/png"); var clickEvent = new Mouse伊夫nt("click", { "view": window, "bubbles": true, "cancelable": false }); a.dispatchEvent(clickEvent); }); }, function(err) { console.log(err); }) // 省略了 loadImage 函数 // 代码和第一个例证相符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 此处略去生成 svg url 的过程
var svgUrl = DomURL.createObjectURL(blob);
var svgWidth = document.querySelector('#kity_svg').getAttribute('width');
var svgHeight = document.querySelector('#kity_svg').getAttribute('height');
 
var embededImages = document.querySelectorAll('#kity_svg image');
// 由 nodeList 转为 array
embededImages = Array.prototype.slice.call(embededImages);
// 加载底层的图
loadImage(svgUrl).then(function(img) {
 
var canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d");
 
canvas.width = svgWidth;
canvas.height = svgHeight;
 
ctx.drawImage(img, 0, 0);
    // 遍历 svg 里面所有的 image 元素
    embededImages.reduce(function(sequence, svgImg){
 
        return sequence.then(function() {
            var url = svgImg.getAttribute('xlink:href') + 'abc',
                dX = svgImg.getAttribute('x'),
                dY = svgImg.getAttribute('y'),
                dWidth = svgImg.getAttribute('width'),
                dHeight = svgImg.getAttribute('height');
 
            return loadImage(url).then(function( sImg) {
                ctx.drawImage(sImg, 0, 0, sImg.width, sImg.height, dX, dY, dWidth, dHeight);
            }, function(err) {
                console.log(err);
            });
        }, function(err) {
            console.log(err);
        });
    }, Promise.resolve()).then(function() {
        // 准备在前端下载
        var a = document.createElement("a");
        a.download = 'download.png';
        a.href = canvas.toDataURL("image/png");
 
        var clickEvent = new MouseEvent("click", {
            "view": window,
            "bubbles": true,
            "cancelable": false
        });
 
        a.dispatchEvent(clickEvent);
 
        });
 
      }, function(err) {
        console.log(err);
   })
 
   // 省略了 loadImage 函数
   // 代码和第一个例子相同

说明

  1. 事例中 svg 里面包车型地铁图疑似根节点下边包车型客车,由此用于表示地点的 x, y 直接取来就能够使用,在实际上中,那一个职责可能须求跟别的质量做一些运算之后得出。若是是依赖svg 库创设的,那么可以一向使用库里面用于固定的函数,比直接从尾部运算特别有支持和可相信。
  2. 我们这里斟酌的是本域的图样的导出难题,跨域的图片由于「污染了」画布,在试行 toDataUrl 函数的时候会报错。

数据编码

末段,正是将图像实行导出。

只要 canvas 能平素导出成 blob,那是最佳的。因为 blob 可透过 AJAX 上传。

canvas.toBlob(function(blob) { // ... }, 'image/png')

1
2
3
canvas.toBlob(function(blob) {
    // ...
}, 'image/png')

而是,超级多浏览器都不扶持。只好导出 data uri 格式:

uri = canvas.toDataURL('image/png') // data:image/png;base64,xxxx

1
uri = canvas.toDataURL('image/png')  // data:image/png;base64,xxxx

但 base64 会扩充长度。所以,还得解回二进制:

base64 = uri.substr(uri.indexOf(',') + 1) binary = atob(base64)

1
2
base64 = uri.substr(uri.indexOf(',') + 1)
binary = atob(base64)

这时候的 binary,便是最终数额了呢?

风流浪漫旦将 binary 通过 AJAX 提交的话,会发掘实际传输字节,比 binary.length 大。

原来 atob 再次回到的数量,仍然为字符串型的。传输时,就关乎字集编码了。

故而还需再转移一遍,形成真的的二进制数据:

var len = binary.length var buf = new Uint8Array(len) for (var i = 0; i len; i++) { buf[i] = binary.charCodeAt(i) }

1
2
3
4
5
6
var len = binary.length
var buf = new Uint8Array(len)
 
for (var i = 0; i  len; i++) {
    buf[i] = binary.charCodeAt(i)
}

当时的 buf,技术被 AJAX 一点儿也不动的传输。

结语

在这里间和大家分享了 在后边贰个将 svg 转为 png 的主意和进度中大概会遭遇的八个难题,一个是浏览器对 canvas 的尺码限定,另贰个是导出图片的标题。当然,那八个难点还应该有其余的缓和措施,同有难点候由于文化所限,本文内容难免有尾巴,招待大家研讨指正。最终多谢@techird 和 @Naxior 关于那三个难点的斟酌。

1 赞 2 收藏 评论

图片 10

末段效果

归纳,我们简要演示下:Demo

找一个大块的文书测量试验。比方 qq.com 首页 HTML,有 637,101 字节。

先利用「每像素 1 字节」的编码,各类浏览器生成的 PNG 大小:

Chrome FireFox Safari
体积 289,460 203,276 478,994
比率 45.4% 31.9% 75.2%

在那之中火狐压缩率最高,减少了 2/3 的体积。

更改的 PNG 看起来是这么的:

图片 11

唯独可惜的是,全体浏览器生成的图形,都不是「256 色索引」的。


再测量试验「每像素 3 字节」,看看会不会有纠正:

Chrome FireFox Safari
体积 297,239 202,785 384,183
比率 46.7% 31.8% 60.3%

Safari 有了成都百货上千的开辟进取,可是 Chrome 却更糟了。

FireFox 有多少的进级,压缩率仍然为最高的。

图片 12

长期以来缺憾的是,纵然全体图片并从未选用 Alpha 通道,但转变的 PNG 仍然是 31位的。

何况,也无从设置压缩等第,使得这种压缩形式,效用并不高。

对照 Flash 压缩,差距就差不离了:

deflate 压缩 lzma 压缩
体积 133,660 108,015
比率 21.0% 17.0%

同期 Flash 生成的是通用格式,后端解码时,使用规范库就可以。

而 PNG 还得位图解码、像素处理等步骤,很辛勤。

进而,现实中依然先行使用 Flash,本文只是开脑洞而已。

实际上用场

可是这种艺术,实际依旧管用到过。用在三个很大日志上传之处(并且不能够用 Flash卡塔 尔(英语:State of Qatar)。

因为后端并不解析,仅仅积存而已。所以,能够将日志对应的 PNG 下回本地,在组织者本身计算机上深入分析。

解压更易于,正是将像素还原回数据,这里有个简陋的 Demo。

如此,既缩短了宽带,也节省存款和储蓄空间。

3 赞 4 收藏 1 评论

图片 13

版权声明:本文由大奖888-www.88pt88.com-大奖888官网登录发布于前端开发,转载请注明出处:文件则须求再信任FileSaver.js插件,如若上传也能