简体   繁体   English

使用 JavaScript 从客户端上传压缩图像文件

[英]Upload compressed image file from client-side using JavaScript

I am trying to compress images on client side using JavaScript on some low bandwidth devices and I'm currently stuck in a limbo using the HTML5 File API.我正在尝试在某些低带宽设备上使用 JavaScript 在客户端压缩图像,而我目前使用 HTML5 文件 API 陷入困境。 I'm new to this, please bear with me if I'm missing something important.我是新手,如果我错过了重要的事情,请多多包涵。

I have some input tags which should ideally open the mobile camera, capture single image, compress and send files to the backend.我有一些输入标签,理想情况下应该打开移动相机,捕获单个图像,压缩并将文件发送到后端。 Although this can be done with a single input field with multiple uploads enabled but I need the multiple image fields to segregate images based on some categories.虽然这可以通过启用多个上传的单个输入字段来完成,但我需要多个图像字段来根据某些类别隔离图像。

Here's the input boxes:这是输入框:

<input type="file" name="file1" id="file1" capture="camera" accept="image/*">
<input type="file" name="file2" id="file2" capture="camera" accept="image/*">...

Here's the image compression logic:这是图像压缩逻辑:

    // Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
    window.resizePhotos = function(id, maxSize){
            var file = document.getElementById(id).files[0];

        // Ensuring it's an image
        if(file.type.match(/image.*/)) {
            // Loading the image
            var reader = new FileReader();
            reader.onload = function (readerEvent) {
                var image = new Image();
                image.onload = function (imageEvent) {

                    // Resizing the image and keeping its aspect ratio
                    var canvas = document.createElement("canvas"),
                        max_size = maxSize,
                        width = image.width,
                        height = image.height;
                    if (width > height) {
                        if (width > max_size) {
                            height *= max_size / width;
                            width = max_size;
                        }
                    } else {
                        if (height > max_size) {
                            width *= max_size / height;
                            height = max_size;
                        }
                    }
                    canvas.width = width;
                    canvas.height = height;
                    canvas.getContext("2d").drawImage(image, 0, 0, width, height);
                    var dataUrl = canvas.toDataURL("image/jpeg");
                    var resizedImage = dataURLToBlob(dataUrl);
                    $.event.trigger({
                        type: "imageResized",
                        blob: resizedImage,
                        url: dataUrl
                    });
                }
                image.src = readerEvent.target.result;
            }
            reader.readAsDataURL(file);
        }
    };

    // Function to convert a canvas to a BLOB
    var dataURLToBlob = function(dataURL) {
        var BASE64_MARKER = ';base64,';
        if (dataURL.indexOf(BASE64_MARKER) == -1) {
            var parts = dataURL.split(',');
            var contentType = parts[0].split(':')[1];
            var raw = parts[1];

            return new Blob([raw], {type: contentType});
        }

        var parts = dataURL.split(BASE64_MARKER);
        var contentType = parts[0].split(':')[1];
        var raw = window.atob(parts[1]);
        var rawLength = raw.length;

        var uInt8Array = new Uint8Array(rawLength);

        for (var i = 0; i < rawLength; ++i) {
            uInt8Array[i] = raw.charCodeAt(i);
        }

        return new Blob([uInt8Array], {type: contentType});
    }

    // Handling image resized events
    $(document).on("imageResized", function (event) {
        if (event.blob && event.url) {
            document.getElementById('file1').files[0] = event.url; // --> Tried this, did not work
            document.getElementById('file1').files[0].value = (URL || webkitURL).createObjectURL(event.blob); // --> Tried doing this looking at some other answers but did not work
            console.log(document.getElementById('file1').files[0]); // Original file is loading fine
            console.log(event.url); // Image compression is working correctly and producing the base64 data
        }
    });

    $(window).on("load", function() {
        // Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
        $("#file1").on("click touchstart" , function(){
            $(this).val("");
        });

        // Action triggers when user has selected any file 
        $("#file1").change(function(e) {
            resizePhotos("file1", 1024)
        });
    });

In PHP script, I'd usually try to catch files from the POST request like:在 PHP 脚本中,我通常会尝试从 POST 请求中捕获文件,例如:

$file1 = $_FILES["file1"]["tmp_name"];
$file2 = $_FILES["file2"]["tmp_name"];
...

