IPad / iPhone crashes when loading images in Javascript

I'm trying to create an image gallery in Safari that mimics an iPad app for iPad. It works great, except that as soon as I upload images larger than 6 MB or add them to the DOM or create new Image objects, the new images either stop loading or the browser crashes. This problem is common enough (with everyone else facing the same limit) that I excluded my Javascript code as the culprit.

Given that you can transfer much more than a few MB in an element or through a media player in a browser, this limit seems redundant, and there should be some workaround. Perhaps freeing up memory or something else.

I also came across this link for UIWebView .

"JavaScript allocation is also limited to 10 MB. Safari throws an exception if you exceed this limit for general memory allocation for JavaScript."

Which corresponds to what I see quite well. Is it possible to free objects in Javascript, or is Safari / UIWebView saving the current number and never letting go? Alternatively, is there any workaround for loading data in another way that does not eat this 10 MB?

+52
javascript iphone ipad mobile-webkit webkit
Jun 06 '10 at 21:53
source share
11 answers

Update: I think there is an even easier way to do this, depending on your application. Instead of having multiple images, if you just have one <img> or Image element (or maybe two, for example, the 'this' image and the "next" image if you need animations or transitions) and just refresh .src , .width , .height , etc., you should never get close to the 10 MB limit. If you want to make a carousel app, you'll have to use smaller placeholders first. You might find that this method might be easier to implement.




I think I actually found a workaround for this.

Basically, you will need to do more in-depth image management and explicitly compress any image you don't need. You usually do this with document.removeChild(divMyImageContainer) or $("myimagecontainer").empty() or whatever you have, but on Mobile Safari it does absolutely nothing; the browser just does not free up memory.

Instead, you need to update the image so that it takes up very little memory; and you can do this by changing the src attribute. The fastest way I know is to use the data url . So instead:

 myImage.src="/path/to/image.png" 

... say this instead:

 myImage.src="data:image/gif;base64,AN_ENCODED_IMAGE_DATA_STRING" 

The following is a test demonstrating its performance. In my tests, my large 750K image will eventually destroy the browser and stop all JS exploits. But after resetting src I was able to load image instances more than 170 times. It also explains how the code works.

 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() gif; base64, R0lGODlhEAAOALMAAOazToeHh0tLS / 7LZv / 0jvb29t / f3 // Ub // ge8WSLf / rhf / 3kdbW1mxsbP // mf /// yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj / gAwXEQA7"; 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 will need to figure out how to apply it to your own code. The code consists of three parts, which I will explain below, but the only really important part is imgStoredImage.src = strNullImage;

loadNextImage() just loads the new image and calls shrinkImages() . It also assigns an onload , which is used to start the process of loading another image (error: I have to clear this event later, but it’s not).

waitAndReload() is only for display time to be displayed on the screen. Mobile Safari is rather slow and displays large images, so it needs time after loading the image to draw a screen.

shrinkImages() goes through all previously uploaded images (except the active one) and changes .src to the dataurl address.

I am using the file folder image for dataurl here (this was the first dataurl image I could find). I use it simply so that you can see the script work. You probably want to use a transparent gif instead, so use this string of data url instead: data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

+14
Jul 16 '10 at 17:31
source share

Download restrictions of 6.5MB (iPad) / 10MB (iPhone) are calculated based on the number of image elements used to set the image through its src property. Mobile safari does not seem to distinguish images downloaded from the cache or via the network. It also doesn't matter if the image is entered in dom or not.

The second part of the solution is that mobile safari seems to be able to upload an unlimited number of images via the css property "background-image".

This proof of concept uses a preamplifier pool that immediately sets the properties of the background image. I know that this is not optimal and does not return the used image downloader to the pool, but I'm sure you got the idea :)

Idea adapted from Rob Laplaki's canvas canvas look 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> 
+12
Jan 18 '11 at 8:27
source share

I was fortunate enough to start with a proposal by Steve Simitsis and Andrew.

My project:

PhoneGap-based application with 6 main sections and about 45 subsections, which have a jquery loop gallery in size from 2 to 7 images, each 640 x 440 (a total of 215 images). At first I used ajax to load fragments of the page, but since then I went to the site with one page, and all sections were hidden until needed.

Initially, after going through about 20 galleries, I received a warning about memory 1, then 2, then a crash.

After creating all the images in a div with the image used as the background, I could go through the gallery (about 35) in the application before the crash, but after visiting the previously visited galleries this will ultimately fail.

The solution that seems to work for me is to keep the background image URL in the attribute of the div header and set all background images as an empty gif. With 215+ images, I wanted to save the URL in html for convenience and quick reference.

When the sub-navigation button is pressed, I rewrite the css background image to the correct source, which is contained in the div header tag, for the gallery ONLY displayed. This freed me from having to do some fantastic javascript to store the correct source image.

 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 latest div galleries so that they are empty. Thus, in addition to the gfx interface, I only have 2-7 images "active". Anything else I add that contains the images, I just use this "ondemand" technique to replace the title with the background image.

