Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Как обеспечить надёжное срабатывание onmouseout.


краткое содержание

Как обеспечить надёжное срабатывание onmouseout.

При быстрых перемещениях мыши браузер не всегда успевает обработать onmouseout на элементе, чтобы, например, убрать его с зкрана. 3 способа решения этой задачи и один вывод о событиях мыши.
Существует дней: 224
Автор: 12345c
Другие выпуски:
Рассылка 'Упражнения по яванскому письму. Javascript.'
 
Статья.
30.11.06

Контроль за наведением мыши.

Известно, что если элемент, на котором имеется обработчик события onmouseover, onmouseout, имеет небольшие размеры, то любой браузер при быстрых перемещениях мыши может его не заметить. Если по onmouseover появляется некоторый объект (подсказка, например), то её непоявление не страшно - при медленном движении мыши она появится. Значительно хуже то, что та же подсказка может не отключиться при отведении мыши, потому что при быстром перемещении событие просто не возникнет, браузер не заметит уход мыши с поля события.

Описанная проблема существует во всех браузерах и связана с тем, что события onmouseover и подобные им возникают довольно часто - при каждом прохождении границы элемента, и браузер просто не успевает отработать их все. Если с этим явлением не бороться, возникнут неприятные стуации, когда подсказка или пункт меню продолжает висеть на экране, хотя указатель мыши находится уже в совсем другом месте.

Один из способов решения - не делать слишком мелких активных элементов. При размере элемента (текста, кнопки, рисунка) более 10 пикс., как показывает практика, уже очень сложно заставить браузер ошибиться. Если же элемент представляет собой границу шириной 1-2 пикс. или накрыт другим элементом так, что его видимый для мыши край имеет ширину 1-2 пикс. - эффект несрабатывания событий виден во всей красе.

Возьмём простой пример - рамка таблицы. Будем следить за событием таблицы, но не за событием ячейки этой таблицы. Фактически, при проведении мышью над такой одноячеечной таблицей происходят 6 (шесть) событий прохождения границ:

  • Наведение на таблицу.
  • Выход с поля таблицы, наведение на ячейку.
  • Выход из ячейки, наведение на таблицу.
  • Выход из таблицы.

...не говоря о том, что если пытаться контролировать ещё onmousemove, времени на обработку всех событий при современных скоростях обработки скриптов в браузерах явно не хватит.

Для разнообразия постановки экспериментов введём в таблицу тег (источник событий) так, чтобы между ним и краем таблицы остался маленький зазор.

<script>d=document;
g=function(X){return document.getElementById(X);};
nBgC=-1;A=['red','green','blue'];</script>
<center><table 
  style="border:1px solid gray;width:400px;height:250px;">
  <tr><th><center><table
    style="width:120px;background:yellow">
  <tr id=cell  style="background:#fff">
    <td align=center
  onMouseOver="g('cell').style.backgroundColor= \\
    A[++nBgC%A.length];g('b1').innerHTML= \\
    (parseInt(g('b1').innerHTML)+1)+'over';"
  onMouseOut=g('cell').style.backgroundColor='#fff'; \\
    g('b1').innerHTML+='Out';>
      <b id=b1 onMouseOver=event.cancelBubble=!0;
        style="background:#eef"> 0 </b>
</td></tr></table></center></th></tr></table></center>
(обратные слеши означают: убрать перенос строки)

В указанном примере сделаны условия для отображения несрабатывания другого события - onmouseover, но немного изменив пример, получим несрабатывание onmouseout - достаточно изменить атрибут события во внутреннем теге.

Если быстро двигать мышью вверх-вниз по тексту, то будут наблюдаться несрабатывания события, которому мы запретили распространяться по дереву документа. Читатели могут возразить, что пример искусственный, однако такое часто встречается на практике - когда работает событие наведения, но не обрабатывается событие отведения мыши. Возможно, если их обрабатывать одновременно, то, если они происходят всегда в одной точке, скрипт будет работать правильно. Но кто даст этому гарантии, да ещё для всех браузеров? Выключив одно из событий, видим, что точки обработки событий проскакивают тонкую полоску в 2 пикселя, не замечая её. Если расширить - ловля событий становится довольно надёжной.

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

