Подтвердить что ты не робот

Падение браузера iPad/iPhone при загрузке изображений в Javascript

Я пытаюсь создать галерею изображений в Safari, которая имитирует приложение для iPad iPad. Он отлично работает, за исключением того, что как только я загружаю изображения размером более 6 МБ или добавляя их в DOM или создавая новые объекты Image, новые изображения либо прекращают загрузку, либо выходят из строя браузера. Эта проблема достаточно распространена (со всеми остальными, сталкивающимися с одним и тем же пределом), что я исключил свой код Javascript в качестве виновника.

Учитывая, что вы можете передавать гораздо больше, чем несколько МБ в элементе или через медиаплеер в браузере, этот лимит кажется излишним, и должно быть какое-то обходное решение. Возможно, освободив память или что-то еще.

Я также столкнулся с этой ссылкой для UIWebView.

"Распределение JavaScript также ограничено 10 МБ. Safari вызывает исключение, если вы превысите этот предел для общего распределения памяти для JavaScript."

Что соответствует тому, что я вижу достаточно хорошо. Возможно ли освободить объекты в Javascript, или Safari/UIWebView сохраняет текущее количество и никогда не отпускает? Альтернативно, есть ли какое-либо обходное решение для загрузки данных другим способом, который не съедает этот 10 МБ?

4b9b3361

Ответ 1

Обновление: я думаю, что есть еще более простой способ сделать это, в зависимости от вашего приложения. Вместо того, чтобы иметь несколько изображений, если у вас просто есть один элемент <img> или Image (или, может быть, два, например изображение 'this' и "следующий" образ, если вам нужны анимации или переходы) и просто обновите .src, .width, .height и т.д., вы никогда не должны приближаться к пределу 10 МБ. Если вы хотите сделать приложение для карусели, вам придется сначала использовать меньшие заполнители. Вы могли бы найти, что этот метод может быть проще реализовать.


Я думаю, что на самом деле я нашел для этого обход.

В принципе, вам нужно будет сделать более глубокое управление изображениями и явно сжать любое изображение, которое вам не нужно. Обычно вы делаете это с помощью 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"

Ниже приведен тест, демонстрирующий его работоспособность. В моих тестах мое большое изображение 750 КБ в конечном итоге уничтожит браузер и остановит все эксплойты 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, которое я мог найти). Я использую его просто, чтобы вы могли видеть работу script. Вы, вероятно, захотите использовать прозрачный gif вместо этого, поэтому используйте эту строку url данных вместо: data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

Ответ 2

Ограничения на скачивание 6.5MB (iPad)/10MB (iPhone) рассчитываются на основе количества элементов изображения, используемых для установки изображения через его свойство src. Мобильное сафари, похоже, не отличает изображения, загруженные из кеша или через сеть. Также не имеет значения, вводится ли изображение в dom или нет.

Вторая часть решения заключается в том, что мобильное сафари, похоже, может загружать неограниченное количество изображений через свойство css "background-image".

В этом доказательстве концепции используется пул предусилителя, который сразу задает свойства фонового изображения. Я знаю, что это не оптимально и не возвращает использованный загрузчик изображений в пул, но я уверен, что вы получили идею:)

Идея адаптирована из обходного образа холста Роба Лаплаки 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> 

Ответ 3

Мне посчастливилось начать с предложения Стив Симитсиса и Эндрю.

Мой проект:

Приложение на основе PhoneGap с 6 основными разделами и около 45 подразделов, которые имеют галерею цикла jquery размером от 2 до 7 изображений, каждая 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, чтобы они были пустыми. Таким образом, помимо интерфейса gfx, у меня есть только 2-7 изображений "активно". Что-нибудь еще я добавляю, что содержит изображения, я просто использую эту технику "ondemand" для замены заголовка фоновым изображением.

Теперь, кажется, я могу использовать приложение неограниченно, без сбоев. Не знаю, поможет ли это кому-либо другому, и это может быть не самое изящное решение, но оно предоставило мне исправление.

Ответ 4

До сих пор мне повезло с использованием тегов <div> вместо тегов <img> и установки изображения в качестве фонового изображения div.

В целом, это безумие. Если пользователь делает утвердительный запрос на большее количество изображений, тогда нет причин, по которым Safari не должна позволять вам его загружать.

Ответ 5

