首页 > 解决方案 > 使用 JavaScript 从客户端上传压缩图像文件

问题描述

我正在尝试在一些低带宽设备上使用 JavaScript 在客户端压缩图像,而我目前使用 HTML5 File API 陷入困境。我是新手,如果我错过了重要的事情,请多多包涵。

我有一些输入标签,理想情况下应该打开移动相机,捕获单个图像,压缩并将文件发送到后端。虽然这可以通过启用多个上传的单个输入字段来完成,但我需要多个图像字段来根据某些类别隔离图像。

这是输入框:

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

这是图像压缩逻辑:

    // 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)
        });
    });

在 PHP 脚本中,我通常会尝试从 POST 请求中捕获文件,例如:

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

但这不起作用,因为它在 tmp 目录中查找原始用户选择的文件(例如,在我的情况下,实际的临时文件是 C:\xampp\tmp\php25CB.tmp )

我尝试过的一件事是将输入字段放在表单标签之外,使用按钮启用点击行为,并在表单中使用修改后的数据创建新的输入字段,例如:

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

不用说,这没有任何效果,PHP 脚本无法识别任何文件。

请指导我并建议 JavaScript/PHP 脚本中所需的任何更改,以便我可以接受修改后的文件,而不是输入字段中原始用户上传的文件。

标签: javascriptphphtmlimagefile

解决方案


  1. 您只能使用另一个列表更改文件输入值,
    方法如下:https ://stackoverflow.com/a/52079109/1008999 (也在示例中)
  2. 使用FileReader 是浪费时间、CPU、编码和解码以及 RAM ......请
    改用URL.createObjectURL
  3. 不要使用 canvas.toDataURL...改用canvas.toBlob
  4. 画布压缩不好,请阅读早期评论并查看jsfiddle proff ...
    如果您坚持使用画布尝试缩小尺寸,那么
    1. 先试试看图片大小是否合理
    2. 比较预先存在的图像file.size是否小于canvas.toBlob提供的图像,然后选择您想要旧图像还是新图像。
    3. 如果调整图像大小还不够,请查看此解决方案,该解决方案会更改质量,直到满足所需的文件大小和图像方面。

没有任何测试,这也是我重构代码的方式:

/**
 * @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)
  })
})

推荐阅读