データURIをファイルに変換してからFormDataに追加する


282

私は、Mozilla HacksサイトにあるようなHTML5画像アップローダーを再実装しようとしましたが、これはWebKitブラウザーで動作します。タスクの一部は、canvasオブジェクトから画像ファイルを抽出し、それをアップロードのためにFormDataオブジェクトに追加することです。

問題は、その中でcanvas持っているtoDataURLイメージファイルの表現を返す関数を、いるFormDataオブジェクトは、ファイルを受け入れるか、ブロブからオブジェクトファイルのAPI

Mozillaソリューションでは、次のFirefox専用機能を使用しましたcanvas

var file = canvas.mozGetAsFile("foo.png");

... WebKitブラウザーでは利用できません。私が考えることができる最善の解決策は、Data URIをFileオブジェクトに変換する方法を見つけることです。これは、File APIの一部であると考えていましたが、私は一生それを行うための何かを見つけることができません。

出来ますか?そうでない場合、代替案はありますか?

ありがとう。


画像のDataURIをサーバーに保存する場合:stackoverflow.com/a/50131281/5466401
Sibin John Mattappallil

回答:


470

いくつかのことを試した後、私はなんとか自分でこれを理解することができました。

まず、これはdataURIをBlobに変換します。

function dataURItoBlob(dataURI) {
    // convert base64/URLEncoded data component to raw binary data held in a string
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    return new Blob([ia], {type:mimeString});
}

そこから、データをファイルとしてアップロードされるようにフォームに追加するのは簡単です。

var dataURL = canvas.toDataURL('image/jpeg', 0.5);
var blob = dataURItoBlob(dataURL);
var fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);

28
なぜこれが常に起こるのですか...あちこちでSO検索を使用して何時間も何時間も問題を解決しようとします。次に、質問を投稿します。1時間以内に、別の質問から回答を得ます。私は文句を言っていないことを... stackoverflow.com/questions/9388412/...
syaz

@stoive Blobを構築することはできますが、S3への、POSTまたはPUTS3への構築方法を説明していただけますか?
Gaurav Shah

1
@mimo-基になるを指しArrayBufferBlobBuilderインスタンスに書き込まれます。
Stoive 2013

2
@stoiveその場合、なぜbb.append(ia)でないのですか?
2013

4
ありがとう!これにより、小さな修正で問題が解決しましたvar file = new File( [blob], 'canvasImage.jpg', { type: 'image/jpeg' } ); fd.append("canvasImage", file);
Prasad19sara

141

BlobBuilderとArrayBufferは非推奨になりました。Blobコンストラクターで更新されたトップコメントのコードは次のとおりです。

function dataURItoBlob(dataURI) {
    var binary = atob(dataURI.split(',')[1]);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}

2
ただのアイデア:array=[]; array.length=binary.length;... array[i]=bina...など。配列は事前に割り当てられます。反復ごとに配列を拡張する必要があるpush()を節約し、ここでは何百万ものアイテム(=バイト)を処理しているため、重要です。
DDS

2
Safariでも失敗します。@WilliamT。ただし、Firefox / Safari / Chromeの場合は有効です。
ObscureRobot 2013

「バイナリ」はビットの配列ではなく、バイトの配列であるため、少し誤解を招く名前です。
Niels Abildgaard 2014年

1
"type: 'image / jpeg'"-png画像の場合、または事前に画像の拡張子がわからない場合はどうなりますか?
Jasper

最初にUint8Arrayを作成する方が、配列を作成してからUint8Arrayに変換するよりも優れています。
2015年

52

これはiOSとSafariで動作します。

StoiveのArrayBufferソリューションを使用する必要がありますが、vava720が示すようにBlobBuilderを使用することはできません。

function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ab], { type: 'image/jpeg' });
}

11
すごい!しかし、ストームのソリューションのように、MIME文字列を動的に保つことはできますか?// mimeコンポーネントを分離しますvar mimeString = dataURI.split( '、')[0] .split( ':')[1] .split( ';')[0]
Per

webkit接頭辞付きのiOS6のフォールバックとは何ですか?これをどのように処理しますか?
confile

30

Firefoxにはcanvas.toBlob(メソッドとcanvas.mozGetAsFile()メソッドがあります。

しかし、他のブラウザはそうではありません。

canvasからdataurlを取得し、dataurlをblobオブジェクトに変換できます。

これが私のdataURLtoBlob()機能です。とても短いです。

function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {type:mime});
}

この関数をFormDataと共に使用して、キャンバスまたはdataurlを処理します。

例えば:

var dataurl = canvas.toDataURL('image/jpeg',0.8);
var blob = dataURLtoBlob(dataurl);
var fd = new FormData();
fd.append("myFile", blob, "thumb.jpg");

また、HTMLCanvasElement.prototype.toBlobGeckoエンジン以外のブラウザ用のメソッドを作成することもできます。

if(!HTMLCanvasElement.prototype.toBlob){
    HTMLCanvasElement.prototype.toBlob = function(callback, type, encoderOptions){
        var dataurl = this.toDataURL(type, encoderOptions);
        var bstr = atob(dataurl.split(',')[1]), n = bstr.length, u8arr = new Uint8Array(n);
        while(n--){
            u8arr[n] = bstr.charCodeAt(n);
        }
        var blob = new Blob([u8arr], {type: type});
        callback.call(this, blob);
    };
}

canvas.toBlob()のFirefoxだけでなく、すべての最新ブラウザで動作します。例えば:

canvas.toBlob(
    function(blob){
        var fd = new FormData();
        fd.append("myFile", blob, "thumb.jpg");
        //continue do something...
    },
    'image/jpeg',
    0.8
);

1
ここで言及されているcanvas.toBlobのポリフィルは、この問題を処理する正しい方法です。
Jakob Kruse

2
この投稿の最後のことを強調したいと思います。
Eric Simonton、2017年

25

私の優先する方法はcanvas.toBlob()です

しかし、とにかくここにフェッチを使用してbase64をBLOBに変換するもう1つの方法があります^^、

var url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="

fetch(url)
.then(res => res.blob())
.then(blob => {
  var fd = new FormData()
  fd.append('image', blob, 'filename')
  
  console.log(blob)

  // Upload
  // fetch('upload', {method: 'POST', body: fd})
})


フェッチとは何ですか?どのように関連していますか?
Ricardo Freitas

Fetchは、XMLHttpRequestデータのURLが単なるURL であるため、代わりに使用できる最新のajaxメソッドです。ajaxを使用してそのリソースをフェッチでき、blob、arraybuffer、テキストのいずれにするかを決定するオプションが用意されています
エンドレス

1
@Endless 'fetch()'ローカルのbase64文字列...本当に賢いハック!
Diego ZoracKy 2017年

1
すべてのフェッチ実装で普遍的にサポートされているわけblob:data:はないことに注意してください。私たちは以来、私たちは、このアプローチを使用して知っている:我々は唯一のモバイルブラウザ(WebKitの)を扱うが、例えばエッジ、ITTはサポート行いませんdeveloper.mozilla.org/en-US/docs/Web/API/...
oligofren

19

@Stoiveと@ vava720のおかげで、この2つを組み合わせ、非推奨のBlobBuilderとArrayBufferの使用を避けました

function dataURItoBlob(dataURI) {
    'use strict'
    var byteString, 
        mimestring 

    if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
        byteString = atob(dataURI.split(',')[1])
    } else {
        byteString = decodeURI(dataURI.split(',')[1])
    }

    mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0]

    var content = new Array();
    for (var i = 0; i < byteString.length; i++) {
        content[i] = byteString.charCodeAt(i)
    }

    return new Blob([new Uint8Array(content)], {type: mimestring});
}

12

進化する標準は、Mozillaが推測するのを危険にさらしたため、canvas.getAsFile()ではなくcanvas.toBlob()のように見えます。

私はまだそれをサポートしているブラウザを見ていません:(

この素晴らしいスレッドをありがとう!

また、受け入れられた回答を試す人はBlobBuilderに注意する必要があります。サポートが制限されている(名前空間に対応している)ためです。

    var bb;
    try {
        bb = new BlobBuilder();
    } catch(e) {
        try {
            bb = new WebKitBlobBuilder();
        } catch(e) {
            bb = new MozBlobBuilder();
        }
    }

BlobBuilderに別のライブラリのポリフィルを使用していましたか?


私はポリフィルなしでChromeを使用しており、名前空間に出くわしたことを覚えていません。私は熱心に予想されるcanvas.toBlob()-それはよりもはるかに適切と思われますgetAsFile
Stoive

1
BlobBuilder支持されて非難されているようですBlob
sandstrom

BlobBuilderは非推奨であり、このパターンはひどいです。より良いでしょう:window.BlobBuilder =(window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder); 入れ子になったtryキャッチは本当に醜いので、利用可能なビルダーがない場合はどうなりますか?
Chris Hanson

これはひどいですか?例外がスローされ、1)BlobBuilderが存在しない場合、何も起こらず、次のブロックが実行されます。2)存在するが例外がスローされる場合、それは非推奨であり、とにかく使用すべきではないため、次のtryブロックに進みます。理想的には、Blobが最初にサポートされているかどうかを確認し、この前にtoBlobサポートを確認します
TaylorMac