Мне не удалось найти решение для этого. Вот несколько методов, которые я пробовал, и все они потерпели неудачу:

  • Просто изменил фон DIV, используя div.style.backgroundImage = "url("+base64+")"

  • Изменен .src изображения с помощью img.src = base64

  • Удалено старое и добавлено новое изображение с помощью removeChild( document.getElementById("img") ); document.body.appendChild( newImg )

  • То же, что и выше, но со случайной высотой на новом изображении

  • Удаление и добавление изображения в качестве объекта canvas 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 секунд, и ничего больше, без загрузки или отображения изображения, конечно же, завернутого в объект, также через некоторое время сбрасывает память!

Ответ 6

В приложении с рельсами я ленился загружать сотни фотографий среднего размера (бесконечный свиток) и неизбежно попадал в лимит 10 Мб на iphone. Я попытался загрузить графику в холст (новое изображение, src=, затем Image.onload), но все равно попал в тот же предел. Я также попытался заменить img src и удалить его (когда он вышел из видимой области), но все равно не сигары. В конце концов, все тэги img w/div w/the photos были выбраны в качестве фона.

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

Теперь я могу загружать более 40 Мб фотографий на одну страницу без удара по стене. Я столкнулся с нечетной проблемой, хотя некоторые из фреймворков css не отображались. Быстрый поток js исправил это. Установите свойство div css bg каждые 3 секунды.

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

Это можно увидеть в действии http://fotodeck.com. Проверьте это на своем iphone/ipad.

Ответ 7

Есть проблемы с памятью, и способ решить эту проблему очень прост. 1) Поместите все миниатюры в холст. Вы будете создавать много новых объектов изображения и рисовать их в холст, но если ваш миниатюра очень мал, вы должны быть в порядке. Для контейнера, в котором вы будете отображать изображение реального размера, создайте только один объект изображения и повторно используйте этот объект и не забудьте также нарисовать его в холсте. Таким образом, каждый раз, когда пользователь нажимает миниатюру, вы обновляете свой основной объект изображения. Не вставляйте IMG-теги на страницу. Вместо этого добавьте теги CANVAS с правильной шириной и высотой эскизов и основным контейнером дисплея. iPad будет плакать, если вы вставляете слишком много тегов IMG. Так, избегайте их!!! Вставьте только холст. Затем вы можете найти объект canvas со страницы и получить контекст. Таким образом, каждый раз, когда пользователь нажимает миниатюру, вы получите 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 МБ каждый.

Ответ 8

Я столкнулся с нехваткой памяти с Javascript на iPad, когда мы пытались обновить изображение очень часто, как каждые пару секунд. Это была ошибка, которая часто обновлялась, но Safari вырвался на главный экран. Как только я установил время обновления под контролем, веб-приложение функционировало нормально. Казалось, что Javascript-движок не мог справиться с сборкой мусора достаточно быстро, чтобы отбросить все старые изображения.

Ответ 9

У меня также были подобные проблемы при рендеринге больших списков изображений на iPhone. В моем случае отображение даже 50 изображений в списке было достаточно, чтобы либо сбой браузера, либо изредка вся операционная система. По какой-то причине любые изображения, сделанные на странице, не были собраны в мусор, даже при объединении и повторном использовании только нескольких экранных элементов DOM или использовании изображений в качестве свойства фонового изображения. Даже отображение изображений непосредственно, поскольку URI данных достаточно, чтобы рассчитывать на ограничение.

Решение оказалось довольно простым - использование position: absolute в элементах списка позволяет им собирать мусор достаточно быстро, чтобы не работать в пределе памяти. Это все еще связано с тем, что в любой момент в DOM было всего около 20-30 изображений, создавая и удаляя узлы DOM элемента с помощью прокрутки positon, наконец, сделал трюк.

Кажется, это особенно зависит от того, что webkit-transform':'scale3d() применяется к любому предку изображений в DOM. Я полагаю, что относительно текущая очень высокая DOM и рендеринг ее на графическом процессоре сбрасывает утечку памяти в webkit-рендерере?

Ответ 10

Я подал ошибку с jQuery, поскольку jQuery пытается справиться с утечками памяти... поэтому я считаю это ошибкой. Надеемся, что команда может придумать некоторый краткий и умный способ решения этой проблемы в Mobile Safari в ближайшее время.

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

Ответ 11

У меня тоже работает аналогичная проблема в Chrome, создавая расширение, которое загружает изображения на одной странице (всплывающее окно, фактически), заменяя старые изображения новыми. Память, используемая старыми изображениями (удаленная из DOM), никогда не освобождается, и она потребляет всю память ПК за короткое время. Попробовали различные трюки с CSS, без успеха. Используя аппаратное обеспечение с меньшим объемом памяти, чем ПК, например iPad, эта проблема возникает, естественно, естественно.