Действия браузера по умолчанию

Многие события автоматически влекут за собой действие браузера.

Например:

  • Клик по ссылке инициирует переход на новый URL.
  • Нажатие на кнопку «отправить» в форме – отсылку ее на сервер.
  • Двойной клик на тексте – инициирует его выделение.

Если мы обрабатываем событие в JavaScript, то зачастую такое действие браузера нам не нужно. К счастью, его можно отменить.

Отмена действия браузера

Есть два способа отменить действие браузера:

  • Основной способ – это воспользоваться объектом события. Для отмены действия браузера существует стандартный метод event.preventDefault().
  • Если же обработчик назначен через onсобытие (не через addEventListener), то можно просто вернуть false из обработчика.

В следующем примере при клике по ссылке переход не произойдет:

<a href="/" onclick="return false">Нажми здесь</a>
или
<a href="/" onclick="event.preventDefault()">здесь</a>
Возвращать true не нужно

Обычно значение, которое возвращает обработчик события, игнорируется.

Единственное исключение – это return false из обработчика, назначенного через onсобытие.

Иногда в коде начинающих разработчиков можно увидеть return других значений. Но они не нужны и никак не обрабатываются.

Пример: меню

Рассмотрим задачу, когда нужно создать меню для сайта, например такое:

<ul id="menu" class="menu">
  <li><a href="/php">PHP</a></li>
  <li><a href="/html">HTML</a></li>
  <li><a href="/javascript">JavaScript</a></li>
  <li><a href="/flash">Flash</a></li>
</ul>

Данный пример при помощи CSS может выводиться так:

HTML-разметка сделана так, что все элементы меню являются не кнопками, а ссылками, то есть тегами <a>.

Это потому, что некоторые посетители очень любят сочетание «правый клик – открыть в новом окне». Да, мы можем использовать и <button> и <span>, но если правый клик не работает – это их огорчает. Кроме того, если на сайт зайдёт поисковик, то по ссылке из <a href="..."> он перейдёт, а выполнить сложный JavaScript и получить результат – вряд ли захочет.

Поэтому в разметке мы используем именно <a>, но обычно клик будет обрабатываться полностью в JavaScript, а стандартное действие браузера (переход по ссылке) – отменяться.

Например, вот так:

menu.onclick = function(event) {
  if (event.target.nodeName != 'A') return;

  var href = event.target.getAttribute('href');
  alert( href ); // может быть подгрузка с сервера, генерация интерфейса и т.п.

  return false; // отменить переход по url
};

В конце return false, иначе браузер перейдёт по адресу из href.

Так как мы применили делегирование, то меню может увеличиваться, можно добавить вложенные списки ul/li, стилизовать их при помощи CSS – обработчик не потребует изменений.

Другие действия браузера

Действий браузера по умолчанию достаточно много.

Вот некоторые примеры событий, которые вызывают действие браузера:

  • mousedown – нажатие кнопкой мыши в то время как курсор находится на тексте начинает его выделение.
  • click на <input type="checkbox"> – ставит или убирает галочку.
  • submit – при нажатии на <input type="submit"> в форме данные отправляются на сервер.
  • wheel – движение колёсика мыши инициирует прокрутку.
  • keydown – при нажатии клавиши в поле ввода появляется символ.
  • contextmenu – при правом клике показывается контекстное меню браузера.

Все эти действия можно отменить, если мы хотим обработать событие исключительно при помощи JavaScript.

События могут быть связаны между собой

Некоторые события естественным образом вытекают друг из друга.

Например, нажатие мышкой mousedown на поле ввода <input> приводит к фокусировке внутрь него. Если отменить действие mousedown, то и фокуса не будет.

Попробуйте нажать мышкой на первый <input> – произойдёт событие onfocus. Это обычная ситуация.

Но если нажать на второй, то фокусировки не произойдёт.

<input value="Фокус работает" onfocus="this.value=''">
<input onmousedown="return false" onfocus="this.value=''" value="Кликни меня">

Это потому, что отменено стандартное действие при onmousedown.

…С другой стороны, во второй <input> можно перейти с первого нажатием клавиши Tab, и тогда фокусировка сработает. То есть, дело здесь именно в onmousedown="return false".

Особенности IE8-

В IE8- для отмены действия по умолчанию нужно назначить свойство event.returnValue = false.

Кроссбраузерный код для отмены действия по умолчанию:

element.onclick = function(event) {
  event = event || window.event;

  if (event.preventDefault) { // если метод существует
    event.preventDefault(); // то вызвать его
  } else { // иначе вариант IE8-:
    event.returnValue = false;
  }
}

Можно записать в одну строку:

...
event.preventDefault ? event.preventDefault() : (event.returnValue=false);
...

Итого

  • Браузер имеет встроенные действия при ряде событий – переход по ссылке, отправка формы и т.п. Как правило, их можно отменить.
  • Есть два способа отменить действие по умолчанию: первый – использовать event.preventDefault() (IE8-: event.returnValue=false), второй – return false из обработчика. Второй способ работает только если обработчик назначен через onсобытие.

Задачи

важность: 3

Почему в этом документе return false не работает?

<script>
  function handler() {
    alert( "..." );
    return false;
  }
</script>

<a href="http://w3.org" onclick="handler()">w3.org</a>

По замыслу, переход на w3.org при клике должен отменяться. Однако, на самом деле он происходит.

В чём дело и как поправить?

Дело в том, что обработчик из атрибута onclick делается браузером как функция с заданным телом.

То есть, в данном случае он будет таким:

function(event) {
  handler() // тело взято из атрибута onclick
}

При этом возвращаемое handler значение никак не используется и не влияет на результат.

Рабочий вариант:

<script>
  function handler() {
    alert("...");
    return false;
  }
</script>

<a href="http://w3.org" onclick="return handler()">w3.org</a>

Также можно использовать объект события для вызова event.preventDefault(), например:

<script>
  function handler(event) {
    alert("...");
    event.preventDefault();
  }
</script>

<a href="http://w3.org" onclick="handler(event)">w3.org</a>
важность: 5

Сделайте так, чтобы при клике на ссылки внутри элемента #contents пользователю выводился вопрос о том, действительно ли он хочет покинуть страницу и если он не хочет, то прерывать переход по ссылке.

Так это должно работать:

Детали:

  • Содержимое #contents может быть загружено динамически и присвоено при помощи innerHTML. Так что найти все ссылки и поставить на них обработчики нельзя. Используйте делегирование.
  • Содержимое может содержать вложенные теги, в том числе внутри ссылок, например, <a href=".."><i>...</i></a>.

Открыть песочницу для задачи.

Это – классическая задача на тему делегирования.

В реальной жизни, мы можем перехватить событие и создать AJAX-запрос к серверу, который сохранит информацию о том, по какой ссылке ушел посетитель.

Мы перехватываем событие на contents и поднимаемся до parentNode пока не получим A или не упремся в контейнер.

contents.onclick = function(evt) {
  var target = evt.target;

  function handleLink(href) {
    var isLeaving = confirm('Уйти на ' + href + '?');
    if (!isLeaving) return false;
  }

  while (target != this) {
    if (target.nodeName == 'A') {
      return handleLink(target.getAttribute('href')); // (*)
    }
    target = target.parentNode;
  }
};

В строке (*) используется атрибут, а не свойство href, чтобы показать в confirm именно то, что написано в HTML-атрибуте, так как свойство может отличаться, оно обязано содержать полный валидный адрес.

Открыть решение в песочнице.

важность: 5

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

Результат должен выглядеть так:

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

P.S. Обратите внимание – клик может быть как на маленьком изображении IMG, так и на A вне него. При этом event.target будет, соответственно, либо IMG, либо A.

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

  • Если получится – сделайте предзагрузку больших изображений, чтобы при клике они появлялись сразу.
  • Всё ли в порядке с семантической вёрсткой в HTML исходного документа? Если нет – поправьте, чтобы было, как нужно.

Открыть песочницу для задачи.

Решение состоит в том, чтобы добавить обработчик на контейнер #thumbs и отслеживать клики на ссылках.

Когда происходит событие, обработчик должен изменять src #largeImg на href ссылки и заменять alt на ее title.

Код решения:

var largeImg = document.getElementById('largeImg');

document.getElementById('thumbs').onclick = function(e) {
  var target = e.target;

  while (target != this) {

    if (target.nodeName == 'A') {
      showThumbnail(target.href, target.title);
      return false;
    }

    target = target.parentNode;
  }

}

function showThumbnail(href, title) {
  largeImg.src = href;
  largeImg.alt = title;
}

Предзагрузка картинок

Для того, чтобы картинка загрузилась, достаточно создать новый элемент IMG и указать ему src, вот так:

var imgs = thumbs.getElementsByTagName('img');
for (var i = 0; i < imgs.length; i++) {
  var url = imgs[i].parentNode.href;

  var img = document.createElement('img');
  img.src = url;
}

Как только элемент создан и ему назначен src, браузер сам начинает скачивать файл картинки.

При правильных настройках сервера как-то использовать этот элемент не обязательно – картинка уже закеширована.

Семантичная верстка

Для списка картинок используется DIV. С точки зрения семантики более верный вариант – список UL/LI.

Открыть решение в песочнице.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.