web-dev-qa-db-ja.com

JavaScriptで画像をロードするとiPad / iPhoneブラウザーがクラッシュする

IPadの写真アプリを模倣したSafariで画像ギャラリーを構築しようとしています。完全に機能しますが、6MB以上の画像をDOMに追加するか、新しい画像オブジェクトを作成して読み込むと、新しい画像の読み込みが停止するか、ブラウザがクラッシュします。この問題は十分に広まっているので(他のすべての人が同じ制限に達しています)、Javascriptコードを犯人として除外しました。

要素内またはブラウザ内のメディアプレーヤーを介して数MBをはるかに超えてストリーミングできることを考えると、この制限は不要と思われ、何らかの回避策が利用できるはずです。おそらく、メモリなどを解放することによって。

私もこれに遭遇しました IWebViewのリファレンス

「JavaScriptの割り当ても10 MBに制限されています。JavaScriptの合計メモリ割り当てのこの制限を超えると、Safariは例外を発生させます。」

これは私が見ているものとかなり一致しています。 Javascriptでオブジェクトの割り当てを解除することはできますか、それともSafari/UIWebViewは合計を維持し続けますか?あるいは、この10MBを使い果たさない別の方法でデータをロードする回避策はありますか?

53
Steve Simitzis

更新:アプリケーションによっては、もっと簡単な方法があると思います。複数の画像を使用する代わりに、1つの_<img>_要素またはImageオブジェクト(または、アニメーションまたはトランジションが必要な場合は 'this'画像と 'next'画像のように2つ) _.src_、_.width_、_.height_などを更新するだけで、10MBの制限に近づかないでください。カルーセルアプリケーションを実行する場合は、最初に小さいプレースホルダーを使用する必要があります。この手法を実装する方が簡単な場合があります。


私は実際にこれに対する回避策を見つけたかもしれないと思います。

基本的に、より深い画像管理を行い、不要な画像を明示的に縮小する必要があります。通常、document.removeChild(divMyImageContainer)または$("myimagecontainer").empty()を使用してこれを行いますが、Mobile Safariではこれは何もしません。ブラウザは単にメモリの割り当てを解除することはありません。

代わりに、画像自体を更新して、メモリをほとんど消費しないようにする必要があります。画像のsrc属性を変更することでそれを行うことができます。私が知っている最も簡単な方法は、 データURL を使用することです。だからこれを言う代わりに:

_myImage.src="/path/to/image.png"
_

...代わりにこれを言ってください:

_myImage.src="data:image/gif;base64,AN_ENCODED_IMAGE_DATA_STRING"
_

以下は、それが機能することを実証するためのテストです。私のテストでは、750KBの大きなイメージが最終的にブラウザーを強制終了し、すべてのJS実行を停止しました。しかし、srcをリセットした後、画像のインスタンスを170回以上読み込むことができました。コードの動作の説明も以下にあります。

_var strImagePath = "http://path/to/your/gigantic/image.jpg";
var arrImages = [];
var imgActiveImage = null
var strNullImage = "data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7";
var intTimesViewed = 1;
var divCounter = document.createElement('h1');
document.body.appendChild(divCounter);

var shrinkImages = function() {
    var imgStoredImage;
    for (var i = arrImages.length - 1; i >= 0; i--) {
        imgStoredImage = arrImages[i];
        if (imgStoredImage !== imgActiveImage) {
            imgStoredImage.src = strNullImage;
        }
    }
};
var waitAndReload = function() {
    this.onload = null;
    setTimeout(loadNextImage,2500);
};
var loadNextImage = function() {
    var imgImage = new Image();
    imgImage.onload = waitAndReload;
    document.body.appendChild(imgImage);
    imgImage.src = strImagePath + "?" + (Math.random() * 9007199254740992);
    imgActiveImage = imgImage;
    shrinkImages()
    arrImages.Push(imgImage);
    divCounter.innerHTML = intTimesViewed++;
};
loadNextImage()
_

このコードは、私のソリューションをテストするために書かれたものなので、自分のコードに適用する方法を理解する必要があります。コードは3つの部分に分かれていますが、以下で説明しますが、本当に重要な部分は_imgStoredImage.src = strNullImage;_だけです

