Это очень поздний ответ, но я оставлю его тому, кому он нужен.
Задача: загрузить содержимое iframe из разных источников, выдать onLoaded
в случае успеха и onError
в случае ошибки загрузки.
Это самое кросс-браузерное решение, независимое от происхождения, которое я мог бы разработать. Но прежде всего кратко расскажу о других подходах, которые у меня были, и почему они плохие.
<сильный>1. iframe Это было для меня небольшим шоком, что iframe имеет только событие onload
, и оно вызывается при загрузке и при ошибке, нет никакого способа узнать, ошибка это или нет.
<сильный>2. performance.getEntriesByType('resource'). Этот метод возвращает загруженные ресурсы. Похоже, то, что нам нужно. Но какой позор, Firefox всегда добавляет ресурс в массив ресурсов, независимо от того, загружен он или нет. Невозможно узнать по экземпляру Resource
, был ли он успешным. Как обычно. Кстати, этот способ не работает в ios‹11.
<сильный>3. script Я пытался загрузить html с помощью тега <script>
. Выдает onload
и onerror
корректно, к сожалению, только в Chrome.
И когда я уже был готов сдаться, мой старший коллега рассказал мне о теге html4 <object>
. Это похоже на тег <iframe>
, за исключением того, что у него есть запасные варианты, когда контент не загружен. Похоже, это то, что нам нужно! К сожалению, это не так просто, как кажется.
РАЗДЕЛ КОДА
var obj = document.createElement('object');
// we need to specify a callback (i will mention why later)
obj.innerHTML = '<div style="height:5px"><div/>'; // fallback
obj.style.display = 'block'; // so height=5px will work
obj.style.visibility = 'hidden'; // to hide before loaded
obj.data = src;
После этого мы можем установить некоторые атрибуты в <object>
, как мы хотели сделать с iframe
. Единственная разница, мы должны использовать <params>
, а не атрибуты, но их имена и значения идентичны.
for (var prop in params) {
if (params.hasOwnProperty(prop)) {
var param = document.createElement('param');
param.name = prop;
param.value = params[prop];
obj.appendChild(param);
}
}
Теперь самое сложное. Как и многие похожие элементы, <object>
не имеет спецификаций для обратных вызовов, поэтому каждый браузер ведет себя по-разному.
- Хром. При ошибке и при загрузке генерирует событие
load
.
- Firefox. Выдает
load
и error
правильно.
- Сафари. Ничего не излучает....
Похоже, ничем не отличается от iframe
, getEntriesByType
, script
.... Но у нас есть встроенный резервный вариант браузера! Итак, поскольку мы устанавливаем резервный вариант (innerHtml) напрямую, мы можем сказать, загружен ли <object>
или нет.
function isReallyLoaded(obj) {
return obj.offsetHeight !== 5; // fallback height
}
/**
* Chrome calls always, Firefox on load
*/
obj.onload = function() {
isReallyLoaded(obj) ? onLoaded() : onError();
};
/**
* Firefox on error
*/
obj.onerror = function() {
onError();
};
Но что делать с Сафари? Старый добрый setTimeout
.
var interval = function() {
if (isLoaded) { // some flag
return;
}
if (hasResult(obj)) {
if (isReallyLoaded(obj)) {
onLoaded();
} else {
onError();
}
}
setTimeout(interval, 100);
};
function hasResult(obj) {
return obj.offsetHeight > 0;
}
Да.... не так быстро. Дело в том, что <object>
при сбое не упоминается в спецификациях поведения:
- Пытаюсь загрузить (размер=0)
- Не удается (размер = любой) действительно
- Резервный вариант (размер = как в InnnerHtml)
Итак, код нуждается в небольшом улучшении
var interval = function() {
if (isLoaded) { // some flag
return;
}
if (hasResult(obj)) {
if (isReallyLoaded(obj)) {
interval.count++;
// needs less then 400ms to fallback
interval.count > 4 && onLoadedResult(obj, onLoaded);
} else {
onErrorResult(obj, onError);
}
}
setTimeout(interval, 100);
};
interval.count = 0;
setTimeout(interval, 100);
Ну и для начала загрузки
document.body.appendChild(obj);
Это все. Я постарался объяснить код во всех деталях, чтобы он не выглядел таким глупым.
P.S. WebDev отстой
person
Дмитрий Киселев
schedule
01.03.2019