Now, it seems, I can use the application indefinitely, without crashing. I don't know if this will help anyone else, and it might not be the most elegant solution, but it provided me with a fix.

+6
Aug 17 '10 at 15:47
source share

So far I have been lucky using the <div> tags instead of the <img> tags and setting the image as the background image of the div.

All in all, this is crazy. If the user makes an affirmative request for more images, then there is no reason why Safari should not allow you to download it.

+5
Jul 20 '10 at 2:45
source share

I could not find a solution for this. Here are a few methods I tried, and all of them failed:

  • Just changed the background of the DIV using div.style.backgroundImage = "url("+base64+")"

  • Changed .src image using img.src = base64

  • Removed the old and added a new image using removeChild( document.getElementById("img") ); document.body.appendChild( newImg ) removeChild( document.getElementById("img") ); document.body.appendChild( newImg )

  • Same as above, but with random height in the new image

  • Delete and add an image as an HTML5 canvas object. Also does not work, since you need to create a new Image(); , cm. *

  • At startup, create a new Image() object, let it call the container. The image was displayed as <canvas> , every time the image changed, I would change the .src container and redraw the canvas using ctx.drawImage( container, 0,0 ) .

  • Names as previous, but without redrawing the canvas. Simply modifying an Image() src object uses memory.

The strange thing I noticed: an error occurs even if the image does not appear! For example, in this case:

 var newImg = new Image( 1024, 750 ); newImg.src = newString; // A long base64 string 

Every 5 seconds, and nothing more, without loading or displaying the image, of course, wrapped in an object, it also resets the memory after a while!

+3
Oct 10 2018-10-10
source share

In the application with rails, I was lazy to upload hundreds of medium-sized photos (endless scroll) and inevitably fell into the 10 MB limit on the iphone. I tried loading the graphics into the canvas (new image, src =, then Image.onload), but still hit the same limit. I also tried replacing img src and removing it (when it came out of the visible area), but still not cigars. In the end, all the img w / div w / the photos tags were selected as the background.

  $.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); } }); 

Now I can upload more than 40 MB of photos on one page without hitting the wall. I ran into an odd problem, although some of the css frameworks did not display. Fast js thread fixed this. Set the css bg div property every 3 seconds.

  setInterval(function() { $(".big_box .images div.img").each(function() { $(this).css({background:$(this).css("background")}); }); }, 3000); 

This can be seen in action at http://fotodeck.com . Check it out on your iphone / ipad.

+3
Feb 08 2018-11-11T00:
source share

There are memory problems, and the way to solve this problem is very simple. 1) Put all the thumbnails on the canvas. You will create many new image objects and draw them into the canvas, but if your thumbnail is very small, you should be fine. For the container in which you will display the image in real size, create only one image object and reuse this object and do not forget to also draw it in the canvas. That way, every time a user clicks a thumbnail, you update your main image object. Do not embed IMG tags on the page. Instead, add CANVAS tags with the correct width and height for the thumbnails and the main display container. iPad will cry if you insert too many IMG tags. So, avoid them !!! Insert only the canvas. Then you can find the canvas object from the page and get the context. Thus, each time the user clicks the thumbnail, you will get the src of the main image (a real-size image) and draw it on the main canvas, reusing the main Image object and shooting events. Clearing events every time at the beginning.

 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 create 200 thumbnails, and each one is like 15kb. Real images are 1 MB each.

+2
Jul 19 '10 at 3:05
source share

I ran out of memory from Javascript to iPad when we tried to refresh the image very often, like every couple of seconds. It was a bug that was frequently updated, but Safari broke into the main screen. As soon as I set the update time under control, the web application was functioning normally. It seemed that the Javascript engine could not handle garbage collection fast enough to discard all old images.

+2
Aug 6 2018-10-06T00:
source share

I also had similar issues when rendering large image lists on iPhone. 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 made on the page were not garbage collected, even when combining and reusing only a few DOM screen elements or using images as a property of the background image. Even displaying images directly, since the data URI is enough to count on limitation.

The solution turned out to be quite simple - using position: absolute in the list items allows them to collect garbage fast enough to not work in the memory limit. This is still due to the fact that at any time in the DOM there were only about 20-30 images, creating and deleting nodes of the DOM element by scrolling positon finally did the trick.

This seems to depend especially on what webkit-transform':'scale3d() applies to any ancestor of images in the DOM. I believe that the relatively current very high DOM and rendering it on the GPU discards a memory leak in the webkit renderer?

+1
Oct 08 '15 at 22:48
source share

I filed an error with jQuery since jQuery is trying to deal with memory leaks ... so I consider this an error. We hope that the team can come up with some short and smart way to solve this problem in Mobile Safari in the near future.

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

0
Aug 25 2018-10-2514:
source share

I also have a similar problem working in Chrome, creating an extension that loads images on one page (popup, actually), replacing old images with new ones. The memory used by old images (deleted from the DOM) is never freed, and it consumes all PC memory in a short time. Tried various CSS tricks, without success. Using hardware with less memory than a PC, such as an iPad, this problem naturally occurs naturally.

0
Aug 26 '10 at 13:28
source share



All Articles