5
var BlobBuilder = (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder);

トライキャッチなしで使用できます。

check_caに感謝します。すごい仕事。


1
ブラウザーが非推奨のBlobBuilderをサポートしている場合でも、これはエラーをスローします。ブラウザは、新しいメソッドをサポートしていても、サポートしている場合は古いメソッドを使用します。これは望ましくありません。以下のChris Boscoのアプローチを参照してください
TaylorMac

4

Stoiveによる元の答えは、Blobに対応するように最後の行を変更することで簡単に修正できます。

function dataURItoBlob (dataURI) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs
    var byteString;
    if (dataURI.split(',')[0].indexOf('base64') >= 0)
        byteString = atob(dataURI.split(',')[1]);
    else
        byteString = unescape(dataURI.split(',')[1]);
    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to an ArrayBuffer
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    return new Blob([ab],{type: mimeString});
}

3

これがストイブの答えの ES6バージョンです。

export class ImageDataConverter {
  constructor(dataURI) {
    this.dataURI = dataURI;
  }

  getByteString() {
    let byteString;
    if (this.dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(this.dataURI.split(',')[1]);
    } else {
      byteString = decodeURI(this.dataURI.split(',')[1]);
    }
    return byteString;
  }

  getMimeString() {
    return this.dataURI.split(',')[0].split(':')[1].split(';')[0];
  }

  convertToTypedArray() {
    let byteString = this.getByteString();
    let ia = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return ia;
  }

  dataURItoBlob() {
    let mimeString = this.getMimeString();
    let intArray = this.convertToTypedArray();
    return new Blob([intArray], {type: mimeString});
  }
}

使用法:

const dataURL = canvas.toDataURL('image/jpeg', 0.5);
const blob = new ImageDataConverter(dataURL).dataURItoBlob();
let fd = new FormData(document.forms[0]);
fd.append("canvasImage", blob);

3

ありがとう!このソリューションの@steovi。

ES6バージョンにサポートを追加し、unescapeからdataURIに変更しました(unescapeは非推奨です)。

converterDataURItoBlob(dataURI) {
    let byteString;
    let mimeString;
    let ia;

    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(dataURI.split(',')[1]);
    } else {
      byteString = encodeURI(dataURI.split(',')[1]);
    }
    // separate out the mime component
    mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to a typed array
    ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }
    return new Blob([ia], {type:mimeString});
}

1

シンプルにする:D

function dataURItoBlob(dataURI,mime) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs

    var byteString = window.atob(dataURI);

    // separate out the mime component


    // write the bytes of the string to an ArrayBuffer
    //var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // write the ArrayBuffer to a blob, and you're done
    var blob = new Blob([ia], { type: mime });

    return blob;
}

-2

toDataURLは文字列を提供し、その文字列を非表示の入力に置くことができます。


例を挙げていただけますか?私はbase64文字列をアップロードしたくありません(これは何を<input type=hidden value="data:..." />するかです)、ファイルデータをアップロードしたいと思います(これらにプロパティ<input type="file" />を設定することを許可されていないことを除いて、何をするかのvalueように)。
ストイブ

これは回答ではなくコメントである必要があります。答えを適切に説明してください。@Cat Chen
ラッキー

-5

私はRavinder Payalとまったく同じ問題を抱えていましたが、その答えを見つけました。これを試して:

var dataURL = canvas.toDataURL("image/jpeg");

var name = "image.jpg";
var parseFile = new Parse.File(name, {base64: dataURL.substring(23)});

2
Parse.comの使用を本当に提案していますか?あなたの答えは依存関係を必要とすることに言及する必要があります!
Pierre Maoui 2016

2
WTF?base64コードの画像をPARSEサーバーにアップロードしてダウンロードするのはなぜですか?base64をサーバーに直接アップロードできる場合、base64文字列または画像ファイルをアップロードするには同じデータが必要です。そして、単にユーザーに画像をダウンロードしてもらいたい場合は、これを使用できますwindow.open(canvas.toDataURL("image/jpeg"))
Ravinder Payal '19 / 07/19
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.