Создание стиля Node, добавление innerHTML, добавление в DOM и головные боли IE

У меня вопрос двух частей.

Во-первых, сценарий:

Из-за некоторых странных проблем, с которыми мы столкнулись в отношении поддержки мобильных браузеров NOSCRIPT, мне поручено разработать альтернативное решение для обнаружения JS. Логика решения состоит в том, чтобы на странице было два DIV. Один из них - ошибка, указывающая, что у вас нет JS и его показаний по умолчанию. Если у нас есть JS, мы хотим добавить новый блок STYLE в HEAD, который преодолевает предыдущий CSS и скрывает ошибку и вместо этого отображает содержимое.

Пример HTML:

<div id="div1">div 1 (should be shown if JS enabled)</div>
<div id="div2">div 2 (should be hidden if JS enabled)</div>

Это JS я начал с:

  var styleNode = document.createElement('style');
  styleNode.setAttribute("type", "text/css");
  styleNode.innerHTML = "#div1 {display: block;} #div2 {display: none;}";
  headTag.appendChild(styleNode);

Но у меня были проблемы. Некоторые поисковые запросы, приводящие к этому описанию проблемы безопасности, которую может иметь IE, если вы попытаетесь вставить innerHTML в созданный элемент, прежде чем поместить его в DOM:

http://karma.nucleuscms.org/item/101

Итак, я модифицировал script как таковой:

  var styleNode = document.createElement('style');
  styleNode.setAttribute("type", "text/css");
  var headTag = document.getElementsByTagName("head")[0];
  headTag.appendChild(styleNode);
  var aStyleTags = headTag.getElementsByTagName("style");
  var justAddedStyleTag = aStyleTags[aStyleTags.length-1];
  justAddedStyleTag.innerHTML = "#div1 {display: block;} #div2 {display: none;}";

Вопрос 1: является ли допустимым обходным решением проблемы IE? Есть ли более эффективное решение?

вопрос 2: даже с настройкой, script все еще не работает в IE. Он отлично работает в Firefox, но в IE 7 я получаю "неизвестную ошибку времени выполнения".

У меня есть образец этого кода в JSBIN:

http://jsbin.com/ucesi4/4

Кто-нибудь знает, что происходит с IE?

UPDATE:

Я наткнулся на эту ссылку через google. Обратите внимание на последний комментарий:

http://msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx

Тем не менее, вы действительно должны положить все правила стиля в HEAD для строгого соответствие XHTML. Это можно сделать также быть немного сложным, потому что вы не может использовать innerHTML для инъекции непосредственно HEAD или STYLE. (Оба эти тега читаются ТОЛЬКО.)

Eep! Правда? Является ли FireFox чрезмерным прощением? Или это просто очень странный IE quirk?

ОБНОВЛЕНИЕ 2:

Немного больше о том, что мы пытаемся решить здесь. Мы имеем дело с мобильными устройствами и некоторыми устаревшими устройствами: а) не поддерживают NOSCRIPT и b) имеют медленные JS-двигатели.

Так как они не поддерживают NOSCRIPT, мы по умолчанию показываем ошибку, а затем скрываем ее через JS, если она есть, и представляя их с соответствующим контентом. Из-за медленных двигателей JS на них люди видят "мерцание" DIV, показывающее/скрывающееся. Это было предлагаемое решение для обработки этого, поскольку оно загрузило бы CSS до того, как DIVs были даже отображены.

Так как он кажется недействительным, решение будет состоять в том, что на этих старых устройствах мы будем использовать этот метод (поскольку он работает, даже если не в IE), а затем все другие соответствующие браузеры будут делать, как было предложено... мы просто обновим свойство DISPLAY CSS через встроенный JS после того, как каждый DIV будет загружен в DOM.

Все, что сказал, мне все еще интересно узнать, является ли эта проблема ошибкой IE, или если IE действительно придерживается надлежащих стандартов, делая STYLE элементом только для чтения.

Ответ 1

В IE вы можете использовать style.styleSheet.cssText:

var style = document.createElement('style');
style.type = 'text/css';

if (style.styleSheet) { // IE
    style.styleSheet.cssText = css;
} else {
    style.appendChild(document.createTextNode(css));
}

document.getElementsByTagName('head')[0].appendChild(style);

Попробуйте здесь: http://jsfiddle.net/QqF77/

См. ответ на этот вопрос: Как создать <style> тегом с Javascript

Ответ 2

Не используйте innerHTML, используйте document.createTextNode(), и ваша жизнь станет бесконечно лучше;)

var styleNode = document.createElement('style');
styleNode.setAttribute("type", "text/css");
var textNode = document.createTextNode("#div1 {display: block;} #div2 {display: none;}");
styleNode.appendChild(textNode);
headTag.appendChild(styleNode);

EDIT:

Поскольку это решение, похоже, не работает для вас, я бы отказался от него. Вместо этого перейдите к решению, в котором стили уже определены, и где вы просто отключили/активировали стили с помощью javascript, если они доступны.

Вы можете сделать это следующим образом:

<head>
<style>
.jsenabled #div2, #div1 { display: none;}
.jsenabled #div1, #div2 { display: block;}
</style>
<script>
//i know you don't use jQuery, but the solution should still be valid as a concept
//bind to DOM-ready, then set the class jsenabled on the body tag
$(function() {
 $(document.body).addClass('jsenabled');
});
</script>
</head>
<body>
<div id="div1">div 1 (should be shown if JS enabled)</div>
<div id="div2">div 2 (should be hidden if JS enabled)</div>
</body>

ИЗМЕНИТЬ 2:

Если это нужно сделать до готовности DOM, вы можете сделать что-то вроде уродливого:

<head>
<style>
#div2, .show { display: block;}
#div1, .hide { display: none;}
</style>
</head>
<body>
<div class="hide">
<script>document.write('</div><div id="div1">');</script>
   div 1 (should be shown if JS enabled)
</div>
<script>document.write('<div class="hide">');</script>
<div id="div2">div 2 (should be hidden if JS enabled)</div>
<script>document.write('</div>');</script>
</body>

Или чтобы все было просто, вы могли просто сделать

<head>
<script>document.write('<style>#div1 {display: block;} #div2 {display: none;}</style>');
</head>
<body>
<div id="div1">div 1 (should be shown if JS enabled)</div>
<div id="div2">div 2 (should be hidden if JS enabled)</div>
</body>

Ответ 3

Лучшей практикой является размещение всей ссылки < link для CSS в < head и most < script на самом конце тела < body (кроме коротких фрагментов < script, которые должны выполняться КАК МОЖНО СКОРЕЕ идти в < head). Как стандартные, так и браузеры позволяют делать глупые вещи, но только потому, что это приемлемо, это не значит, что это хорошая идея.

Вместо того, чтобы проходить через все rigamarole создания стиля node и манипулирования DOM (которые просто предоставляют больше возможностей для чего-то не так), я рекомендую гораздо более простое решение. Жесткий код столько, сколько вы можете в свой документ (и таблицу стилей?) Заранее один раз (а не создавать вещи "на лету" каждый раз). Вот пример:

<body ...
...
<div id="warning" style="display: block;">
JS is required for this site, but isn't available ...
</div>
<div id="message" style="display: none;">
JS is available
</div>
...

<script ...
var warningEl = document.getElementById('warning');
warningEl.style.display = 'none';
var messageEl = document.getElementById('message');
messageEl.style.display = 'block';
</script ...

Это должно работать достаточно хорошо в большинстве случаев (это, безусловно, будет работать в большем количестве браузеров, чем то, что вы пытались сделать вначале). Однако любая попытка изменить DOM каким-либо образом до того, как страница будет сначала отображаться пользователю, будет не гарантирована работать (другими словами, ни ваш путь, ни пример выше всегда будет работать во всех случаях). Чем раньше вы запускаете свой Javascript, тем более вероятные операции DOM (включая getElementById) могут завершиться неудачно. И чем позже вы запустите свой Javascript, тем более вероятно, что пользователь заметит заметное "мерцание" своего дисплея. Таким образом, вам предлагается выбор компромисса между широкой совместимостью и заметным мерцанием.

Вы можете подождать, пока DOM гарантированно будет полностью готов, прежде чем вы запустите свой Javascript ( "готов" в jQuery или addEventListener "domcontextloaded" или даже addEventListener "load" ). Во всех случаях гарантируется правильное функционирование. Но во многих случаях он мерцает (возможно, очень плохо).

Единственный способ, которым я знаю (но я надеюсь, что кто-то еще знает больше:-), чтобы полностью исключить возможность мерцания, это поставить в < head фрагмент Javascript, который изменяет window.location, но ничего не делает. (Нет ссылок на DOM, которые обычно становятся очевидными под словом "документ" где-то в коде.) Чистый эффект, если JS доступен, будет немедленной перезагрузкой другой страницы, прежде чем большая часть чего-либо будет показана пользователю. Начальная страница может содержать ваше предупреждение, а новая страница - реальный материал без предупреждения.

Даже этот метод имеет некоторые недостатки: во-первых, двойная загрузка требует дополнительной полосы пропускания и немного задерживает видимость пользователя. Это не имеет значения на типичных рабочих столах. Но на карманных компьютерах это может быть неприемлемо. И, во-вторых, это приведет к хаосу с SEO. Вторая страница, которая должна быть невидимой и доступна только с первой страницы, может отображаться независимо в webdexes, чтобы пользователи могли легко получить к ней доступ напрямую (вы можете "исправить" это умным использованием "канонических" и/или "мета... роботы" ). И SERP начальной страницы может упасть стремительно, когда единственным ее содержанием является предупреждающее сообщение.