簡體   English   中英

在Javascript中加載圖像時,iPad / iPhone瀏覽器崩潰

[英]iPad/iPhone browser crashing when loading images in Javascript

我正在嘗試在Safari中構建一個模仿iPad照片應用程序的圖庫。 它工作得很好,除了我通過將它們添加到DOM或創建新的Image對象加載超過6MB左右的圖像,新圖像停止加載或瀏覽器崩潰。 這個問題已經足夠普遍(其他所有人都遇到了相同的限制),我已經排除了我的Javascript代碼作為罪魁禍首。

鑒於您可以在元素中或通過瀏覽器內媒體播放器流式傳輸多於幾MB,這個限制似乎是不必要的,並且應該有某種可用的解決方法。 也許是通過釋放記憶或其他東西。

我也遇到過UIWebView的這個參考

“JavaScript分配也限制在10 MB。如果超過JavaScript的總內存分配限制,Safari會引發異常。”

這與我看到的相當匹配。 是否可以在Javascript中解除分配對象,或者Safari / UIWebView是否保持運行總計並且永遠不會放手? 或者,是否有任何解決方法以另一種方式加載數據而不會消耗這10MB?

更新:我認為有一種更簡單的方法可以做到這一點,具體取決於您的應用程序。 如果您只需要一個<img>元素或Image對象(或者兩個,如果您需要動畫或過渡,則可以是'this'圖像和'next'圖像)而只需更新.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 exectution。 但是在重置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()

編寫此代碼是為了測試我的解決方案,因此您必須弄清楚如何將其應用於您自己的代碼。 代碼分為三部分,我將在下面解釋,但唯一非常重要的部分是imgStoredImage.src = strNullImage;

loadNextImage()只是加載一個新圖像並調用shrinkImages() 它還分配一個onload事件,用於開始加載另一個圖像的過程(bug:我應該稍后清除此事件,但我不是)。

waitAndReload()僅用於允許圖像時間顯示在屏幕上。 移動Safari非常慢並且顯示大圖像,因此在加載圖像以繪制屏幕后需要時間。

shrinkImages()遍歷所有先前加載的圖像(活動圖像除外)並將.src更改為dataurl地址。

我在這里使用dataurl的文件夾圖像(這是我能找到的第一個dataurl圖像)。 我正在使用它,所以你可以看到腳本工作。 你可能想要使用透明的gif代替,所以請使用這個數據url字符串: data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

6.5MB(iPad)/ 10MB(iPhone)下載限制是根據用於通過其src屬性設置圖像的圖像元素的數量計算的。 移動safari似乎不區分從緩存或通過網絡加載的圖像。 將圖像注入dom也無關緊要。

該解決方案的第二部分是移動safari似乎能夠通過“background-image”css屬性加載無限數量的圖像。

這個概念證明使用了一個預先安置的池,它在成功下載后設置了背景圖像屬性。 我知道它不是最佳的,並沒有將使用過的Image下載器返回到池中,但我相信你明白了:)

這個想法改編自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> 

我很幸運,從Steve Simitzis和Andrew的建議開始。

我的項目:

基於PhoneGap的應用程序,包含6個主要部分,以及約45個子部分,其中包含2到7個圖像的jquery循環庫,每個640 x 440(共215個圖像)。 起初我使用ajax來加載頁面片段,但我已經切換到一個單頁的網站,所有部分都隱藏起來直到需要。

最初,在經歷了大約20個畫廊之后,我得到了記憶警告1,然后是2,然后是崩潰。

在將所有圖像作為背景應用於div之后,我可以在崩潰之前通過應用程序中的更多畫廊(大約35個),但是在去往之前訪問過的畫廊之后,它最終會失敗。

似乎對我有用的解決方案是將背景圖像URL存儲在div的title屬性中,並將所有背景圖像設置為空白gif。 有215多張圖片,為了方便和快速參考,我想在html中保留url。

當按下一個子導航按鈕時,我將css背景圖像重寫為包含在div標題標簽中的正確來源,僅用於顯示的圖庫。 這使我免於必須做任何花哨的JavaScript來存儲正確的源圖像。

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

當按下新的子導航按鈕時,我將最后一個畫廊div的背景圖像重寫為空白GIF。 所以,除了界面gfx,我總是只有2-7張圖像'活躍'。 除了我添加的包含圖像的任何內容之外,我只使用這種“ondemand”技術將標題與background-image交換。

現在看來我可以無限期地使用該應用程序而不會崩潰。 不知道這是否會對其他人有所幫助,它可能不是最優雅的解決方案,但它為我提供了一個解決方案。

到目前為止,我很幸運使用<div>標簽而不是<img>標簽並將圖像設置為div的背景圖像。

