Асинхронная загрузка произвольного html
В связи с учитывать время загрузки страницы становится всё более актуально асинхронно подгружать части веб-страниц уже после загрузки основного минимума. Реклама — один из претендентов на отложенную загрузку, но простой ajax тут не поможет, т.к. в общем случае в подгружаемом куске может встречаться, например, document.write, и если документ уже загружен и закрыт то данный метод открывает его заново, обнуляя при этом.
Гугл в этом плане не сильно помогает, т.к. основной описываемый метод — подмена document.write своим собственным методом, который тупо добавляет аргумент в конец документа, но если вызов идёт не в конце и писать надо куда-то в середину — возникает проблема. На хабре
У меня возникла другая идея, возможно — велосипед, но желание попробовать было слишком сильным. А именно, подгружать отложенный код в скрытый iframe, а после загрузки переносить его содержимое туда, где оно должно быть. Более того, чтобы не делать лишних запросов к серверу, используется
Проблема с IE в том, что тот вообще не поддерживает data URI до 8-й версии, а в 8-й позволяет таким образом кодировать только картинки и стили но не html. В Chrome проблема в безопасности, он не даёт читать содержимое фрейма с data URI, считая что он загружен с другого домена. Наверняка загрузка iframe с сервера а не встраивание будет работать во всех броузерах, но я пока не проверял.
Само собой, куски DOM нельзя копировать между фреймами. Часть броузеров имеют метод document.importNode, но, как
Но если вырезать скрипты — а зачем они нужны, если они уже отработали и можно скопировать полученный в результате их работы DOM? — то могут потеряться методы и глобальные переменные, используемые в обработчиках событий. В качестве решения я копирую все свойства окна (window) из iframe, которых нет у главного окна. Возможно при этом будут проблемы с замыканиями (closure), не проверял. При попытке прочитать часть свойств возникают исключения, поэтому блок копирования нужно обернуть в try/catch.
Остаются стили, ибо подгружаемый html-код вполне может содержать CSS, который надо как-то скопировать. Как это сделать правильно я, честно говоря, не нашёл, и воспользовался методом window.getComputedStyle (он не кросс-броузерный, поэтому в IE надо будет делать по-другому), который содержит уже конечные стили элементов после вычисления всех классов и явно указанных свойств. При копировании DOM-иерархии я смотрю их в скрытом фрейме, куда подгружен html-код, и явно прописываю создаваемым элементам. Но копировать всё что есть — тоже не выход, поэтому пришлось составить «белый список» свойств, и аналогично обернуть в try/catch.
В итоге для отложенной загрузки надо в конец страницы прописать:
<iframe style='display:none' onLoad='l("...",this)' src='data:text/html;base64,...'></iframe>
где первое многоточие — ID элемента, в который надо загрузить код, второе — сам код, закодированный в base64. Естественно, такой iframe можно создавать динамически в любой момент, например при событиях ready или load. Загрузчик выглядит так:
1. var allowedStyles = {
2. color: true,
3. cursor: true,
4. backgroundColor: true,
5. backgroundImage: true,
6. borderTopWidth: true,
7. borderRightWidth: true,
8. borderBottomWidth: true,
9. borderLeftWidth: true,
10. display: true,
11. fontFamily: true,
12. fontSize: true,
13. fontSizeAdjust: true,
14. fontStretch: true,
15. fontStyle: true,
16. fontVariant: true,
17. fontWeight: true,
18. paddingTop: true,
19. paddingRight: true,
20. paddingBottom: true,
21. paddingLeft: true,
22. textAlign: true,
23. textDecoration: true,
24. };
25.
26. function im(node, rec, w2) {
27. switch (node.nodeType) {
28. case document.ELEMENT_NODE:
29. if (node.nodeName == 'script') return false;
30. if (node.nodeName == 'IFRAME') return document.importNode(node,true);
31. var newNode = document.createElement(node.nodeName);
32. // does the node have any attributes to add?
33. if (node.attributes && node.attributes.length > 0)
34. for (var i = 0, il = node.attributes.length; i < il; i++) { 35. var attrName = node.attributes[i].nodeName; 36. newNode.setAttribute(attrName, node.getAttribute(attrName)); 37. } 38. // are we going after children too, and does the node have any? 39. if (rec && node.childNodes && node.childNodes.length > 0)
40. for (var i = 0, il = node.childNodes.length; i < il; i++) {
41. var newChild = im(node.childNodes[i], rec, w2);
42. if (newChild) newNode.appendChild(newChild);
43. }
44. //
45. var styles = w2.getComputedStyle(node, null);
46. for (var s in styles) try {
47. if (allowedStyles[s]) newNode.style[s] = styles[s];
48. } catch (e) {}
49. return newNode;
50. case document.TEXT_NODE:
51. case document.CDATA_SECTION_NODE:
52. return document.createTextNode(node.nodeValue);
53. }
54. };
55.
56. function l(name, iframe) {
57. for (var i in iframe.contentWindow)
58. try {
59. if (window[i] === undefined)
60. window[i] = iframe.contentWindow[i];
61. } catch (e) {}
62.
63. var d = document.getElementById(name);
64. var children = iframe.contentDocument.body.childNodes;
65. for (var i=0, l=children.length; i
66. var clone = im(children[i], true, iframe.contentWindow);
67. if (clone) d.appendChild(clone);
68. }
69. }
Подход вполне рабочий, в данный момент, как я уже писал, работает только в FF и Opera, и довольно сырой, но мне хотелось поскорее поделиться идеей и почитать комментарии умных людей, прежде чем доделывать дальше. Одна из проблем, которая не решена — что делать если в подгружаемом коде в свою очередь тоже содержится iframe. Для рекламы это не редкость. Сейчас копируется элемент iframe вместе с src, но при этом содержимое фрейма загружается заново, и получается что фрейм грузится дважды. Копировать его содержимое через DOM или брать innerHTML, кодировать base64 и прописывать в src=«data:...» не выход, т.к. iframe может грузиться с другого домена, и доступа к его содержимому из соображений безопасности броузер не даст. Поэтому код, содержащий iframe, лучше так не подгружать.
Источник HabraHabr.ru





