Sử dụng HTML5 để thay đổi kích thước hình ảnh trước khi tải lên


102

Tôi đã tìm thấy một vài bài đăng khác nhau và thậm chí cả câu hỏi trên stackoverflow trả lời câu hỏi này. Về cơ bản tôi đang thực hiện điều này giống như bài đăng này .

Vì vậy, đây là vấn đề của tôi. Khi tải ảnh lên, tôi cũng cần gửi phần còn lại của biểu mẫu. Đây là html của tôi:

<form id="uploadImageForm" enctype="multipart/form-data">
  <input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
  <input id="name" value="#{name}" />
  ... a few more inputs ... 
</form>

Trước đây, tôi không cần thay đổi kích thước hình ảnh, vì vậy javascript của tôi trông như thế này:

window.uploadPhotos = function(url){
    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

Tất cả điều này đều hoạt động tốt ... bây giờ tôi cần thay đổi kích thước hình ảnh ... làm thế nào tôi có thể thay thế hình ảnh trong biểu mẫu để hình ảnh đã thay đổi kích thước được đăng chứ không phải hình ảnh được tải lên?

window.uploadPhotos = function(url){

    var resizedImage;

    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 1200,
                    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);
                resizedImage = canvas.toDataURL('image/jpeg');
            }
            image.src = readerEvent.target.result;
        }
        reader.readAsDataURL(file);
    }


   // TODO: Need some logic here to switch out which photo is being posted...

    var data = new FormData($("form[id*='uploadImageForm']")[0]);

    $.ajax({
        url: url,
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        type: 'POST',
        success: function(data){
            ... handle error...
            }
        }
    });
};

Tôi đã nghĩ đến việc di chuyển đầu vào tệp ra khỏi biểu mẫu và có một đầu vào ẩn trong biểu mẫu mà tôi đặt giá trị thành giá trị của hình ảnh đã thay đổi kích thước ... Nhưng tôi tự hỏi liệu tôi có thể thay thế hình ảnh đó không đã có trong biểu mẫu.


Bạn đang làm việc với bất kỳ ngôn ngữ phía máy chủ nào hay chỉ html5 và javascript?
luke2012

1
@ luke2012 java server side
ferics2

Có thể cắt hình ảnh ở phía máy khách bằng cách sử dụng jCrop, sau đó gửi tọa độ đến phía máy chủ và cắt nó. tức làBufferedImage dest = src.getSubimage(rect.x, rect.y, rect.width, rect.height);
luke2012

4
@ luke2012 vấn đề là giảm kích thước hình ảnh TRƯỚC KHI gửi nó đến máy chủ.
ferics2

Hãy xem nguồn js của pandamatak.com/people/anand/test/crop có vẻ tương tự ..
luke2012

Câu trả lời:


160

Đây là những gì tôi đã làm và nó hoạt động tuyệt vời.

Đầu tiên, tôi đã di chuyển đầu vào tệp ra bên ngoài biểu mẫu để nó không được gửi:

<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
    <input id="name" value="#{name}" />
    ... a few more inputs ... 
</form>

Sau đó, tôi đã thay đổi uploadPhotoschức năng để chỉ xử lý việc thay đổi kích thước:

