使用Javascript加载图片时,iPad / iPhone浏览器崩溃

我正在尝试在Safari中构build一个模仿iPad照片应用程序的图库。 它完美的工作,除了一旦我加载超过6MB左右的图像或者通过将它们添加到DOM或创build新的图像对象,新的图像要么停止加载或浏览器崩溃。 这个问题已经足够普及了(其他人都碰到同样的限制),我排除了我的Javascript代码作为罪魁祸首。

考虑到您可以在元素或浏览器内的媒体播放器中传输多于几MB的内容,这种限制似乎是不必要的,应该有一些解决方法可用。 也许通过释放内存或其他东西。

我也遇到了这个UIWebView的参考 。

“JavaScript的分配也被限制在10 MB,如果你超过JavaScript的总内存分配限制,Safari会引发exception。

这与我所见相当相符。 是否有可能在Javascript中释放对象,还是Safari / UIWebView保持运行总量,永不放弃? 另外,是否有任何解决方法来加载数据的另一种方式,不吃这个10MB?

更新:我认为有一个更简单的方法来做到这一点,根据您的应用程序。 如果只有一个<img>元素或Image对象(或者两个,如“this”图像,如果需要animation或转换,则为“next”图像),而.src简单地更新.src .width.height等,你不应该接近10MB的限制。 如果您想要制作轮播应用程序,则必须先使用较小的占位符。 你可能会发现这个技术可能更容易实现。


我想我可能已经find了解决办法。

基本上,你需要做一些更深层次的图像pipe理,并明确地缩小你不需要的图像。 你通常通过使用document.removeChild(divMyImageContainer)或者$("myimagecontainer").empty()或者你有什么,但是在移动版Safari上, 浏览器根本就不会释放内存。

相反,你需要更新图像本身,所以它占用很less的内存; 你可以通过改变图像的src属性来做到这一点。 我所知道的最快的方法是使用数据URL 。 所以不要这样说:

 myImage.src="/path/to/image.png" 

…而是说:

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

下面是一个testing来演示它的工作。 在我的testing中,我的大型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() 

这段代码是为了testing我的解决scheme而编写的,所以你必须弄清楚如何将它应用到你自己的代码中。 代码有三个部分,我将在下面解释,但唯一真正重要的部分是imgStoredImage.src = strNullImage;

loadNextImage()只是加载一个新的图像,并调用shrinkImages() 。 它还分配一个onload事件,用于开始加载另一个图像的过程(错误:我应该稍后清除这个事件,但我没有)。

waitAndReload()仅在这里允许图像时间显示在屏幕上。 移动Safari非常慢,并显示大图像,所以它需要时间后,图像已经加载画画面。

shrinkImages()会遍历所有先前加载的图像(活动的图像除外),并将.src更改为dataurl地址。

我在这里使用了dataurl的文件夹映像(这是我能find的第一个dataurl映像)。 我正在使用它,所以你可以看到脚本工作。 你可能会想使用一个透明的gif,所以使用这个数据的urlstring,而不是: data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

6.5MB(iPad)/ 10MB(iPhone)下载限制是根据用于通过src属性设置图像的图像元素数量来计算的。 移动Safari浏览器似乎不区分从caching或通过networking加载的图像。 图像是否被注入了dom也没有关系。

解决scheme的第二部分是移动Safari浏览器似乎可以通过“background-image”css属性加载无限数量的图像。

这个概念certificate使用一组预处理器,它设置了一次成功下载的背景图像属性。 我知道这不是最佳的,不会将使用过的图片下载器返回到游戏池,但是我确信你明白了:)

这个想法是从罗布拉普拉卡的原始的解决方法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 = '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的build议开始。

我的项目:

基于PhoneGap的应用程序,包含6个主要部分,以及约45个子部分,其中包含2到7个图像的jquery循环库,每个图像640 x 440(总共215个图像)。 起初,我使用Ajax来加载页面片段,但我已经切换到一个页面的网站,所有部分隐藏,直到需要。

最初,经过大约20家画廊,我得到了记忆警告1,然后2,然后崩溃。

在将所有图像作为背景图像应用到div后,我可以在应用程序崩溃之前通过更多的画廊(约35),但是在去过之前参观过的画廊之后,最终会失败。

似乎为我工作的解决scheme是将背景图像URL存储在div的title属性中,并将所有背景图像设置为空白gif。 有215个图像,为了方便和快速参考,我想把url保存在html中。

当按下一个子导航button时,我只是将所显示的图库重写为包含在div标题标记中的正确源的css背景图像。 这节省了我不得不做任何花哨的JavaScript来存储正确的源图像。

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

当按下新的导航button时,我将最后一个图库div的背景图像改写为空白gif。 所以,除了gfx的界面外,我一直只有2-7张“活跃”的图片。 与其他任何我添加包含图像,我只是使用这个“ondemand”技术来交换标题与背景图像。

现在看来,我可以无限期地使用应用程序,没有崩溃。 不知道这是否会帮助其他人,也可能不是最优雅的解决scheme,但它为我提供了一个解决scheme。

到目前为止,我有运气使用<div>标签而不是<img>标签,并将图像设置为div的背景图像。

总而言之,这是疯狂的。 如果用户正在对更多图片内容进行肯定的请求,那么Safari没有理由不允许您加载它。

在Rails应用程序中,我懒加载数百个中等大小的照片(无限滚动),并不可避免地在iPhone上达到10Mb的限制。 我试图加载graphics到一个canvas(新的图像,src =,然后Image.onload),但仍然达到相同的限制。 我也尝试更换img src并将其删除(当它出现可视区域时),但仍然没有雪茄。 最后,把所有的img标签w / div的w /照片作为背景去掉了。

  $.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背景graphics显示失败。 一个快速的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上。

我无法find解决scheme。 这里有几个我尝试过的方法,都失败了:

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

  • 使用img.src = base64更改了图像的img.src = base64

  • 删除旧的,并添加新的图像使用removeChild( document.getElementById("img") ); document.body.appendChild( newImg ) removeChild( document.getElementById("img") ); document.body.appendChild( newImg )

  • 与上面相同,但在新图像上具有随机高度

  • 删除并添加图像作为HTML5canvas对象。 因为一个新的Image(); 必须创build,请参阅*

  • 在启动时,创build了一个新的Image()对象,我们称之为容器。 将图像显示为<canvas> ,每次图像改变时,我都会改变容器的.src并使用ctx.drawImage( container, 0,0 )ctx.drawImage( container, 0,0 )布。

  • 和以前一样,但是实际上没有重绘canvas。 只需更改Image()对象的src使用内存。

我注意到一个奇怪的事情:即使图像不显示,也会发生错误! 例如,当这样做时:

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

每隔5秒钟,没有别的,不加载或显示图像,当然包裹在一个对象,一段时间后也崩溃的内存!

当我们试图刷新一个图像的时候,我在iPad上遇到了内存不足的问题,就像每隔几秒钟一样。 这是一个经常刷新的错误,但Safari浏览器崩溃到主屏幕。 一旦我得到了控制刷新时间,networking应用程序运行良好。 似乎JavaScript引擎不能赶上垃圾收集足够快,丢弃所有的旧图像。

内存有问题,解决这个问题的方法很简单。 1)把所有的缩略图放在canvas上。 您将创build大量新的图像对象并将其绘制到canvas中,但是如果您的缩略图非常小,则应该没问题。 对于要显示真实大小图像的容器,只能创build一个Image对象,并重用此对象,并确保将其绘制到canvas中。 所以,每次用户点击缩略图,你都会更新你的主要Image对象。 不要在页面中插入IMG标签。 插入CANVAS标签,而不是正确的缩略图和主显示容器的宽度和高度。 如果你插入太多的IMG标签,iPad会哭泣。 所以,避免他们! 只插入canvas。 然后,您可以从页面中findcanvas对象并获取上下文。 所以每次用户点击一个缩略图时,你都会得到主图像(真实尺寸图像)的src,并将其绘制到主canvas上,重新使用主要的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; 

我创build了200个缩略图,每个都像15kb。 真实的图像是每个1 MB。

我也有类似的问题,同时在iPhones上呈现大量的图像列表。 就我而言,在列表中甚至显示50个图像足以导致浏览器崩溃或偶尔崩溃整个操作系统。 出于某种原因,任何呈现在页面上的图像都不会被垃圾回收,即使只是汇集和回收了几个屏幕DOM元素或使用图像作为背景图像属性。 即使直接将图像显示为Data-URI也足以计入极限。

这个解决scheme最终是相当简单的 – 使用position: absolute列表上的项目允许他们收集垃圾足够快,不会遇到内存限制。 这仍然涉及在任何时候只有约20-30的图像在DOM中,通过滚动位置创build和删除项目的DOM节点终于做了诀窍。

它似乎特别依赖于webkit-transform':'scale3d()应用于DOM中图像的任何祖先。 相对而言,stream动一个非常高的DOM并在GPU上渲染它会让webkit渲染器产生内存泄漏,我想呢?

我提交了一个与jQuery的错误,作为jQuery的处理内存泄漏…所以我会考虑这个错误。 希望在不久的将来,移动Safari能够提供一些简洁而巧妙的处理这个问题的方法。

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

我在Chrome中也遇到了类似的问题,开发了一个扩展程序,可以在同一页面(实际上是popup窗口)中将图像加载到新图像上。 旧图像(从DOM中删除)使用的内存永远不会释放,在短时间内消耗所有的PC内存。 已经尝试了CSS的各种技巧,没有成功。 使用比PC更less内存的硬件,比如iPad,这个问题自然会出现得更早。