在 Javascript 中加载图像时 iPad/iPhone 浏览器崩溃
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2986039/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iPad/iPhone browser crashing when loading images in Javascript
提问by Steve Simitzis
I'm trying to build an image gallery in Safari that mimics the iPad photo app. It works perfectly, except that once I load more than 6MB or so worth of images either by adding them to the DOM or creating new Image objects, new images either stop loading or the browser crashes. This problem is widespread enough (with everyone else hitting up against the same limit) that I've ruled out my Javascript code as the culprit.
我正在尝试在 Safari 中构建一个模仿 iPad 照片应用程序的图片库。它工作得很好,除了一旦我通过将它们添加到 DOM 或创建新的 Image 对象来加载超过 6MB 左右的图像,新图像要么停止加载,要么浏览器崩溃。这个问题非常普遍(其他人都遇到了相同的限制),我已经排除了我的 Javascript 代码是罪魁祸首。
Given that you can stream much more than a few MB in a element or through the in-browser media player, this limit seems unnecessary, and there should be some kind of workaround available. Perhaps by freeing up memory or something else.
鉴于您可以在一个元素中或通过浏览器内的媒体播放器流式传输多于几 MB 的内容,这个限制似乎没有必要,应该有某种解决方法可用。也许通过释放内存或其他东西。
I also came across this reference for UIWebView.
我也遇到了 UIWebView 的这个参考。
"JavaScript allocations are also limited to 10 MB. Safari raises an exception if you exceed this limit on the total memory allocation for JavaScript."
“JavaScript 分配也被限制为 10 MB。如果您超出 JavaScript 总内存分配的这个限制,Safari 会引发异常。”
Which matches what I'm seeing fairly well. Is it possible to deallocate objects in Javascript, or does Safari/UIWebView keep a running total and never lets go? Alternately, is there any workaround to load in data another way that doesn't eat up this 10MB?
这与我所看到的相当吻合。是否可以在 Javascript 中释放对象,或者 Safari/UIWebView 是否保持运行总数并且永不放手?或者,是否有任何解决方法可以以另一种方式加载数据而不消耗这 10MB?
回答by Andrew
Update: I think there's an even easier way to do this, depending on your application. Instead of having multiple images, if you simply have one <img>element or Imageobject (or maybe two, like a 'this' image and a 'next' image if you need animations or transitions) and simply update the .src, .width, .heightand so on, you should never get near the 10MB limit. If you wanted to do a carousel application, you'd have to use smaller placeholders first. You might find this technique might be easier to implement.
更新:我认为有一种更简单的方法可以做到这一点,具体取决于您的应用程序。而不必多张图片,如果你只是有一个<img>元素或Image物体(或者两个,像“这个”形象和“未来”的形象,如果你需要的动画或转换),并简单地更新.src,.width,.height等等,你应该永远不要接近 10MB 的限制。如果您想做轮播应用程序,则必须首先使用较小的占位符。您可能会发现这种技术可能更容易实现。
I think I may actually have found a work-around to this.
我想我实际上可能已经找到了解决方法。
Basically, you'll need to do some deeper image management and explicitly shrink any image you don't need. You'd normally do this by using document.removeChild(divMyImageContainer)or $("myimagecontainer").empty()or what have you, but on Mobile Safari this does absolutely nothing; the browser simply never deallocates the memory.
基本上,您需要进行一些更深入的图像管理并明确缩小您不需要的任何图像。您通常会通过使用document.removeChild(divMyImageContainer)或$("myimagecontainer").empty()或您拥有的东西来做到这一点,但在 Mobile Safari 上这绝对没有任何作用;浏览器根本不会释放内存。
Instead, you need to update the image itself so it takes up very little memory; and you can do that by changing the image's srcattribute. The quickest way I know of to do that is to use a data URL. So instead of saying this:
相反,您需要更新图像本身,使其占用很少的内存;您可以通过更改图像的src属性来做到这一点。我所知道的最快方法是使用数据 URL。所以,而不是这样说:
myImage.src="/path/to/image.png"
...say this instead:
...改为这样说:
myImage.src="data:image/gif;base64,AN_ENCODED_IMAGE_DATA_STRING"
Below is a test to demonstrate it working. In my tests, my large 750KB image would eventually kill the browser and halt all JS exectution. But after resetting src, I"ve been able to load in instances of the image over 170 times. An explanation of how the code works is below as well.
下面是一个测试来证明它的工作。在我的测试中,我的 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()
This code was written to test my solution, so you'll have to figure out how to apply it to your own code. The code comes in three parts, which I will explain below, but the only really important part is imgStoredImage.src = strNullImage;
编写此代码是为了测试我的解决方案,因此您必须弄清楚如何将其应用于您自己的代码。代码分为三部分,我将在下面解释,但唯一真正重要的部分是imgStoredImage.src = strNullImage;
loadNextImage()simply loads a new image and calls shrinkImages(). It also assigns an onloadevent which is used to begin the process of loading another image (bug: I should be clearing this event later, but I'm not).
loadNextImage()只需加载一个新图像并调用shrinkImages(). 它还分配了一个onload事件,用于开始加载另一个图像的过程(错误:我应该稍后清除此事件,但我没有)。
waitAndReload()is only here to allow the image time to show up on the screen. Mobile Safari is pretty slow and displaying big images, so it needs time after the image has loaded to paint the screen.
waitAndReload()只是为了让图像时间显示在屏幕上。Mobile Safari 速度很慢并且显示大图像,因此在图像加载后需要时间来绘制屏幕。
shrinkImages()goes through all previously loaded images (except the active one) and changes the .srcto the dataurl address.
shrinkImages()遍历所有先前加载的图像(活动图像除外)并将 更改.src为 dataurl 地址。
I'm using a file-folder image for the dataurl here (it was the first dataurl image I could find). I'm using it simply so you can see the script working. You'll probably want to use a transparent gif instead, so use this data url string instead: data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
我在这里为 dataurl 使用了一个文件夹图像(这是我能找到的第一个 dataurl 图像)。我只是在使用它,所以您可以看到脚本正在运行。您可能想改用透明 gif,因此请改用此数据 url 字符串:data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
回答by Alex
The 6.5MB(iPad) / 10MB(iPhone) download limits are calculated based on the number of image elements used to set an image through its src property. Mobile safari doesn't seem to differentiate images loaded from cache or via the network. It also doesn't matter whether the image is injected into the dom or not.
6.5MB(iPad) / 10MB(iPhone) 下载限制是根据用于通过其 src 属性设置图像的图像元素数量计算的。Mobile safari 似乎无法区分从缓存或通过网络加载的图像。图像是否注入 dom 也无关紧要。
The second part to the solution is that mobile safari seems to be able to load an unlimited number of images via the "background-image" css property.
解决方案的第二部分是移动 safari 似乎能够通过“背景图像”css 属性加载无限数量的图像。
This proof of concept uses a pool of precacher's which set the background-image properties once successfully downloaded. I know that it's not optimal and doesn't return the used Image downloader to the pool but i'm sure you get the idea :)
这个概念证明使用了一个 precacher 池,一旦成功下载,它就会设置背景图像属性。我知道这不是最优的,并且不会将使用过的图像下载器返回到池中,但我相信你明白了:)
The idea is adapted from Rob Laplaca's original canvas workaround http://roblaplaca.com/blog/2010/05/05/ipad-safari-image-limit-workaround/
这个想法改编自 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>
回答by Transoptic
I've had luck starting with the suggestion of Steve Simitzis, and Andrew.
从 Steve Simitzis 和 Andrew 的建议开始,我很幸运。
My project:
我的项目:
PhoneGap-based app with 6 main sections, and about 45 subsections which have a jquery cycle gallery of between 2 and 7 images, each 640 x 440 (215+ images altogether). At first I was using ajax to load page fragments, but I've since switched to a one-page site, with all sections hidden until needed.
基于 PhoneGap 的应用程序有 6 个主要部分,以及大约 45 个子部分,其中包含 2 到 7 个图像的 jquery 循环库,每个 640 x 440(总共 215 个以上的图像)。起初,我使用 ajax 加载页面片段,但后来我切换到单页站点,所有部分都隐藏起来,直到需要为止。
Initially, after going through about 20 galleries, I was getting memory warning 1, then 2, then the crash.
最初,经过大约 20 个画廊后,我收到了内存警告 1,然后是 2,然后是崩溃。
After making all the images into divs with the image applied as a background, I could get through more galleries (about 35) in the app before a crash, but after going to previously visited galleries, it would eventually fail.
在将所有图像制作成 div 并将图像应用为背景后,我可以在崩溃前通过应用程序中的更多画廊(大约 35 个),但在转到以前访问过的画廊之后,它最终会失败。
The solution that seems to be working for me, is to store the background image URL in the div's title attribute, and setting all of the background images to be a blank gif. With 215+ images, I wanted to keep the url someplace in the html for sake of ease and quick reference.
似乎对我有用的解决方案是将背景图像 URL 存储在 div 的标题属性中,并将所有背景图像设置为空白 gif。有 215 多张图片,为了方便和快速参考,我想将 url 保留在 html 中的某个地方。
When a subnavigation button is pressed, I rewrite the css background image to the correct source which is contained in the div's title tag, for ONLY the gallery that is showing. This saved me from having to do any fancy javascript to store the correct source image.
当按下子导航按钮时,我将 css 背景图像重写为包含在 div 标题标签中的正确源,仅用于显示的画廊。这使我不必做任何花哨的 javascript 来存储正确的源图像。
var newUrl = $(this).attr('title');
$(this).css('background-image', 'url('+newUrl+')');
When a new subnavigation button is pressed, I rewrite the background image of the last gallery divs to be blank gifs. So, aside from interface gfx, I only have 2-7 images 'active' at all times. With anything else I add that contains images, I just use this "ondemand" technique to swap the title with the background-image.
当按下新的子导航按钮时,我将最后一个画廊 div 的背景图像重写为空白 gif。因此,除了界面 gfx,我始终只有 2-7 个“活动”图像。对于我添加的任何其他包含图像的内容,我只是使用这种“按需”技术将标题与背景图像交换。
Now it seems I can use the app indefinitely with no crashes. Don't know if this will help anyone else, and it may not be the most elegant solution, but it provided a fix for me.
现在看来我可以无限期地使用该应用程序而不会崩溃。不知道这是否对其他人有帮助,它可能不是最优雅的解决方案,但它为我提供了解决方案。
回答by Steve Simitzis
So far I've had luck using <div>tags instead of <img>tags and setting the image as the div's background-image.
到目前为止,我很幸运地使用<div>标签而不是<img>标签并将图像设置为 div 的背景图像。
All in all, it's crazy. If the user is making an affirmative request for more image content, then there's no reason why Safari shouldn't allow you to load it.
总而言之,这很疯狂。如果用户对更多图像内容提出肯定请求,那么 Safari 就没有理由不允许您加载它。
回答by Louis B.
I was unable to find a solution for this. Here are a couple of methods I tried, and all of them failed:
我无法为此找到解决方案。以下是我尝试过的几种方法,但都失败了:
Simply changed the background of a DIV using
div.style.backgroundImage = "url("+base64+")"Changed the
.srcof an image usingimg.src = base64Removed the old and added the new image using
removeChild( document.getElementById("img") ); document.body.appendChild( newImg )The same as above but with a random height on the new image
Removing and adding the image as a HTML5 canvas object. Also doesn't work, since a new
Image();has to be created, see *On launch, created a new
Image()object, let's call it container. Displayed the image as<canvas>, every time the image changed, I would change container's.srcand redraw the canvas usingctx.drawImage( container, 0,0 ).The sames as the previous, but without actually redrawing the canvas. Simply changing the
Image()object'ssrcuses up memory.
只需使用更改 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使用内存。
A strange thing I noticed: The bug occurs even if the image isn't displayed! For example, when doing this:
我注意到一件奇怪的事情:即使没有显示图像,也会出现错误!例如,在执行此操作时:
var newImg = new Image( 1024, 750 );
newImg.src = newString; // A long base64 string
Every 5 seconds, and nothing else, no loading or displaying the image, of course wrapped up in an object, also crashes the memory after some time!
每 5 秒,没有别的,没有加载或显示图像,当然包裹在一个对象中,一段时间后也会使内存崩溃!
回答by danlee
On a rails app, I was lazy loading hundreds of mid-size photos (infinite scroll) and inevitably hit the 10Mb limit on the iphone. I tried loading the graphics into a canvas (new Image, src=, then Image.onload) but still hit the same limit. I also tried replacing the img src and removing it (when it went out of viewable area) but still no cigar. In the end, switching out all the img tags w/ div's w/ the photo as background did the trick.
在 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);
}
});
I can now load well over 40Mb of photos on one page w/o hitting the wall. I encountered an odd issue, though, with some of the css background graphics failing to show up. A quick js thread fixed that. Set the div's css bg property every 3 sec's.
我现在可以在一页上加载超过 40Mb 的照片而不会撞到墙上。但是,我遇到了一个奇怪的问题,一些 css 背景图形无法显示。一个快速的 js 线程修复了这个问题。每 3 秒设置一次 div 的 css bg 属性。
setInterval(function() {
$(".big_box .images div.img").each(function() {
$(this).css({background:$(this).css("background")});
});
}, 3000);
You can see this in action at http://fotodeck.com. Check it out on your iphone/ipad.
您可以在http://fotodeck.com 上看到这一点。在您的 iphone/ipad 上查看。
回答by David Bakkom
I encountered an out of memory with Javascript on the iPad when we were trying to refresh an image very often, like every couple of seconds. It was a bug to refresh that often, but Safari crashed out to the home screen. Once I got the refresh timing under control, the web app functioned fine. It seemed as if the Javascript engine couldn't keep up with garbage collection quickly enough to discard all the old images.
当我们经常尝试刷新图像时,我在 iPad 上遇到了 Javascript 内存不足的问题,比如每隔几秒。经常刷新是一个错误,但 Safari 崩溃到主屏幕。一旦我控制了刷新时间,网络应用程序运行良好。似乎 Javascript 引擎跟不上垃圾收集的速度,无法丢弃所有旧图像。
回答by Sergio
There are issues with memory and the way to solve this problem is very simple. 1) Put all your thumbnails in canvas. You will be creating a lot of new Image objects and drawing them into canvas, but if your thumbnail are very small you should be fine. For the container where you will be displaying the real size image, create only one Image object and reuse this object and make sure to also draw it into a canvas. So, every time a user clicks the thumbnail, you will update your main Image object. Do not insert IMG tags in the page. Insert CANVAS tags instead with the correct width and height of the thumbnails and the main display container. iPad will cry foul if you insert too many IMG tags. So, avoid them!!! Insert only canvas. You can then find the canvas object from the page and get the context. So every time the user clicks a thumbnail, you will get the src of the main image (real size image) and draw it to the main canvas, reusing the main Image object and the firing the events. Clearing the events every time at the beginning.
内存有问题,解决这个问题的方法很简单。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;
I have create 200 thumbnails and each is like 15kb. The real images are like 1 MB each.
我创建了 200 个缩略图,每个缩略图像 15kb。真实图像每个大约 1 MB。
回答by haversine
I also had similar problems while rendering large lists of images on iPhones. In my case displaying even 50 images in the list was enough to either crash the browser or occasionally the entire operating system. For some reason any images rendered onto the page weren't garbage collected, even when pooling and recycling just a few onscreen DOM elements or using the images as background-image property. Even displaying the images directly as Data-URIs is enough to count towards the limit.
我在 iPhone 上渲染大量图像时也遇到了类似的问题。就我而言,即使在列表中显示 50 个图像也足以使浏览器崩溃或偶尔使整个操作系统崩溃。出于某种原因,渲染到页面上的任何图像都不会被垃圾收集,即使仅池化和回收一些屏幕 DOM 元素或将图像用作背景图像属性也是如此。即使将图像直接显示为数据 URI 也足以计入限制。
The solution ended up being rather simple - using position: absoluteon the list items allows them to be garbage collected fast enough to not run into a memory limit. This still involved on having only about 20-30 images in the DOM at any moment, creating and removing the item's DOM nodes by scroll positon finally did the trick.
解决方案最终变得相当简单 -position: absolute在列表项上使用允许它们以足够快的速度被垃圾收集而不会遇到内存限制。这仍然涉及在任何时候在 DOM 中只有大约 20-30 个图像,通过滚动位置创建和删除项目的 DOM 节点终于成功了。
It seems it's particularily dependent on having webkit-transform':'scale3d()applied to any ancestor of the images in the DOM. Relatively flowing a very tall DOM and rendering it on the GPU pisses off a memory leak in webkit renderer, I guess?
它似乎特别依赖于已webkit-transform':'scale3d()应用于 DOM 中图像的任何祖先。相对地流动一个非常高的 DOM 并在 GPU 上渲染它会激怒 webkit 渲染器中的内存泄漏,我猜?
回答by Omiod
I'm running in a similar issue in Chrome too, developing an extension that loads images in the same page (the popup, actually) replacing old images with new ones. The memory used by the old images (removed from the DOM) is never freed, consuming all the PC memory in a short time. Have tried various tricks with CSS, without success. Using hardware with less memory than a PC, like the iPad, this problem arises earlier, naturally.
我也在 Chrome 中遇到了类似的问题,开发了一个扩展程序,在同一页面(实际上是弹出窗口)中加载图像,用新图像替换旧图像。旧图像(从 DOM 中删除)使用的内存永远不会释放,在短时间内消耗所有 PC 内存。用 CSS 尝试了各种技巧,都没有成功。使用内存比 PC 少的硬件,比如 iPad,自然会更早出现这个问题。

