【问题标题】:iPad/iPhone browser crashing when loading images in Javascript在 Javascript 中加载图像时 iPad/iPhone 浏览器崩溃
【发布时间】:2011-02-28 11:40:07
【问题描述】:

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

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

我也遇到过这个reference for UIWebView

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

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

【问题讨论】:

  • 您可以回到过去的 C 时代并使用“删除”来... err... 删除您未显示的图像 :)
  • 好吧,这糟透了。我讨厌苹果。
  • 图像写入页面后,我认为不适用相同的内存限制。我很确定我已经查看过包含超过 10MB 图像的网页。所以我想问题是你是如何使用 JS 来获取图像的?你能用一些你如何加载它们的例子来更新你的问题吗?
  • @Andrew,信不信由你,Mobile Safari 无法管理包含超过 7MB 图像的平面 HTML 文件。当浏览器内存不足时,它会简单地放入所有剩余图像的蓝色小占位符图形。所以这不是JS的问题。我发现很难理解 Apple 无法从袖子里拿出一些技巧来管理稀缺的内存资源,但看起来他们甚至没有尝试过。我认为这意味着下面的解决方法可能是唯一在页面上获取超过 10MB 图像的方法。
  • 10 MB 的限制适用于 JavaScript 代码字符串。 Safari 中的图片限制实际上更像是 6.5MB。

标签: javascript iphone ipad webkit mobile-webkit


【解决方案1】:

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


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

基本上,您需要进行一些更深入的图像管理并明确缩小您不需要的任何图像。你通常会使用document.removeChild(divMyImageContainer)$("myimagecontainer").empty() 或你有什么来做到这一点,但在Mobile 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() 只是为了让图像有时间出现在屏幕上。移动版 Safari 速度很慢,而且显示的图片很大,因此在加载图片后需要一段时间才能绘制屏幕。

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

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

【讨论】:

  • 这可能会一遍又一遍地重新加载相同的图像,但它不适用于 70 多个图像的序列。我仍然需要尝试 Wolvrix 的画布解决方法,但我对 Apple 将移动 Safari 与这些奇怪的限制相结合感到非常不满。如果移动版 Safari 只释放旧图像而不是在页面重新加载之前保留它们,那就太好了,基本上阻止了新图像的加载。
【解决方案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。

【讨论】:

  • 这个方法我运气不好,仅供参考。
  • @Steve:我将在本周末之前在 YouTube 上发布一段视频,并亲自动手。我会做一些解释以及我是如何让它工作的。发布后我会通知您。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      从 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 个“活动”图像。对于我添加的任何其他包含图像的内容,我只需使用这种“按需”技术将标题与背景图像交换。

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

      【讨论】:

      • 我猜你没有预加载任何图像?
      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          • 使用div.style.backgroundImage = "url("+base64+")"简单地改变一个DIV的背景

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

          【讨论】:

            【解决方案9】:

            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> 
            

            【讨论】:

              【解决方案10】:

              在 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 上查看。

              【讨论】:

              • 有机会我会试试的。
              【解决方案11】:

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

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

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-07-05
                • 1970-01-01
                • 2013-01-06
                • 2016-11-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多