катастрофический отказ браузера iPad/iPhone при загрузке изображений в JavaScript

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

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

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

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

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

53
задан Steve Simitzis 6 June 2010 в 10:53
поделиться

7 ответов

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

2
ответ дан 7 November 2019 в 08:52
поделиться

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


Думаю, я действительно нашел способ обойти это.

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

waitAndReload () здесь только для того, чтобы дать изображению время появиться на экране. Mobile Safari работает довольно медленно и отображает большие изображения, поэтому требуется время после загрузки изображения, чтобы раскрасить экран.

shrinkImages () просматривает все ранее загруженные изображения (кроме активного) и меняет .src на адрес dataurl.

Я использую здесь образ файловой папки для dataurl (это было первое изображение dataurl, которое я смог найти). Я использую его просто для того, чтобы вы могли видеть, как скрипт работает. Вместо этого вы, вероятно, захотите использовать прозрачный gif, поэтому используйте вместо этого эту строку URL-адреса данных: data: image / gif; base64, R0lGODlhAQABAIAAAP /// wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw ==

14
ответ дан 7 November 2019 в 08:52
поделиться

Пока мне повезло, я использовал теги

вместо тегов и установил изображение в качестве фонового изображения div.

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

5
ответ дан 7 November 2019 в 08:52
поделиться

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

2
ответ дан 7 November 2019 в 08:52
поделиться

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

Мой проект:

Приложение на основе 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 галереи, чтобы оно было пустым gif. Итак, помимо интерфейса gfx, у меня всегда есть только 2-7 активных изображений. Ко всему, что я добавляю, что содержит изображения, я просто использую эту технику «ondemand», чтобы поменять заголовок на фоновое изображение.

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

6
ответ дан 7 November 2019 в 08:52
поделиться

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

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

-1
ответ дан 7 November 2019 в 08:52
поделиться

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

0
ответ дан 7 November 2019 в 08:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: