2010-06-06 113 views
50

我试图在Safari中构建一个模仿iPad照片应用程序的图像库。它的工作原理非常完美,除了一旦我将大于6MB的图像加载到DOM或创建新的图像对象后,新图像就会停止加载或浏览器崩溃。这个问题已经足够普及了(其他人都碰到了相同的限制),我排除了我的Javascript代码作为罪魁祸首。使用Javascript加载图像时,iPad/iPhone浏览器崩溃

鉴于您可以在元素中或通过浏览器内的媒体播放器流式传输超过几MB,此限制看起来没有必要,应该有一些解决方法可用。也许通过释放内存或其他东西。

我也碰到过这个reference for UIWebView

“JavaScript分配也被限制为10 MB。如果您超过JavaScript的总内存分配限制,Safari会引发异常。”

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

+0

你可以回到良好的旧C日,并使用“删除”来...错误...删除,你没有显示的图像:) – 2010-06-06 21:55:46

+42

嗯,这**吸**。我讨厌苹果。 – 2010-06-06 21:57:37

+1

将图像写入页面后,我认为不应使用相同的内存限制。我很确定我已经浏览了超过10MB图片的网页。所以我想问题是你如何使用JS来获取图像?你能用一些你如何加载它们的例子来更新你的问题吗? – Andrew 2010-06-06 23:20:18

回答

13

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


我想我实际上可能找到了解决这个问题的方法。

基本上,您需要做一些更深层次的图像管理,并明确缩小任何不需要的图像。您通常通过使用document.removeChild(divMyImageContainer)$("myimagecontainer").empty()或您有什么,但在移动Safari上这完全没有;浏览器根本就不会释放内存。

相反,您需要更新图像本身,因此占用的内存很少;你可以通过改变图像的src属性来做到这一点。我知道最快的方法是使用data 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() 

这段代码被写入测试我的解决方案,所以你必须弄清楚如何将它应用到你自己的代码中。代码分为三部分,我将在下面解释,但唯一真正重要的部分是imgStoredImage.src = strNullImage;

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

waitAndReload()只在这里允许图像时间显示在屏幕上。 Mobile Safari非常慢,并且显示大图像,因此在图像加载完成后需要花费时间来绘制屏幕。

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

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

+1

这可能会反复重新加载相同的图像,但它不适用于一系列70+图像。我仍然需要尝试使用Wolvrix的解决方案,但是我对苹果公司感到非常不满,因为这些奇怪的限制使得移动版Safari无法使用。如果移动Safari只是释放旧图像而不是保留它们直到页面重新加载,本质上会阻止加载新图像,那将会很不错。 – 2010-07-27 16:08:33

2

有内存问题,解决这个问题的方法很简单。 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。

+0

我没有用这种方法运气,fyi。 – 2010-07-20 02:41:56

+0

@Steve:我将在本周末之前在YouTube上发布一段视频。我会做一些解释以及我如何运作。一旦发布,我会通知你。 – Sergio 2010-07-22 12:17:26

5

到目前为止,我已经有幸使用<div>标签代替<img>标签并将图像设置为div的背景图像。

总而言之,这太疯狂了。如果用户对更多图片内容提出肯定要求,那么Safari不应允许您加载它。

2

当我们想每隔几秒钟刷新一次图像时,我在iPad上遇到内存不足的问题。这是一个经常刷新的错误,但Safari却撞到了主屏幕。一旦我掌握了更新时机,网络应用程序运行良好。似乎JavaScript引擎无法及时赶上垃圾收集,丢弃所有旧图像。

6

我有幸开始了Steve Simitzis和Andrew的建议。

我的项目:

基于PhoneGap的应用内具有6个主要部分,并且约45小节具有图像2和7之间的一个jquery循环库,每个640×440(215+图像共)。起初,我使用ajax来加载页面片段,但后来我切换到一个页面的网站,所有部分隐藏,直到需要。

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

将所有图像制作成背景图片后,我可以在应用程序崩溃之前通过更多画廊(约35个),但在去过以前访问过的画廊之后,最终会失败。

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

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

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

当按下新的子导航按钮时,我将最后一个图库div的背景图像重写为空白gif。所以,除了gfx接口之外,我始终只有2-7张“活动”图像。随着其他任何我添加包含图像,我只是使用这种“ondemand”技术来交换标题与背景图像。

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

+0

我猜你不会预载任何图像? – 2010-08-26 14:02:58

+0

我真的想试试这个。 – 2010-10-12 19:03:05

0

我提交了一个jQuery的错误,因为jQuery用来处理内存泄漏......所以我会考虑这个错误。希望这个团队能够在移动Safari中快速处理这个问题,并提出一些简洁明智的方法。

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

0

我在Chrome浏览器类似的问题也运行,开发加载在同一页图像的扩展名(弹出,实际上)换成新的旧图像。 旧图像使用的内存(从DOM中删除)永远不会释放,在短时间内消耗所有PC内存。 已经尝试过各种CSS技巧,但没有成功。 使用比PC更少内存的硬件,比如iPad,这个问题自然会出现。

3

我无法找到解决方案。这里有几个我试过的方法,和他们都失败了:

  • 只是改变使用img.src = base64

  • 删除更改使用div.style.backgroundImage = "url("+base64+")"

  • 一个DIV的背景图像的.src旧的并添加新图像使用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秒,没有别的,没有加载或显示图像,当然在物体包裹起来的,也崩溃了一段时间后内存!

12

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

解决方案的第二部分是移动Safari浏览器似乎可以通过“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> 
3

在Rails应用程序中,我懒加载数百张中等尺寸的照片(无限滚动),并且不可避免地达到iPhone上的10Mb限制。我尝试将图形加载到画布中(新图像,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背景图形无法显示出来。一个快速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上。

+0

当我有机会时,我会试试这个。 – 2011-02-09 02:05:14

1

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

解决方案最终变得相当简单 - 在列表项上使用position: absolute可以使它们被快速收集到足够快的速度以避免遇到内存限制。这仍然涉及在任何时候在DOM中只有大约20-30个图像,通过滚动位置创建和移除项目的DOM节点最终取得了成效。

看起来它特别依赖于将webkit-transform':'scale3d()应用于DOM中图像的任何祖先。相对而言,流动一个非常高的DOM并将其渲染到GPU上会让webkit渲染器产生内存泄漏,我猜?

相关问题