loadNextImage()は、単に新しいイメージをロードして、shrinkImages()を呼び出します。また、別の画像の読み込みプロセスを開始するために使用されるonloadイベントを割り当てます(バグ:このイベントを後でクリアする必要がありますが、そうではありません)。

waitAndReload()は、画像の時間を画面に表示できるようにするためだけのものです。 Mobile Safariは非常に遅く、大きな画像を表示するため、画像を読み込んで画面をペイントするまで時間がかかります。

shrinkImages()は、以前にロードされたすべてのイメージ(アクティブなイメージを除く)を通過し、_.src_をdataurlアドレスに変更します。

ここでは、dataurlにファイルフォルダーイメージを使用しています(これは私が見つけることができる最初のdataurlイメージでした)。スクリプトが機能しているのを確認できるように、単純に使用しています。おそらく、代わりに透過gifを使用する必要があるので、代わりに次のデータURL文字列を使用してください:_data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==_

14
Andrew

6.5MB(iPad)/ 10MB(iPhone)のダウンロード制限は、srcプロパティで画像を設定するために使用される画像要素の数に基づいて計算されます。モバイルサファリは、キャッシュまたはネットワーク経由で読み込まれた画像を区別しないようです。また、画像がDOMに注入されるかどうかは関係ありません。

ソリューションの2番目の部分は、モバイルサファリが「background-image」cssプロパティを介して無制限の数の画像をロードできるように見えることです。

この概念実証では、正常にダウンロードされると背景画像のプロパティを設定するプリキャッシュのプールを使用します。私はそれが最適ではなく、使用されたイメージダウンローダーをプールに返さないことを知っていますが、あなたはアイデアを得ると確信しています:)

このアイデアは、Rob Laplacaの元のキャンバスの回避策から採用されています http://roblaplaca.com/blog/2010/05/05/ipad-safari-image-limit-workaround/

<!DOCTYPE html>
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>iPad maximum number of images test</title> 
<script type="text/javascript">
    var precache = [
        new Image(),
        new Image(),
        new Image(),
        new Image()
    ];

    function setImage(precache, item, waiting) {
        precache.onload = function () {
            item.img.style.backgroundImage = 'url(' + item.url + ')';
            if (waiting.length > 0) {
                setImage(precache, waiting.shift(), waiting);
            }
        };
        precache.src = item.url;
    }

    window.onload = function () {
        var total = 50,
            url = 'http://www.roblaplaca.com/examples/ipadImageLoading/1500.jpg',
            queue = [],
            versionUrl,
            imageSize = 0.5,
            mb,
            img;

        for (var i = 0; i < total; i++) {
            mb = document.createElement('div');
            mb.innerHTML = ((i + 1) * imageSize) + 'mb';
            mb.style.fontSize = '2em';
            mb.style.fontWeight = 'bold';

            img = new Image();
            img.width = 1000;
            img.height = 730;
            img.style.width = '1000px';
            img.style.height = '730px';
            img.style.display = 'block';

            document.body.appendChild(mb);
            document.body.appendChild(img);


            queue.Push({
                img: img,
                url: url + '?ver=' + (i + +new Date())
            });
        }

        //
        for (var p = 0; p < precache.length; p++) {
            if (queue.length > 0) {
                setImage(precache[p], queue.shift(), queue);
            }
        }
    };
</script>
</head> 
<body> 
<p>Loading (roughly half MB) images with the <strong>img tag</strong></p> 
</body> 
</html> 
12
Alex

私は、Steve SimitzisとAndrewの提案から始まりました。

私のプロジェクト:

PhoneGapベースのアプリで、メインセクションが6つ、サブセクションが約45個あり、それぞれに640 x 440(合計215個以上の画像)の2〜7個の画像のjqueryサイクルギャラリーがあります。最初はページフラグメントを読み込むためにajaxを使用していましたが、その後、必要になるまですべてのセクションを非表示にして1ページのサイトに切り替えました。

最初は、約20のギャラリーを通過した後、メモリ警告1、2、それからクラッシュが発生していました。