But this doesn't work because it looks for the original user selected file at a tmp directory (eg the actual temporary file in my case is C:\xampp\tmp\php25CB.tmp )但这不起作用,因为它在 tmp 目录中查找原始用户选择的文件(例如,在我的情况下,实际的临时文件是 C:\xampp\tmp\php25CB.tmp )

One thing I've tried is put the input fields outside of the form tags, enabled the click behaviour using a button and created new input field with the modified data within the form like:我尝试过的一件事是将输入字段放在表单标签之外,使用按钮启用点击行为,并在表单中使用修改后的数据创建新的输入字段,例如:

var newinput = document.createElement("input");
newinput.type = 'file';
newinput.name = 'file1';
newinput.files[0] = event.url;
document.getElementById('parentdiv').appendChild(newinput);

Needless to say, this had no effect and the PHP script could not identify any file.不用说,这没有任何效果,PHP 脚本无法识别任何文件。

Please guide me and suggest any changes required in the JavaScript/PHP script so I can accept the modified file and not the original user uploaded file from the input field.请指导我并建议 JavaScript/PHP 脚本中所需的任何更改,以便我可以接受修改后的文件,而不是输入字段中原始用户上传的文件。

  1. You can only change a file input value with another list您只能使用另一个列表更改文件输入值
    here is how: https://stackoverflow.com/a/52079109/1008999 (also in the example)方法如下: https://stackoverflow.com/a/52079109/1008999 (也在示例中)
  2. Using the FileReader is a waste of time, CPU, Encoding & decoding and RAM...使用 FileReader 是浪费时间、CPU、编码和解码以及 RAM ......
    use URL.createObjectURL instead改用URL.createObjectURL
  3. Don't use canvas.toDataURL... use canvas.toBlob instead不要使用 canvas.toDataURL... 改用canvas.toBlob
  4. Canvas have bad compression, read earlier comment and see the jsfiddle proff ... Canvas 压缩不良,阅读早期评论并查看jsfiddle proff ...
    If you insist on using canvas to try and squeeze the size down then如果您坚持使用 canvas 尝试将尺寸缩小
    1. First try to see if the image is in a reasonable size first先试试看图片大小是否合理
    2. Compare if the pre existing image file.size is smaller than what the canvas.toBlob provides and choose if you want the old or the new one instead.比较预先存在的图像file.size是否小于canvas.toBlob提供的大小,然后选择您想要旧的还是新的。
    3. If resizing the image isn't enough have a look at this solution that change the quality until a desired file size & image aspect have been meet.如果调整图像大小还不够,请查看此解决方案,该解决方案会更改质量,直到满足所需的文件大小和图像方面。

Without any testing, this is how i would have refactor your code too:没有任何测试,这也是我重构代码的方式:

/**
 * @params {File[]} files Array of files to add to the FileList
 * @return {FileList}
 */
function fileListItems (files) {
  var b = new ClipboardEvent('').clipboardData || new DataTransfer()
  for (var i = 0, len = files.length; i<len; i++) b.items.add(files[i])
  return b.files
}

// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = async function resizePhotos (input, maxSize) {
  const file = input.files
  
  if (!file || !file.type.match(/image.*/)) return
  
  const image = new Image()
  const canvas = document.createElement('canvas')
  const max_size = maxSize
  image.src = URL.createObjectURL(file)
  await image.decode()
  let width = image.width
  let height = image.height
  
  // Resizing the image and keeping its aspect ratio
  if (width > height) {
    if (width > max_size) {
        height *= max_size / width
        width = max_size
    }
  } else {
      if (height > max_size) {
          width *= max_size / height
          height = max_size
      }
  }
  canvas.width = width
  canvas.height = height
  canvas.getContext('2d').drawImage(image, 0, 0, width, height)
  const resizedImage = await new Promise(rs => canvas.toBlob(rs, 'image/jpeg', 1))
  
  // PS: You might have to disable the event listener as this might case a change event
  // and triggering this function over and over in a loop otherwise

  input.files = fileListItems([
    new File([resizedImage], file.name, { type: resizedImage.type })
  ])
}


jQuery($ => {
  // Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
  $('#file1').on('click touchstart' , function(){
    $(this).val('')
  })

  // Action triggers when user has selected any file 
  $('#file1').change(function(e) {
    resizePhotos(this, 1024)
  })
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM