Это очень поздний ответ, но я оставлю его тому, кто в нем нуждается.
Задача: загрузить содержимое iframe из разных источников, выдать onLoaded
в случае успеха и onError
при ошибке загрузки .
Это самое независимое от браузеров решение, которое я мог разработать. Но прежде всего я кратко расскажу о других подходах, которые у меня были, и о том, почему они плохие.
1. iframe Это был небольшой шок для меня, что iframe имеет только событие onload
, и оно вызывается при нагрузке и при ошибке, нет способа узнать, является ли это ошибкой или нет.
2. performance.getEntriesByType ( 'Ресурс') . Этот метод возвращает загруженные ресурсы. Похоже, что нам нужно. Но какой позор, Firefox всегда добавляет Resource в массив ресурсов, независимо от того, загружен он или нет. К Resource
экземпляру не было никакого способа узнать, был ли он успешным. По-прежнему. Кстати, этот метод не работает в IOS <11. </p>
3. скрипт Я пытался загрузить 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>
не имеет спецификаций для обратных вызовов, поэтому каждый браузер ведет себя по-разному.
- Chrome . При ошибке и при загрузке генерируется событие
load
.
- Firefox . Выдает
load
и error
правильно.
- Safari . Ничего не излучает ....
Похоже, ничем не отличается от 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();
};
Но что делать с Safari? Старый добрый 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 отстой