總而言之,這很瘋狂。 如果用戶對更多圖像內容做出了肯定的請求,那么Safari就沒有理由不允許您加載它。

我無法找到解決方案。 以下是我嘗試過的幾種方法,但都失敗了:

  • 只需使用div.style.backgroundImage = "url("+base64+")"更改DIV的背景

  • 改變了.src的圖像的使用img.src = base64

  • 刪除舊的並使用removeChild( document.getElementById("img") ); document.body.appendChild( newImg )添加新圖像removeChild( document.getElementById("img") ); document.body.appendChild( newImg ) removeChild( document.getElementById("img") ); document.body.appendChild( newImg )

  • 與上面相同,但在新圖像上具有隨機高度

  • 刪除圖像並將其添加為HTML5畫布對象。 也不起作用,因為新的Image(); 必須創建,見*

  • 在啟動時,創建了一個新的Image()對象,讓我們稱之為容器。 將圖像顯示為<canvas> ,每次圖像更改時,我都會更改容器的.src並使用ctx.drawImage( container, 0,0 )ctx.drawImage( container, 0,0 )布。

  • 與前一個相同,但實際上沒有重繪畫布。 只需更改Image()對象的src占用內存。

我注意到一件奇怪的事情:即使沒有顯示圖像,也會發生錯誤! 例如,這樣做時:

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

每隔5秒,沒有別的,沒有加載或顯示圖像,當然包裹在一個對象中,也會在一段時間后崩潰內存!

在rails應用程序上,我懶得加載數百張中型照片(無限卷軸)並且不可避免地在iPhone上達到了10Mb的限制。 我嘗試將圖形加載到畫布(新的Image,src =,然后是Image.onload),但仍然達到了相同的限制。 我也嘗試更換img src並將其移除(當它離開可視區域時)但仍然沒有雪茄。 最后,將所有帶有照片的img標簽切換為照片作為背景。

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

我現在可以在一頁上加載超過40Mb的照片,而不是撞牆。 然而,我遇到了一個奇怪的問題,一些css背景圖形無法顯示。 一個快速的js線程修復了。 每隔3秒設置div的css bg屬性。

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

您可以在http://fotodeck.com上看到這一點。 在iphone / ipad上查看。

內存存在問題,解決這個問題的方法非常簡單。 1)將所有縮略圖放在畫布上。 您將創建許多新的Image對象並將它們繪制到畫布中,但如果您的縮略圖非常小,那么您應該沒問題。 對於要顯示實際大小圖像的容器,只創建一個Image對象並重用此對象,並確保也將其繪制到畫布中。 因此,每次用戶單擊縮略圖時,您都將更新主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個縮略圖,每個縮略圖都像15kb。 真實的圖像每個都是1 MB。

當我們經常嘗試刷新圖像時,我在iPad上遇到了Javascript內存不足,比如每隔幾秒鍾。 這是一個經常刷新的錯誤,但Safari崩潰到主屏幕。 一旦我控制了刷新時間,Web應用程序運行正常。 似乎Javascript引擎無法快速跟上垃圾收集以丟棄所有舊圖像。

在iPhone上渲染大量圖像時,我也遇到了類似的問題。 在我的情況下,在列表中顯示甚至50個圖像足以使瀏覽器崩潰或偶爾崩潰整個操作系統。 出於某種原因,呈現在頁面上的任何圖像都不會被垃圾收集,即使在匯集和回收幾個屏幕上的DOM元素或使用圖像作為背景圖像屬性時也是如此。 即使直接將圖像顯示為Data-URI也足以計入限制。

解決方案最終變得相當簡單 - 在列表項上使用position: absolute允許它們被垃圾收集得足夠快,不會遇到內存限制。 這仍然涉及在任何時刻在DOM中只有大約20-30個圖像,通過滾動位置創建和刪除項目的DOM節點最終完成了訣竅。

它似乎特別依賴於將webkit-transform':'scale3d()應用於DOM中圖像的任何祖先。 相對流動一個非常高的DOM並在GPU上渲染它會導致webkit渲染器中的內存泄漏,我想?

我也在Chrome中運行類似的問題,開發一個擴展程序,在同一頁面(實際上是彈出窗口)中加載圖像,用新的圖像替換舊圖像。 舊圖像使用的內存(從DOM中刪除)永遠不會被釋放,在短時間內消耗所有PC內存。 用CSS嘗試了各種技巧,沒有成功。 使用比PC更少的內存硬件,比如iPad,這個問題自然而然地出現了。

我提交了一個jQuery的bug作為jQuery trys來處理內存泄漏......所以我認為這是一個bug。 希望團隊能夠盡快在Mobile Safari中提出一些簡潔明智的方法來解決這個問題。

http://dev.jquery.com/ticket/6944#preview

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM