Сбой браузера iPad / iPhone при загрузке изображений в Javascript - PullRequest
53 голосов
/ 07 июня 2010

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

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

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

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

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

Ответы [ 11 ]

14 голосов
/ 16 июля 2010

Обновление: я думаю, что есть еще более простой способ сделать это, в зависимости от вашего приложения. Вместо нескольких изображений, если у вас просто есть один элемент <img> или Image объект (или, может быть, два, например, «это» изображение и «следующее» изображение, если вам нужны анимации или переходы), и просто обновите .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, которое я смог найти). Я использую его просто, чтобы вы могли увидеть, как работает скрипт. Возможно, вы захотите использовать прозрачный gif вместо этого, поэтому используйте вместо этого строку URL-адреса данных: data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==

12 голосов
/ 18 января 2011

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

Вторая часть решения заключается в том, что мобильное сафари может загружать неограниченное количество изображений через свойство 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> 
6 голосов
/ 17 августа 2010

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

Мой проект:

Приложение на основе PhoneGap с 6 основными разделами и около 45 подразделами, которые имеют галерею циклов jquery от 2 до 7 изображений, каждое из которых 640 x 440 (всего 215 изображений). Сначала я использовал ajax для загрузки фрагментов страницы, но с тех пор я перешел на одностраничный сайт со всеми разделами, скрытыми до необходимости.

Первоначально, пройдя около 20 галерей, я получил предупреждение о памяти 1, затем 2, а затем сбой.

После того, как все изображения были разделены на две части с изображением, примененным в качестве фона, я мог пройти через большее количество галерей (около 35) в приложении перед сбоем, но после перехода в ранее посещенные галереи он в конечном итоге потерпел неудачу.

Решение, которое, кажется, работает для меня, заключается в сохранении URL-адреса фонового изображения в атрибуте заголовка div и установке всех фоновых изображений в пустой GIF-файл. С более чем 215 изображениями я хотел сохранить URL-адрес в html для простоты и быстрого ознакомления.

Когда нажата кнопка субнавигации, я переписываю фоновое изображение css в правильный источник, который содержится в теге заголовка div, ТОЛЬКО для отображаемой галереи. Это избавило меня от необходимости делать какие-либо необычные javascript для хранения правильного исходного изображения.

var newUrl = $(this).attr('title');
$(this).css('background-image', 'url('+newUrl+')'); 

Когда нажата новая кнопка субнавигации, я переписываю фоновое изображение последних галерейных дивов в пустые гифки. Таким образом, кроме интерфейса gfx, у меня всегда активны только 2-7 изображений. Со всем, что я добавляю, содержащим изображения, я просто использую эту технику "ondemand", чтобы поменять заголовок с background-image.

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

5 голосов
/ 20 июля 2010

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

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

3 голосов
/ 08 февраля 2011

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

      $.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.

3 голосов
/ 10 октября 2010

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

  • Просто изменил фон 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 использует память.

Странная вещь, которую я заметил: ошибка возникает, даже если изображение не отображается! Например, при этом:

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

Каждые 5 секунд, и ничего больше, без загрузки или отображения изображения, конечно же, обернутого в объект, через некоторое время также вылетает память!

2 голосов
/ 06 августа 2010

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

2 голосов
/ 19 июля 2010

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

1 голос
/ 09 октября 2015

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

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

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

0 голосов
/ 26 августа 2010

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

...