繁体   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