Verwenden Sie HTML5, um die Größe eines Bildes vor dem Hochladen zu ändern

102

Ich habe ein paar verschiedene Beiträge und sogar Fragen zum Stackoverflow gefunden, die diese Frage beantworten. Ich implementiere im Grunde das Gleiche wie dieser Beitrag .

Also hier ist mein Problem. Wenn ich das Foto hochlade, muss ich auch den Rest des Formulars einreichen. Hier ist mein HTML:

<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>

Zuvor musste ich die Größe des Bildes nicht ändern, daher sah mein Javascript folgendermaßen aus:

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

Das alles hat super funktioniert ... jetzt, wo ich die Größe der Bilder ändern muss ... wie kann ich das Bild im Formular ersetzen, damit das Bild in der Größe veröffentlicht wird und nicht das hochgeladene Bild?

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

Ich habe darüber nachgedacht, die Dateieingabe aus dem Formular zu verschieben und eine versteckte Eingabe in dem Formular zu haben, für die ich den Wert auf den Wert des verkleinerten Bilds festgelegt habe ... Aber ich frage mich, ob ich das Bild einfach ersetzen kann ist schon in der Form.

ferics2
quelle
Arbeiten Sie mit einer serverseitigen Sprache oder nur mit HTML5 und Javascript?
Luke2012
1
@ luke2012 Java Server Seite
ferics2
Schneiden Sie das Bild möglicherweise mit jCrop auf der Clientseite zu, senden Sie die Koordinaten dann an die Serverseite und schneiden Sie es zu. dhBufferedImage dest = src.getSubimage(rect.x, rect.y, rect.width, rect.height);
luke2012
4
@ luke2012 Der Punkt ist, die Bildgröße zu reduzieren, bevor sie an den Server gesendet wird.
ferics2
Werfen Sie einen Blick auf die js Quelle von pandamatak.com/people/anand/test/crop scheint ähnlich zu sein ..
luke2012

Antworten:

160

Hier ist, was ich letztendlich gemacht habe und es hat großartig funktioniert.

Zuerst habe ich die Dateieingabe außerhalb des Formulars verschoben, damit sie nicht gesendet wird:

<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>

Dann habe ich die uploadPhotosFunktion geändert , um nur die Größenänderung zu handhaben:

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

Wie Sie sehen canvas.toDataURL('image/jpeg');, ändere ich das Bild mit geänderter Größe in ein dataUrl adn und rufe dann die Funktion dataURLToBlob(dataUrl);auf, um das dataUrl in einen Blob zu verwandeln, den ich dann an das Formular anhängen kann. Wenn der Blob erstellt wird, löse ich ein benutzerdefiniertes Ereignis aus. Hier ist die Funktion zum Erstellen des Blobs:

/* 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      */

Schließlich ist hier mein Ereignishandler, der den Blob aus dem benutzerdefinierten Ereignis nimmt, das Formular anfügt und es dann sendet.

/* 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...
            }
        });
    }
});
ferics2
quelle
1
Wundervoller Code, nach einigen Optimierungen und Korrekturen (ich weiß nicht genau, was und was) habe ich ihn zum Laufen gebracht. Übrigens, ich denke, als du geschrieben width *= max_size / width;hast, hast du es tatsächlich gemeint width *= max_size / height;.
user1111929
2
Funktioniert dieser Code auf Mobilgeräten? Unter iOS und Android?
Planewalker
4
@planewalker Ich habe den Code speziell für mobile Geräte geschrieben. Reduzierung der Datennutzung.
ferics2
2
@planewalker, ich habe auf iOS getestet, als ich das geschrieben habe. Hoffe es funktioniert für dich.
Ferics2
3
Gut gemacht und danke fürs Teilen! (Der kleinere Fehler, den andere erwähnt, aber nicht identifiziert haben, befindet sich im Auslöser für die Größenänderung des Bildes. "Url: url" sollte "url: dataUrl" sein.)
Seoras
44

Bei Interesse habe ich eine Typoskript-Version erstellt:

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

und hier ist das Javascript-Ergebnis:

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

Verwendung ist wie:

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

oder ( async/ await):

const config = {
    file: $image.files[0],
    maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")
Santiago Hernández
quelle
Wirklich nette Lösung, kann eine zusätzliche Einstellung hinzufügen, um das Ergebnis als base64 zurückzugeben (umgehen Sie den dataURIToBlob-Aufruf am Ende).
Chen
Ich erhalte diesen Fehler:Uncaught (in promise) TypeError: Cannot read property 'type' of undefined
Owen M
1
Nett. Ich musste unescape in (<any> window) .unescape ändern. Außerdem habe ich minSize hinzugefügt.
Robert King
maxSizeist in Bytes oder kb?
Jagruti
1
Ihre Antwort hat mir geholfen, diese Frage zu beantworten , danke.
Syed