window.uploadPhotos = function(url){
    // Read in file
    var file = event.target.files[0];

    // Ensure it's an image
    if(file.type.match(/image.*/)) {
        console.log('An image has been loaded');

        // Load the image
        var reader = new FileReader();
        reader.onload = function (readerEvent) {
            var image = new Image();
            image.onload = function (imageEvent) {

                // Resize the image
                var canvas = document.createElement('canvas'),
                    max_size = 544,// TODO : pull max size from a site config
                    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);
    }
};

Như bạn có thể thấy, tôi đang sử dụng canvas.toDataURL('image/jpeg');để thay đổi hình ảnh đã thay đổi kích thước thành adn dataUrl, sau đó tôi gọi hàm dataURLToBlob(dataUrl);biến dataUrl thành một blob mà sau đó tôi có thể thêm vào biểu mẫu. Khi blob được tạo, tôi kích hoạt một sự kiện tùy chỉnh. Đây là chức năng tạo blob:

/* Utility 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});
}
/* End Utility function to convert a canvas to a BLOB      */

Cuối cùng, đây là trình xử lý sự kiện của tôi, lấy blob từ sự kiện tùy chỉnh, thêm biểu mẫu và sau đó gửi nó.

/* Handle image resized events */
$(document).on("imageResized", function (event) {
    var data = new FormData($("form[id*='uploadImageForm']")[0]);
    if (event.blob && event.url) {
        data.append('image_data', event.blob);

        $.ajax({
            url: event.url,
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',
            success: function(data){
               //handle errors...
            }
        });
    }
});

1
Mã tuyệt vời, sau một số chỉnh sửa và sửa một số lỗi (tôi không nhớ chính xác cái gì và cái nào), tôi đã làm cho nó hoạt động. Nhân tiện, tôi nghĩ khi bạn viết width *= max_size / width;bạn thực sự có ý nghĩa width *= max_size / height;.
user1111929

2
Mã này có hoạt động trên thiết bị di động không? Dưới iOS và Android?
planewalker

4
@planewalker Tôi thực sự đã viết mã dành riêng cho thiết bị di động. Để cắt giảm việc sử dụng dữ liệu.
ferics 2

2
@planewalker, tôi đang thử nghiệm trên iOS khi viết bài này. Hi vọng nó sẽ giúp ích cho bạn.
ferics2

3
Làm tốt lắm và cảm ơn vì đã chia sẻ! (Các lỗi nhỏ khác đã đề cập đến nhưng không được xác định là trong hình ảnh thay đổi kích thước kích hoạt sự kiện "url: url" cần được "url: dataUrl".)
Seoras

44

nếu có ai quan tâm, tôi đã tạo phiên bản sắp chữ:

interface IResizeImageOptions {
  maxSize: number;
  file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
  const file = settings.file;
  const maxSize = settings.maxSize;
  const reader = new FileReader();
  const image = new Image();
  const canvas = document.createElement('canvas');
  const dataURItoBlob = (dataURI: string) => {
    const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
        atob(dataURI.split(',')[1]) :
        unescape(dataURI.split(',')[1]);
    const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
    const max = bytes.length;
    const ia = new Uint8Array(max);
    for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
    return new Blob([ia], {type:mime});
  };
  const resize = () => {
    let width = image.width;
    let height = image.height;

    if (width > height) {
        if (width > maxSize) {
            height *= maxSize / width;
            width = maxSize;
        }
    } else {
        if (height > maxSize) {
            width *= maxSize / height;
            height = maxSize;
        }
    }

    canvas.width = width;
    canvas.height = height;
    canvas.getContext('2d').drawImage(image, 0, 0, width, height);
    let dataUrl = canvas.toDataURL('image/jpeg');
    return dataURItoBlob(dataUrl);
  };

  return new Promise((ok, no) => {
      if (!file.type.match(/image.*/)) {
        no(new Error("Not an image"));
        return;
      }

      reader.onload = (readerEvent: any) => {
        image.onload = () => ok(resize());
        image.src = readerEvent.target.result;
      };
      reader.readAsDataURL(file);
  })    
};

và đây là kết quả javascript:

var resizeImage = function (settings) {
    var file = settings.file;
    var maxSize = settings.maxSize;
    var reader = new FileReader();
    var image = new Image();
    var canvas = document.createElement('canvas');
    var dataURItoBlob = function (dataURI) {
        var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
            atob(dataURI.split(',')[1]) :
            unescape(dataURI.split(',')[1]);
        var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
        var max = bytes.length;
        var ia = new Uint8Array(max);
        for (var i = 0; i < max; i++)
            ia[i] = bytes.charCodeAt(i);
        return new Blob([ia], { type: mime });
    };
    var resize = function () {
        var width = image.width;
        var height = image.height;
        if (width > height) {
            if (width > maxSize) {
                height *= maxSize / width;
                width = maxSize;
            }
        } else {
            if (height > maxSize) {
                width *= maxSize / height;
                height = maxSize;
            }
        }
        canvas.width = width;
        canvas.height = height;
        canvas.getContext('2d').drawImage(image, 0, 0, width, height);
        var dataUrl = canvas.toDataURL('image/jpeg');
        return dataURItoBlob(dataUrl);
    };
    return new Promise(function (ok, no) {
        if (!file.type.match(/image.*/)) {
            no(new Error("Not an image"));
            return;
        }
        reader.onload = function (readerEvent) {
            image.onload = function () { return ok(resize()); };
            image.src = readerEvent.target.result;
        };
        reader.readAsDataURL(file);
    });
};

cách sử dụng giống như:

resizeImage({
    file: $image.files[0],
    maxSize: 500
}).then(function (resizedImage) {
    console.log("upload resized image")
}).catch(function (err) {
    console.error(err);
});

hoặc ( async/ await):

const config = {
    file: $image.files[0],
    maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")

Giải pháp thực sự tốt, có thể thêm một cài đặt bổ sung để cho phép trả về kết quả dưới dạng base64 (bỏ qua lệnh gọi dataURIToBlob ở cuối).
Chen

Tôi gặp lỗi này:Uncaught (in promise) TypeError: Cannot read property 'type' of undefined
Owen M

1
Đẹp. Tôi đã phải thay đổi unescape thành (<any> window) .unescape Tôi cũng đã thêm minSize.
vua robert

maxSizelà byte hay kb?
Jagruti

1
Câu trả lời của bạn đã giúp tôi trả lời câu hỏi này , cảm ơn.
Syed

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.