背景として画像を適用してすべての画像をdivにした後、クラッシュする前にアプリでより多くのギャラリー(約35)を通過できましたが、以前に訪れたギャラリーに行くと、最終的に失敗します。

私のために働いていると思われる解決策は、divのtitle属性に背景画像のURLを保存し、すべての背景画像を空のgifに設定することです。 215以上の画像を使用して、簡単かつ迅速に参照できるように、URLをHTMLのどこかに保持したいと考えました。

サブナビゲーションボタンが押されると、表示しているギャラリーのみのために、divのタイトルタグに含まれる正しいソースにcss背景画像を書き換えます。これにより、適切なソース画像を保存するために派手なjavascriptを実行する必要がなくなりました。

var newUrl = $(this).attr('title');
$(this).css('background-image', 'url('+newUrl+')'); 

新しいサブナビゲーションボタンを押すと、最後のギャラリーdivの背景画像を空のgifに書き換えます。そのため、インターフェイスgfxは別として、常に「アクティブ」な画像は2〜7個しかありません。追加した画像を含むものはすべて、この「オンデマンド」技術を使用して、タイトルを背景画像と入れ替えます。

これで、クラッシュすることなくアプリを無期限に使用できるようになりました。これが他の誰かに役立つかどうかわからない、それは最もエレガントな解決策ではないかもしれないが、それは私に修正を提供しました。

6
Transoptic

これまでのところ、<div>タグの代わりに<img>タグを使用し、画像をdivの背景画像として設定することができました。

全体として、それはクレイジーです。ユーザーがより多くの画像コンテンツに対して肯定的な要求を行っている場合、Safariでの読み込みを許可しない理由はありません。

5
Steve Simitzis

これに対する解決策を見つけることができませんでした。ここに私が試したいくつかの方法があり、それらはすべて失敗しました:

  • div.style.backgroundImage = "url("+base64+")"を使用してDIVの背景を単純に変更しました

  • _.src_を使用して、イメージの_img.src = base64_を変更しました

  • removeChild( document.getElementById("img") ); document.body.appendChild( newImg )を使用して古いイメージを削除し、新しいイメージを追加しました

  • 上記と同じですが、新しい画像の高さがランダムです

  • 画像を削除して、HTML5キャンバスオブジェクトとして追加します。また、新しいImage();を作成する必要があるため、機能しません。*を参照してください

  • 起動時に、新しいImage()オブジェクトを作成し、コンテナと呼びましょう。画像を_<canvas>_として表示し、画像が変更されるたびに、コンテナの_.src_を変更し、ctx.drawImage( container, 0,0 )を使用してキャンバスを再描画します。

  • 前と同じですが、実際にキャンバスを再描画しません。単にImage()オブジェクトのsrcを変更すると、メモリが消費されます。

私が気づいた奇妙なこと:画像が表示されていなくてもバグが発生します!たとえば、これを行う場合:

_var newImg = new Image( 1024, 750 );
newImg.src = newString; // A long base64 string
_

5秒ごとに、そしてもちろん、オブジェクトに包まれた画像の読み込みや表示が行われないと、しばらくするとメモリもクラッシュします!

3
Louis B.

Railsアプリでは、数百枚の中サイズの写真(無限スクロール)を遅延ロードし、必然的にiPhoneの10Mbの制限に達しました。グラフィックをキャンバスにロードしようとしました(新しいイメージ、 src =、それからImage.onload)でも同じ制限に達します。また、img srcを置き換えて削除しました(表示可能領域から出たとき)が、まだ葉巻はありませんでした。/divは、背景としての写真がトリックを行いました。

      $.ajax({
        url:"/listings/"+id+"/big",
        async:true,
        cache:true,
        success:function(data, textStatus, XMLHttpRequest) {
          // detect iOS
          if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i) || navigator.userAgent.match(/iPad/i)) {
            // load html into data
            data = $(data);
            // replace img w/ div w/ css bg
            data.find(".images img").each(function() { 
              var src = $(this).attr("src").replace(/\s/g,"%20");
              var div = $("<div>"); 
              div.css({width:"432px",height:"288px",background:"transparent url("+src+") no-repeat"}); 
              $(this).parent().append(div); 
              $(this).remove(); 
            }); 
            // remove graphic w/ dynamic dimensions
            data.find(".logo").remove();
          }
          // append element to the page
          page.append(data);
        }
      });

