Mobile front-end-image compression and uploading practices, front-end image compression and uploading
Previously, some colleagues talked to me about the function of compressing images with canvas on the mobile end and then uploading images. Recently, I had some free time, so I just practiced it. The demo effect link is posted at the bottom of the article.
When uploading images on mobile terminals, users upload local images on mobile phones, while local images are generally relatively large. For iPhone 6, many images are usually one or two MB in size, if the image is uploaded in this way, the image will be too large. If the user uses mobile traffic, it is obviously not a good way to upload the image completely.
Currently, all new HTML5 APIs are well implemented in Mobile webkit. Therefore, compressing images directly at the front end has become a required feature for uploading images on mobile devices.
The three h5 APIs of filereader, canvas, and formdata are used to compress images on the Mobile End and upload images. Logic is not difficult. The whole process is:
(1) When you use input file to upload an image, use filereader to read the uploaded image data (in base64 format)
(2) Pass the image data to the img object, draw the img to the canvas, and then call canvas. toDataURL to compress the image.
(3) Get the compressed base64 Format Image Data, convert it to binary and insert formdata, and then submit formdata through XmlHttpRequest.
In these three steps, the image is compressed and uploaded.
It seems quite simple to say, but there are still some pitfalls. Next, we will analyze the Code directly:
[1] retrieving image data
First, obtain the image data, that is, listen to the change event of the input file, then obtain the uploaded file object files, convert the files of the class array into an array, and then perform forEach traversal.
Then the file type is determined. If it is not an image, no processing is performed. If an image is instantiated, A filereader reads the uploaded file data in base64 format to determine the Data Length. If the image is larger than kb, compress is called for compression; otherwise, the upload method is called for upload.
Filechooser. onchange = function () {if (! This. files. length) return; var files = Array. prototype. slice. call (this. files); if (files. length> 9) {alert ("up to 9 images can be uploaded at the same time"); return;} files. forEach (function (file, I) {if (! /\/(?: Jpeg | png | gif)/I. test (file. type) return; var reader = new FileReader (); var li = document. createElement ("li"); li. innerHTML = '<div class = "progress"> <span> </div>'; $ (". img-list "). append ($ (li); reader. onload = function () {var result = this. result; var img = new Image (); img. src = result; // if the image size is smaller than kb, directly upload if (result. length <= maxsize) {detail (li2.16.css ("background-image", "url (" + result + ")"); img = null; upload (result, file. type, $ (li); return;} // compress the image after it is loaded, and then upload if (img. complete) {callback ();} else {img. onload = callback;} function callback () {var data = compress (img); callback (li2.16.css ("background-image", "url (" + data + ")"); upload (data, file. type, $ (li); img = null ;}}; reader. readAsDataURL (file );})};
[2] compressing Images
After obtaining the image data, you can compress the image. The compressed image does not directly draw the image to the canvas and then call toDataURL.
In IOS, there are two restrictions on canvas image painting:
The first is the image size. If the image size exceeds 2 million pixels, the image cannot be drawn to the canvas. No error will be reported when drawImage is called, however, when you use toDataURL to retrieve image data, you obtain null image data.
Furthermore, there is a limit on the size of the canvas. If the size of the canvas is greater than or equal to 5 million pixels (that is, the product of width and height, nothing else can be drawn.
To address the first limitation, the solution is to draw the tiles. Tile painting, that is, to divide an image into multiple parts and draw it onto the canvas. In my code, we split the image into 1 million pixels and draw it onto the canvas.
In response to the second limitation, my solution is to properly compress the image width and height. In my code, for the sake of security, the upper limit is 4 million pixels, if the image size is greater than 4 million pixels, It is compressed to less than 4 million pixels. An image of 4 million pixels should be enough. In this case, the width and height are X.
In this way, the two restrictions on IOS are solved.
In addition to the restrictions described above, there are two pitfalls. One is that the toDataURL of canvas can only be compressed into jpg files. When the image uploaded by the user is png, it needs to be converted into jpg files, that is, canvas is used in a unified manner. toDataURL ('image/jpeg ', 0.1) is set to jpeg, and the compression ratio is controlled by yourself.
The other is that if it is png to jpg, when it is drawn to the canvas, there is a transparent area in the canvas, when it is converted to jpg, the transparent area will become black, because the default transparency pixel of the canvas is rgba (,), converting it to jpg will become rgba (,), that is, the transparent background will become black. The solution is to lay a white background on the canvas.
Function compress (img) {var initSize = img. src. length; var width = img. width; var height = img. height; // if the image is larger than 4 million pixels, calculate the compression ratio and press the image size below 4 million var ratio; if (ratio = width * height/4000000)> 1) {ratio = Math. sqrt (ratio); width/= ratio; height/= ratio;} else {ratio = 1;} canvas. width = width; canvas. height = height; // bottom color ctx. fillStyle = "# fff"; ctx. fillRect (0, 0, canvas. width, canvas. height); // If the Graph If the slice pixel is greater than 1 million, use the tile to draw var count; if (count = width * height/1000000)> 1) {count = ~~ (Math. sqrt (count) + 1); // calculate the number of tiles to be divided. // calculate the width and height of each tile. var nw = ~~ (Width/count); var nh = ~~ (Height/count); tCanvas. width = nw; tCanvas. height = nh; for (var I = 0; I <count; I ++) {for (var j = 0; j <count; j ++) {tctx. drawImage (img, I * nw * ratio, j * nh * ratio, nw * ratio, nh * ratio, 0, 0, nw, nh); ctx. drawImage (tCanvas, I * nw, j * nh, nw, nh) ;}} else {ctx. drawImage (img, 0, 0, width, height);} // perform the minimum compression var ndata = canvas. toDataURL ('image/jpeg ', 0.1); console. log ('before compression: '+ InitSize); console. log ('compressed: '+ ndata. length); console. log ('Compression ratio:' + ~~ (100 * (initSize-ndata. length)/initSize) + "%"); tCanvas. width = tCanvas. height = canvas. width = canvas. height = 0; return ndata ;}
[3] upload images
After compressing the image, you can insert it into formdata for upload. First, convert base64 data into a string, then instantiate an ArrayBuffer, and then pass the string into ArrayBuffer in an 8-bit integer format, then, use BlobBuilder or Blob Object to convert the ArrayBuffer of an 8-bit integer into a binary object blob, append the blob Object To formdata, and send it to the background through ajax.
In XmlHttpRequest2, not only big data can be sent, but also an API for obtaining the sending progress, which is also implemented in my code.
// Upload the image. Convert the base64 image into a binary object and insert it into formdata. upload the function upload (basestr, type, $ li) {var text = window. atob (basestr. split (",") [1]); var buffer = new ArrayBuffer (text. length); var ubuffer = new Uint8Array (buffer); var pecent = 0, loop = null; for (var I = 0; I <text. length; I ++) {ubuffer [I] = text. charCodeAt (I);} var Builder = window. webKitBlobBuilder | window. mozBlobBuilder; var blob; if (Builder) {v Ar builder = new Builder (); builder. append (buffer); blob = builder. getBlob (type);} else {blob = new window. blob ([buffer], {type: type});} var xhr = new XMLHttpRequest (); var formdata = new FormData (); formdata. append ('imagefile', blob); xhr. open ('post', '/cupload'); xhr. onreadystatechange = function () {if (xhr. readyState = 4 & xhr. status = 200) {console. log ('uploaded successfully: '+ xhr. responseText); cl EarInterval (loop); // when the message is received, the upload is complete $ li. find (". progress span "). animations ({'width': "100%"}, pecent <95? 200: 0, function () {upload (this).html ("uploaded successfully") ;}); $ (". pic-list "). append ('<a href = "' + xhr. responseText + '">' + xhr. responseText + ' </a>') }}; // data sending progress. The first 50% shows the progress xhr. upload. addEventListener ('progress', function (e) {if (loop) return; pecent = ~~ (100 * e. loaded/e. total)/2; $ li. find (". progress span ").css ('width', pecent +" % "); if (pecent = 50) {mockProgress () ;}}, false ); // function mockProgress () {if (loop) return; loop = setInterval (function () {pecent ++; $ li. find (". progress span ").css ('width', pecent +" % "); if (pecent = 99) {clearInterval (loop) ;}, 100)} xhr. send (formdata );}
At this point, the compression of the uploaded front-end image is complete. Because formdata is used for submission, the data can be processed in the background just like the data submitted by the common form.
If you are interested in this demo, you can view the github address of this demo:
Front-end code: https://github.com/whxaxes/node-test/blob/master/server/upload/index_2.html
By the way, it also sticks out the implementation of the background (nodejs): https://github.com/whxaxes/node-test/blob/master/server/upload/upload_2.js
Then paste the preview effect of the demo (if you want to view the compression effect, use a PC browser ):
Link: http://wanghx.cn: 9030/uindex_2 or directly scan the following QR code to see the effect (the server bandwidth is relatively small, the speed may be slow ).
Because this demo is just for your own practice and play, you can only test your iPhone 6 in the test, so you do not know if there are any problems with other models. If there is any improper situation, please forgive me.