Неожиданно столкнулся с забавной проблемой. Кратко суть можно описать так. Допустим, вы используете requestAnimationFrame для динамического изменения цвета текста, например таким образом:

function render() {
  element.innerHTML = "<font color='#555'>some text</font>";
  requestAnimationFrame(render);
}

Если теперь на element (либо на весь документ) повесить обработчик события onclick, а потом кликнуть по тексту, то… ничего не происходит.

Подробнее

Итак, создадим div:

<div id="main"></div>

Растянем его на весь экран:

#main {
  width: 100%;
  height: 100%;
  position: absolute;
  cursor: default;
  background-color: #CCC;
  font-size: 30vh;
}

Теперь код:

function render() {
  document.getElementById("main").innerHTML = "<font color='#555'>click here</font>";
  requestAnimationFrame(render);
}

document.body.addEventListener("click", function(e) {
  console.log("click");
});

render();

Поиграться с кодом можно тут.

Результат следующий:

  • при клике на текст ничего не происходит, событие не возникает
  • при клике в любой другой части main событие возникает, в консоли выводится "click"

Дополнительные моменты:

  • если убрать font, а оставить просто текст, то клик происходит
  • если заменит font на span, то клика все равно не происходит
  • если закоментить строку requestAnimationFrame(render), то клик происходит
  • другие события, например mousemove, происходят как надо, и над текстом и без него

Зачем это нужно?

Вопрос не совсем относится к теме, но тем не менее: а зачем вообще такой код, какое практическое применение?

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

Создаем класс символа, в котором есть метод getText, выдающий символ текущего цвета, что-то вроде этого:

// some code

symbol.prototype.getText = function() {
  return "<font color='" + this._color + "'>" + this._text + "</font>";
};

Затем в requestAnimationFrame вставляем пересчет цветов и вывод всех символов по очереди на экран.

Осталось добавить обработку "click".

Решение

Первое, что пришло в голову, решение "в лоб". Добавим еще один div:

<div id="click"></div>

Такого же размера как и main, но прозрачный и с большим z-index:

#click {
  width: 100%;
  height: 100%;
  position: absolute;
  cursor: default;
  opasity: 0;
  z-index: 100;
}

После этого клик на тексте и вне его работает нормально.

Из других вариантов приходит на ум только следующее. Заранее добавить для каждого символа элемент span с уникальным индексом, а в requestAnimationFrame просто менять цвет через document.getElementById("span_id").style.color. Мне кажется этот вариант слишком громоздким.

Послесловие

Я не разбирался, в чем конкретная причина такого поведения click-а. Если есть люди, которые понимают, в чем тут дело, просьба поделиться мудростью. Спасибо!

Update

Как указал Zibx в комментариях, чтобы состоялось событие click, события mousedown и mouseup должны произойти на одном элементе. Поскольку в данном случае innerHTML постоянно обновляется, этого не случается.

Комментарии (8)


  1. hell0w0rd
    21.03.2016 17:42
    +4

    У вас постоянно происходит перерисовка #main. В тот момент, как вы кликаете по тексту, этого текста в DOM уже нет.
    Попробуйте в render добавить логирование.


  1. Zibx
    21.03.2016 17:45
    +7

    Для клика маусдаун и маусап должен происходить на одном и том же элементе. В коде выше render зовётся без остановки и перезапихивает html. В результате постоянно получается новый элемент.


    1. cnupm99
      21.03.2016 18:03

      Спасибо, действительно, логично и довольно простое объяснение, когда его знаешь)


      1. Xazzzi
        22.03.2016 06:35

        Такое применение requestAnimationFrame вообще является антипаттерном.
        Если взять ваш пример с хаотичным цветом, то должна была быть функция-рендер, создающая DOM-дерево, и функция-аниматор, которую и нужно было зациклить через requestAnimationFrame. Последняя работала бы с уже существующими элементами просто меняя один аттрибут, и "бага" с click не наблюдалось бы.


        1. cnupm99
          22.03.2016 12:54

          Спасибо, я понял эту идею.
          Просто когда делал, представил, что это будет допустим 80х30 = 2400 символов с уникальными id, не маленькое DOM дерево получается :) На первый взгляд мой вариант показался проще, и вот что получилось.


  1. Anisotropic
    21.03.2016 17:51
    +5

    Сначала делаем костыли в виде устаревшего и давно не использующегося тега "font", а потом мужественно их решаем. Цвет нужно было менять через style.color на уже существующем элементе, а не создавать каждый раз новый.


    1. cnupm99
      21.03.2016 18:08
      -4

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

      мужественно их решаем
      хорошо сказано)


    1. alek0585
      22.03.2016 12:37

      Не будьте так суровы к посту с пометкой «из песочницы» ;)