Как изменить содержимое HTML при загрузке на страницу

Я тестирую A/B на нашем сайте, и большую часть своей работы я делаю в JS файле, который загружается в верхней части страницы, прежде чем что-либо еще будет отображаться, но после загрузки jQuery, который пригодится время от времени.

Взяв очень простой пример изменения тега H1, я бы обычно вводил стиль в голове, чтобы установить непрозрачность H1 в 0, а затем на DOMContentLoaded, я бы манипулировал содержимым H1, а затем задал непрозрачность 1. Причина этого заключается в том, чтобы избежать вспышки старого содержимого до того, как произойдет изменение - скрытие всего объекта более грациозно на глазу.

Я начал искать API MutationObserver. Я использовал это раньше, когда меняю содержимое в диалоговом окне оверлея, которое пользователь может открыть, что кажется довольно крутым подходом, и мне интересно, сумел ли кто-нибудь использовать MutationObserver для прослушивания документа при первом загрузке/синтаксический анализ и внесение изменений в документ перед первым рендерингом и перед DOMContentLoaded?

Этот подход позволил бы мне изменить содержание H1, не скрывая его, не меняя его, а затем показывая его.

Я попытался, но провалился до сих пор, и только что закончил тем, что читал о событиях Mutation для -объектов, и задаюсь вопросом, пытаюсь ли я сделать что-то, что просто невозможно. Однако нам (а не мне) удалось поставить робота на Марс, поэтому я надеюсь, что смогу это решить.

Итак, можно ли использовать MutationObservers для изменения содержимого HTML на лету, когда страница загружается/анализируется?

Спасибо за любую помощь или любые указатели.

С уважением, Ник

Ответ 1

Документы по MDN имеют общий неполный пример и не показывают типичных ошибок. Библиотека мутаций предоставляет удобную оболочку, но, как и все оболочки, она добавляет накладные расходы. См. Производительность MutationObserver для обнаружения узлов во всем DOM.

Создайте и запустите обозреватель.

Давайте использовать рекурсивный MutationObserver для всего документа, который сообщает обо всех добавленных/удаленных узлах.

var observer = new MutationObserver(onMutation);
observer.observe(document, {
  childList: true, // report added/removed nodes
  subtree: true,   // observe any descendant elements
});

Наивное перечисление добавленных узлов.

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

function onMutation(mutations) {
  mutations.forEach(mutation, m => {
    [...m.addedNodes]
      .filter(node =>
        node.localName === 'h1' && /foo/.test(node.textContent))
      .forEach(h1 => {
        h1.innerHTML = h1.innerHTML.replace(/foo/, 'bar');
      });
  });
}

Эффективное перечисление добавленных узлов.

Теперь самая сложная часть. Узлы в записи мутации могут быть контейнерами во время загрузки страницы (например, весь блок заголовка сайта, в котором все его элементы представлены как один добавленный узел): спецификация не требует перечисления каждого добавленного узла. индивидуально, поэтому нам придется заглядывать внутрь каждого элемента, используя querySelectorAll (очень медленно) или getElementsByTagName (очень быстро).

function onMutation(mutations) {
  for (var i = 0, len = mutations.length; i < len; i++) {
    var added = mutations[i].addedNodes;
    for (var j = 0, node; (node = added[j]); j++) {
      if (node.localName === 'h1') {
        if (/foo/.test(node.textContent)) {
          replaceText(node);
        }
      } else if (node.firstElementChild) {
        for (const h1 of node.getElementsByTagName('h1')) {
          if (/foo/.test(h1.textContent)) {
            replaceText(h1);
          }
        }
      }
    }
  }
}

function replaceText(el) {
  const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
  for (let node; (node = walker.nextNode());) {
    const text = node.nodeValue;
    const newText = text.replace(/foo/, 'bar');
    if (text !== newText) {
      node.nodeValue = newText;
    }
  }
}

Почему две уродливые ванильные циклы for? Поскольку forEach и filter и ES2015 for (val of array) могут работать очень медленно в некоторых браузерах, см. Производительность MutationObserver для обнаружения узлов во всем DOM.

Почему TreeWalker? Для сохранения любых слушателей событий, прикрепленных к подэлементам. Чтобы изменить только узлы Text: у них нет дочерних узлов, и их изменение не вызывает новую мутацию, потому что мы использовали childList: true, а не characterData: true.

Обработка относительно редких элементов с помощью живой коллекции HTMLC без перечисления мутаций.

Поэтому мы ищем элемент, который предполагается использовать редко, например, тег H1, или IFRAME и т.д. В этом случае мы можем упростить и ускорить обратный вызов наблюдателя с помощью автоматически обновляемого HTMLCollection, возвращаемого getElementsByTagName.

const h1s = document.getElementsByTagName('h1');

function onMutation(mutations) {
  if (mutations.length === 1) {
    // optimize the most frequent scenario: one element is added/removed
    const added = mutations[0].addedNodes[0];
    if (!added || (added.localName !== 'h1' && !added.firstElementChild)) {
      // so nothing was added or non-H1 with no child elements
      return;
    }
  }
  // H1 is supposed to be used rarely so there'll be just a few elements
  for (var i = 0, h1; (h1 = h1s[i]); i++) {
    if (/foo/.test(h1.textContent)) {
      // reusing replaceText from the above fragment of code 
      replaceText(h1);
    }
  }
}

Ответ 2

Я тестирую A/B для жизни, и я часто использую MutationObservers с хорошими результатами, но гораздо чаще я просто занимаюсь длинным опросом, который на самом деле является тем, что большинство сторонних платформ делают под капотом, когда вы используете их WYSIWYG (или иногда даже их редакторы кода). Петля длиной 50 миллисекунд не должна замедлять страницу или вызывать FOUC.

Обычно я использую простой шаблон, например:

var poller = setInterval(function(){
  if(document.querySelector('#question-header') !== null) {
    clearInterval(poller);

    //Do something
  }
}, 50);

Вы можете получить любой элемент DOM с помощью селектора sizzle, как вы могли бы в jQuery с document.querySelector, который иногда является единственной вещью, в которой вам нужна библиотека в любом случае.

Фактически мы делаем это так часто на моей работе, что у нас есть процесс сборки и библиотека модулей, которая включает в себя функцию под названием Когда выполняет именно то, что вы ищете. Эта конкретная функция проверяет как jQuery, так и элемент, но было бы тривиально модифицировать библиотеку, чтобы не полагаться на jQuery (мы полагаемся на jQuery, поскольку это на большинстве наших клиентских сайтов, и мы используем его для большого количества материалов).

Говоря о сторонних платформах тестирования и javascript-библиотеках, в зависимости от реализации многие платформы там (например, Optimizely, Qubit, и я думаю, Monetate) связывают версию jQuery (когда-то сокращенную), которая доступна сразу же, когда выполняя свой код, чтобы что-то посмотреть, если вы используете стороннюю платформу.