1ページに40Mbをはるかに超える写真を読み込むことができます。壁にぶつかる必要はありません。ただし、CSSの背景グラフィックの一部が表示されないという奇妙な問題が発生しました。クイックjsスレッドがそれを修正しました。 divのcss bgプロパティを3秒ごとに設定します。

  setInterval(function() {
    $(".big_box .images div.img").each(function() {
      $(this).css({background:$(this).css("background")});
    });
  }, 3000);

この動作は http://fotodeck.com で確認できます。 iPhone/iPadで確認してください。

3
danlee

IPadでJavaScriptを使用すると、数秒ごとなど、非常に頻繁に画像を更新しようとしたときにメモリ不足が発生しました。それを頻繁に更新するのはバグでしたが、Safariはホーム画面にクラッシュしました。更新のタイミングを制御できたら、Webアプリは正常に機能しました。 Javascriptエンジンは、古い画像をすべて破棄するのに十分な速さでガベージコレクションに追いつかないように見えました。

2
David Bakkom

メモリに問題があり、この問題を解決する方法は非常に簡単です。 1)すべてのサムネイルをキャンバスに配置します。多数の新しいImageオブジェクトを作成してキャンバスに描画しますが、サムネイルが非常に小さい場合は問題ありません。実際のサイズの画像を表示するコンテナの場合、Imageオブジェクトを1つだけ作成し、このオブジェクトを再利用して、キャンバスにも描画するようにしてください。そのため、ユーザーがサムネイルをクリックするたびに、メインのImageオブジェクトを更新します。ページにIMGタグを挿入しないでください。代わりに、サムネイルとメインディスプレイコンテナの正しい幅と高さでCANVASタグを挿入します。挿入するIMGタグが多すぎると、iPadがファウルします。だから、それらを避けてください!!!キャンバスのみを挿入します。その後、ページからキャンバスオブジェクトを見つけて、コンテキストを取得できます。そのため、ユーザーがサムネイルをクリックするたびに、メイン画像(実サイズの画像)のsrcを取得してメインキャンバスに描画し、メインImageオブジェクトを再利用してイベントを発生させます。開始時に毎回イベントをクリアします。

mainDisplayImage.onload = null;
mainDisplayImage.onerror = null;

...

mainDisplayImage.onload = function() { ... Draw it to main canvas }
mainDisplayImage.onerror = function() { ... Draw the error.gif to main canvas }
mainDisplayImage.src = imgsrc_string_url;

200個のサムネイルを作成しましたが、それぞれ15 kbのようなものです。実際の画像はそれぞれ1 MB程度です。

2
Sergio

IPhoneで画像の大きなリストをレンダリングする際にも、同様の問題がありました。私の場合、リストに50枚の画像を表示するだけで、ブラウザがクラッシュしたり、場合によってはオペレーティングシステム全体がクラッシュしたりします。何らかの理由で、ページ上にレンダリングされた画像は、ほんの数個の画面上のDOM要素をプールおよびリサイクルしたり、画像をbackground-imageプロパティとして使用したりしても、ガベージコレクションされませんでした。データURIとして画像を直接表示するだけでも、制限にカウントするのに十分です。

解決策はやや単純になりました-リスト項目で_position: absolute_を使用すると、メモリ制限に達しないように十分速くガベージコレクションできます。これは、常にDOMに約20〜30個の画像しかなく、スクロールポジトンによってアイテムのDOMノードを作成および削除することに関係していました。

DOMの画像の祖先にwebkit-transform':'scale3d()を適用することに特に依存しているようです。非常に背の高いDOMを比較的流してGPUでレンダリングすると、Webkitレンダラーのメモリリークが発生します。

1
haversine

私はChromeも同じ問題で実行しています。同じページ(実際にはポップアップ)に画像を読み込む拡張機能を開発しています。古い画像を新しい画像に置き換えます。 (DOMから削除された)画像は決して解放されず、短時間ですべてのPCメモリを消費します。CSSでさまざまなトリックを試してみましたが、成功しませんでした。 。

0
Omiod