54

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

鉴于您可以在一个元素中或通过浏览器内的媒体播放器流式传输超过几 MB 的数据,这个限制似乎没有必要,应该有某种可用的解决方法。也许通过释放内存或其他方式。

我也遇到了 UIWebView 这个参考

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

这与我所看到的相当吻合。是否可以在 Javascript 中释放对象,或者 Safari/UIWebView 是否保持运行总数并且永不放手?或者,是否有任何解决方法可以以不占用这 10MB 的另一种方式加载数据?

4

11 回答 11

14

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


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

基本上,您需要进行一些更深入的图像管理并明确缩小您不需要的任何图像。您通常会通过使用document.removeChild(divMyImageContainer)or$("myimagecontainer").empty()或 what have you 来做到这一点,但在 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()

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

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

waitAndReload()在这里只是为了让图像有时间出现在屏幕上。移动版 Safari 非常慢并且显示大图像,因此在图像加载后需要时间来绘制屏幕。

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

我在这里为 dataurl 使用了一个文件夹图像(这是我能找到的第一个 dataurl 图像)。我只是简单地使用它,以便您可以看到脚本正常工作。您可能希望改用透明 gif,因此请改用此数据 url 字符串:data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

于 2010-07-16T17:31:42.723 回答
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> 
于 2011-01-18T08:27:23.380 回答
6

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

总而言之,这很疯狂。如果用户对更多图像内容提出肯定请求,那么 Safari 没有理由不允许您加载它。

于 2010-07-20T02:45:25.570 回答
6

从 Steve Simitzis 和 Andrew 的建议开始,我很幸运。

我的项目:

基于 PhoneGap 的应用程序,包含 6 个主要部分和大约 45 个子部分,其中包含 2 到 7 个图像的 jquery 循环库,每个 640 x 440(总共 215+ 个图像)。起初我使用 ajax 来加载页面片段,但后来我切换到单页站点,所有部分在需要之前都隐藏起来。

最初,在浏览了大约 20 个画廊后,我收到了内存警告 1,然后是 2,然后是崩溃。

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

似乎对我有用的解决方案是将背景图像 URL 存储在 div 的标题属性中,并将所有背景图像设置为空白 gif。对于 215 多张图片,我想将 url 保留在 html 中的某个位置,以便于快速参考。

当按下子导航按钮时,我将 css 背景图像重写为包含在 div 标题标签中的正确源,仅用于显示的画廊。这使我不必做任何花哨的 javascript 来存储正确的源图像。

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

当按下一个新的子导航按钮时,我将最后一个画廊 div 的背景图像重写为空白 gif。因此,除了界面 gfx 之外,我始终只有 2-7 个“活动”图像。对于我添加的任何其他包含图像的内容,我只是使用这种“按需”技术将标题与背景图像交换。

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

于 2010-08-17T15:47:36.947 回答
3

我无法找到解决方案。以下是我尝试的几种方法,但都失败了:

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

  • 使用改变.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 秒,没有别的,没有加载或显示图像,当然包裹在一个对象中,一段时间后内存也会崩溃!

于 2010-10-10T17:01:18.163 回答
3

在 Rails 应用程序上,我懒得加载数百张中等大小的照片(无限滚动),并且不可避免地达到了 iphone 的 10Mb 限制。我尝试将图形加载到画布中(新图像,src=,然后 Image.onload),但仍然达到相同的限制。我还尝试更换 img src 并将其移除(当它超出可视区域时),但仍然没有雪茄。最后,将所有带有 div 的带有照片作为背景的 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 上查看。

于 2011-02-08T02:09:19.243 回答
2

当我们尝试经常刷新图像时,我在 iPad 上遇到了 Javascript 内存不足的问题,比如每隔几秒钟。经常刷新是一个错误,但是 Safari 崩溃到主屏幕。一旦我控制了刷新时间,网络应用程序就可以正常运行。似乎 Javascript 引擎无法足够快地跟上垃圾收集的速度以丢弃所有旧图像。

于 2010-08-06T11:54:10.663 回答
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。

于 2010-07-19T03:05:34.307 回答
1

在 iPhone 上渲染大量图像时,我也遇到了类似的问题。在我的例子中,即使在列表中显示 50 张图像,也足以让浏览器崩溃,或者偶尔让整个操作系统崩溃。由于某种原因,渲染到页面上的任何图像都不会被垃圾收集,即使是在池化和回收一些屏幕上的 DOM 元素或将图像用作背景图像属性时也是如此。即使直接将图像显示为 Data-URI 也足以计入限制。

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

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

于 2015-10-08T22:48:51.763 回答
0

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

于 2010-08-26T13:28:34.417 回答
-1

我用 jQuery 提交了一个错误,因为 jQuery 试图处理内存泄漏......所以我认为这是一个错误。希望团队能尽快在 Mobile Safari 中想出一些简洁而聪明的方法来处理这个问题。

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

于 2010-08-25T14:07:41.643 回答