Что же делать в случае, если ловить событие с малых элементов надо, например, для надёжного выключения слоя, если мышь покинула его пределы?

Есть ещё один способ, который разработчики браузеров проработали более тщательно - пользоваться стилевыми псевдоклассами (":hover", ":link"). Очевидно, проблема с ними быть могла, будь скорость обработки событий для стилей такой же низкой, как для скриптов. Но браузер не может позволить себе такую неряшливость и использует все средства для качественной отработки псевдоклассов. Как это сделали на этапе разработки браузера? Это мог быть и высший приоритет, и обработка быстрой компилированной процедурой, и дополнительный контроль за прохождением границы активной области, который мы сделаем в этой статье для медленного скрипта, которому посвящена статья. Или, действительно, обработка всех событий в одной точке, а не только, скажем, наведения мыши.

Эта негарантированность срабатывания событий есть основная причина того, что делать меню, работающие по наведению мыши, на порядок сложнее, чем меню, действующие по кликам. Кроме того, что события возникают от каждой границы элементов (вставили рисунок в меню или даже наклонный шрифт - вот ещё один тег, элемент и источник событий) и нагружают процессор своей обработкой, иногда они могут не сработать! Поэтому при проектировании скриптов с событиями перемещения мыши следует учитывать это упрямый факт. Поэтому так трудно модифицировать меню с наведением мыши. Добавили один новый элемент в меню, нарушили стройную подогнанную структуру обработки - пишите скрипт по-новой.

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

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

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

 

Способ решения вытекает из описанного нами факта - события наведения (и перемещения) мыши надёжно возникают на достаточно больших площадях. Точнее, достаточно длинных траекториях мыши без пересечения границ элемента. Помогает в решении и тот факт, что события всплывают - если на странице много мелких элементов, обрамлённых одним крупным, какое-нибудь из событий сможет появиться на крупном элементе и быть зафиксировано. И, наконец, прямая подсказка к решению - то, что вокруг меню обычно существует большое поле документа.

Ключом к решению является такая философская мысль, что если мы не можем гарантированно поймать отведение мыши с маленького объекта, мы можем поймать наведение (или перемещение) мыши на большом окаймляющем элементе. Да, надо брать событие мыши на документе или на окружающем слое. Впрочем, если маленький интересующий нас активный слой расположен близко к краю окна, то это не поможет - мышь может быстро уйти за край окна. Поэтому было упомянуто перемещение: если мышь снова зайдёт на окно и создаст событие перемещения по документу, но не по активному объекту - тут сможем с уверенностью сказать, что мышь уже покинула объект и следует проверить, действительно ли было отработано onmouseout.

 

Следующий пример показывает, как тот же элемент (таблица с рамкой) дополнительно контролируется прохождением мыши по документу вне активного элемента. Чтобы показать, что скрипт работает, в течение первых 2 секунд можем наблюдать неподходящие нам действия, а затем скрипт исправляет недоделанное основным скриптом. Для наглядности и переносимости примера выбрано не document.onmousemove, а такое же событие над окаймляющим слоем (таблицей).

Очевидно, что если есть механизм контроля, то он может быть сделан единственным для выполнения действия (если неактуально замечание о крае окна, сделанное выше). Например, проверяя движение мыши по документу вне меню, сворачиваем все ранее открытые пункты меню, потому что знаем, что мышь покинула их.

<script>d=document;
g=function(X){return document.getElementById(X);};
nBgC=-1;A=['red','green','blue'];
clrField=function(){waitChk=1;
  setTimeout("g('b2').innerHTML=parseInt(g('b2').innerHTML)\
  +'overOut';waitChk=0;",2000);}//демонстрационная задержка правки
</script>
<center><table style="border:1px solid gray;width:400px;
  height:250px;background:#f7f7f7" onmousemove
="t=d.all?event.srcElement:event.target;if(self.waitChk)return;\\
   if(t.className!='t1')clrField();"><tr><th><center>
<table class=t1      (элемент с классом, для которого правки нет)
  style="border:0px solid gray;width:120px;background:green">
  <tr id=cell2  style="background:#fcfcfc">
    <td class=t1 align=center onmouseover
    ="g('cell2').style.backgroundColor=A[++nBgC%A.length]; \\
    g('b2').innerHTML=(parseInt(g('b2').innerHTML)+1)+'over';"
  onmouseout=g('cell2').style.backgroundColor='#fcfcfc'; \\
    g('b2').innerHTML+='Out';>
      <b id=b2 onMouseOver=event.cancelBubble=!0;>  0  </b>
</td></tr></table></center></th></tr></table></center>

Контроль обрамляющего элемента имеет известные неудобства. Он требует проверять частопро­исходящие события на документе - делать лишнюю работу для браузера. Чтобы этого избежать, можно попробовать копать задачу глубже.

Куда двигаться глубже? Вероятно, в сторону упомянутого предположения, что все события мыши обрабатываются в одной точке траектории, а не рассыпаны по очереди одно за другим. Основания так предположить даёт тот же первый пример. Если запретить на теге B оба события наведения мыши, увидим, что пример перестаёт давать ошибки, несмотря на то, что активная область имеет ширину в 2 пикселя.

   <b id=b1 onMouseOver=event.cancelBubble=!0;
     onmouseout=event.cancelBubble=!0;
     style="background:#eef"> 0 </b>

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

Например, нельзя произвольно ставить появление подсказки (по наведению!) на один элемент, а убирать отведением с другого, особенно, с близкорасположенными границами. Или можно, но обязательно введение контроля на окаймляющем элементе. Или можно, но границы должны отстоять минимум на 5-6 пикселей и помнить о взрывоопасности этого "сплава". Или можно, но обязательно получить и корректно обработать парное событие с первого элемента. Корректность заключается в том, что если было на элементе "over", то обязательно будет "out", но нельзя надеяться, что любое из этой пары будет на соседнем элементе.

 

Уровень: для начинающих

Примечание.
30.11.06

Практика работы с неинсталлированным IE7.

В предыдущем выпуске было описано, как установить в Windows XP одновременно IE6 и IE7. Некоторые мелкие замечания.

Работа с IE7, запускаемом в системе без инсталляции, показала себя положительно - в браузере можно тестировать страницы и скрипты и даже полноценно работать. С одной оговоркой, которая была подробно описана: не ставить расширение KB915865, идущее в комплекте дистрибутива, отдельно запрашивающее разрешения. С ним работают табы и показывается меню масштабирования в статус-баре. Но платить приходится многим: IE6, чистый, без оболочки типа GreenBrowser или Maxthon, не может открвывать сайт из адресной строки - его выбрасывает в другой браузер, установленный по умолчанию. Которым начинает считаться или IE7, или оболочка IE6, но не IE6 сам по себе (что-то нарушается в настройках реестра, вероятно).

Было предложение запускать XP с IE7 из-под виртуальной машины (VMWare, MS Virtual PC (freeware), ...). Достаточно хорошая мысль - IE7 будет работать чисто, без условностей отсутствия инсталляции. Правда, не в реальном времени, поэтому тестировать скрипты в некоторых случаях будет затруднительно. Лучшее решение, конечно - установка другой ОС на соседнем компьютере.

Уровень: для пользователей

 javascript.aho.ru , © I.Svetlov, 2005-2006 
Текущая очерёдность плана статей (подписчики могут корректировать через голосование).
9. Многуровневое меню с навигацией по наведению мыши.
8. Ключевые слова новых технологий, которые нужно знать разработчику веб-страниц.
3. Как писать тексты с доступом через JS без экранирования специальных символов (&lt; и другие).
4. select и list - в них есть много общего. Как и с меню навигации. Эмулятор селекта.
5. Древовидное меню, подход к данным, отделение данных от представления.
6. Многонедельный календарь со ссылками. (По списку строится календарь.)

Форум сайта рассылки, почта автора рассылки.

 


В избранное