在 Javascript 中加载图像时 iPad/iPhone 浏览器崩溃

IT技术 javascript iphone ipad webkit mobile-webkit
2021-02-10 12:09:12

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

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

我也遇到了 UIWebView 的这个参考

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

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

6个回答

更新:我认为有一种更简单的方法可以做到这一点,具体取决于您的应用程序。而不必多张图片,如果你只是有一个<img>元素或Image物体(或者两个,像“这个”形象和“未来”的形象,如果你需要的动画或转换),并简单地更新.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 执行。但是在重置后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==

这可能会一遍又一遍地重新加载相同的图像,但它不适用于 70 多个图像的序列。我仍然需要使用画布尝试 Wolvrix 的解决方法,但我对 Apple 将移动 Safari 与这些奇怪的限制结合在一起感到非常不满。如果移动版 Safari 能够释放旧图像而不是保留它们直到页面重新加载,这将是很好的,从根本上阻止加载新图像。
2021-03-17 12:09:12

6.5MB(iPad) / 10MB(iPhone) 下载限制是根据用于通过其 src 属性设置图像的图像元素数量计算的。Mobile safari 似乎无法区分从缓存或通过网络加载的图像。图像是否注入 dom 也无关紧要。

解决方案的第二部分是移动 safari 似乎能够通过“背景图像”css 属性加载无限数量的图像。

这个概念证明使用了一个 precacher 池,一旦成功下载,它就会设置背景图像属性。我知道它不是最优的,并且不会将使用过的图像下载器返回到池中,但我相信你明白了:)

这个想法改编自 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> 

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

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

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

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

我猜你没有预加载任何图像?
2021-03-19 12:09:12

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

有机会我会试试这个。
2021-04-12 12:09:12