[英]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中提出一些簡潔明智的方法來解決這個問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.