Как запретить клик по элементу js
Перейти к содержимому

Как запретить клик по элементу js

  • автор:

Мышь: отмена выделения, невыделяемые элементы

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Более новая информация по этой теме находится на странице https://learn.javascript.ru/selection-range.

У кликов мыши есть неприятная особенность.

Двойной клик или нажатие с движением курсора как правило инициируют выделение текста.

Если мы хотим обрабатывать эти события сами, то такое выделение – некрасиво и неудобно. В этой главе мы рассмотрим основные способы, как делать элемент невыделяемым.

Для полноты картины, среди них будут и такие, которые применимы не только к событиям мыши.

Способ 1: отмена mousedown/selectstart

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

Если сделать двойной клик на таком элементе, то обработчик сработает. Но побочным эффектом является выделение текста браузером.

Текст

Чтобы избежать выделения, мы должны предотвратить действие браузера по умолчанию для события selectstart в IE и mousedown в других браузерах.

Полный код элемента, который обрабатывает двойной клик без выделения:

 
Двойной клик сюда выведет "Тест", без выделения

При установке на родителя – все его потомки станут невыделяемыми:

Выделение, всё же, возможно

Отмена действия браузера при mousedown/selectstart отменяет выделение при клике, но не запрещает его полностью.

Если пользователь всё же хочет выделить текстовое содержимое элемента, то он может сделать это.

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

Способ 2: снятие выделения постфактум

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

Для этого мы используем методы работы с выделением, которые описаны в отдельной главе Выделение: Range, TextRange и Selection. Здесь нам понадобится всего лишь одна функция clearSelection , которая будет снимать выделение.

Пример со снятием выделения при двойном клике на элемент списка:

У этого подхода есть две особенности, на которые стоит обратить внимание:

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

Способ 3: свойство user-select

Существует нестандартное CSS-свойство user-select , которое делает элемент невыделяемым.

Оно когда-то планировалось в стандарте CSS3, потом от него отказались, но поддержка в браузерах уже была сделана и потому осталась.

Это свойство работает (с префиксом) везде, кроме IE9-:

  Строка до.. 
Этот текст нельзя выделить (кроме IE9-)
.. Строка после

IE9-: атрибут unselectable=«on»

В IE9- нет user-select , но есть атрибут unselectable.

Он отменяет выделение, но у него есть особенности:

  1. Во-первых, невыделяемость не наследуется. То есть, невыделяемость родителя не делает невыделяемыми детей.
  2. Во-вторых, текст, в отличие от user-select , всё равно можно выделить, если начать выделение не на самом элементе, а рядом с ним.
 
Этот текст невыделяем в IE, кроме дочерних элементов

Левая часть текста в IE не выделяется при двойном клике. Правую часть ( em ) можно выделить, т.к. на ней нет атрибута unselectable .

Этот текст невыделяем в IE, кроме дочерних элементов

Итого

Для отмены выделения есть несколько способов:

  1. CSS-свойство user-select – везде кроме IE9- (нужен префикс, нестандарт).
  2. Атрибут unselectable=»on» – работает для любых IE (должен быть у всех потомков)
  3. Отмена действий на mousedown и selectstart :

elem.onmousedown = elem.onselectstart = function() < return false; >;

Какой же способ выбирать?

Это зависит от задач и вашего удобства, а также конкретного случая. Все описанные способы работают.

Недостаток user-select – в том, что посетитель теряет возможность скопировать текст. А что, если он захочет именно это сделать?

В любом случае эти способы не предназначены для защиты от выделения-и-копирования.

Если уж хочется запретить копирование – можно использовать событие oncopy :

 
Уважаемый копирователь, почему-то автор хочет заставить вас покопаться в исходном коде этой страницы. Если вы знаете JS или HTML, то скопировать текст не составит для вас проблемы, ну а если нет, то увы.

Event.preventDefault()

Метод preventDefault () интерфейса Event сообщает User agent, что если событие не обрабатывается явно, его действие по умолчанию не должно выполняться так, как обычно. Событие продолжает распространяться как обычно, до тех пор, пока один из его обработчиков не вызывает методы stopPropagation () или stopImmediatePropagation () , любой из которых сразу же прекращает распространение.Как отмечено ниже, вызов метода preventDefault () для события, не подлежащего отмене, например события, отправленного через EventTarget.dispatchEvent () , без указания cancelable: true не имеет эффекта.

Синтаксис

event.preventDefault();

Пример

По умолчанию щелчок по флажку переключает его состояние на противоположное. В этом примере показано, как предотвратить такое поведение:

doctype html> html> head> title>Пример preventDefaulttitle> script> function stopDefAction(evt)  evt.preventDefault(); > document .getElementById("my-checkbox") .addEventListener("click", stopDefAction, false); script> head> body> p>Пожалуйста, щёлкните по флажку.p> form> input type="checkbox" id="my-checkbox" /> label for="my-checkbox">Checkboxlabel> form> body> html> 

Вы можете посмотреть работу preventDefault в действии здесь.

В следующем примере некорректный ввод останавливается и вводимый символ не добавляется в поле с preventDefault() .

DOCTYPE html> html> head> title>Пример preventDefaulttitle> script> 
function Init()  var myTextbox = document.getElementById("my-textbox"); myTextbox.addEventListener("keypress", checkName, false); > function checkName(evt)  var charCode = evt.charCode; if (charCode != 0)  if (charCode  97 || charCode > 122)  evt.preventDefault(); alert( "Пожалуйста, используйте только буквы нижнего регистра на латинице" + "\n" + "charCode: " + charCode + "\n", ); > > > 
script> head> body onload="Init ()"> p>Пожалуйста, введите своё имя, используя только буквы нижнего регистра на латинице.p> form> input type="text" id="my-textbox" /> form> body> html> 

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

Примечания

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

Примечание: В Gecko 6.0, вызов preventDefault() приводит к event.defaultPrevented к переходу значения в состояние True .

Вы можете использовать event.cancelable (en-US) чтобы проверить, является ли событие отменяемым. Вызов preventDefault для неотменяемых событий не имеет никакого эффекта.

preventDefault не останавливает дальнейшее распространение событий на DOM. Для этого следует использовать event.stopPropagation (en-US) .

Спецификации

Specification
DOM Standard
# ref-for-dom-event-preventdefault③

Совместимость с браузерами

BCD tables only load in the browser

Всплытие и погружение

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

 
Если вы кликните на EM, сработает обработчик на DIV

Вам не кажется это странным? Почему же сработал обработчик на , если клик произошёл на ?

Всплытие

Принцип всплытия очень простой.

Когда на элементе происходит событие, обработчики сначала срабатывают на нём, потом на его родителе, затем выше и так далее, вверх по цепочке предков.

Например, есть 3 вложенных элемента FORM > DIV > P с обработчиком на каждом:

 body * 
FORM
DIV

P

Клик по внутреннему

вызовет обработчик onclick :

  1. Сначала на самом

    .

  2. Потом на внешнем .
  3. Затем на внешнем .
  4. И так далее вверх по цепочке до самого document .

Поэтому если кликнуть на

, то мы увидим три оповещения: p → div → form .

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

Почти все события всплывают.

Ключевое слово в этой фразе – «почти».

Например, событие focus не всплывает. В дальнейшем мы увидим и другие примеры. Однако, стоит понимать, что это скорее исключение, чем правило, всё-таки большинство событий всплывают.

event.target

Всегда можно узнать, на каком конкретно элементе произошло событие.

Самый глубокий элемент, который вызывает событие, называется целевым элементом, и он доступен через event.target .

Отличия от this (= event.currentTarget ):

  • event.target – это «целевой» элемент, на котором произошло событие, в процессе всплытия он неизменен.
  • this – это «текущий» элемент, до которого дошло всплытие, на нём сейчас выполняется обработчик.

Например, если стоит только один обработчик form.onclick , то он «поймает» все клики внутри формы. Где бы ни был клик внутри – он всплывёт до элемента , на котором сработает обработчик.

При этом внутри обработчика form.onclick :

  • this (= event.currentTarget ) всегда будет элемент , так как обработчик сработал на ней.
  • event.target будет содержать ссылку на конкретный элемент внутри формы, на котором произошёл клик.

example.css

form.onclick = function(event) < event.target.style.backgroundColor = 'yellow'; // браузеру нужно некоторое время, чтобы зарисовать всё жёлтым setTimeout(() =>< alert("target = " + event.target.tagName + ", this=" + this.tagName); event.target.style.backgroundColor = '' >, 0); >;
form < background-color: green; position: relative; width: 150px; height: 150px; text-align: center; cursor: pointer; >div < background-color: blue; position: absolute; top: 25px; left: 25px; width: 100px; height: 100px; >p < background-color: red; position: absolute; top: 25px; left: 25px; width: 50px; height: 50px; line-height: 50px; margin: 0; >body
     Клик покажет оба: и event.target, и this для сравнения: 
DIV

P

Возможна и ситуация, когда event.target и this – один и тот же элемент, например, если клик был непосредственно на самом элементе , а не на его подэлементе.

Прекращение всплытия

Всплытие идёт с «целевого» элемента прямо наверх. По умолчанию событие будет всплывать до элемента , а затем до объекта document , а иногда даже до window , вызывая все обработчики на своём пути.

Но любой промежуточный обработчик может решить, что событие полностью обработано, и остановить всплытие.

Для этого нужно вызвать метод event.stopPropagation() .

Например, здесь при клике на кнопку обработчик body.onclick не сработает:

  

event.stopImmediatePropagation()

Если у элемента есть несколько обработчиков на одно событие, то даже при прекращении всплытия все они будут выполнены.

То есть, event.stopPropagation() препятствует продвижению события дальше, но на текущем элементе все обработчики будут вызваны.

Для того, чтобы полностью остановить обработку, существует метод event.stopImmediatePropagation() . Он не только предотвращает всплытие, но и останавливает обработку событий на текущем элементе.

Не прекращайте всплытие без необходимости!

Всплытие – это удобно. Не прекращайте его без явной нужды, очевидной и архитектурно прозрачной.

Зачастую прекращение всплытия через event.stopPropagation() имеет свои подводные камни, которые со временем могут стать проблемами.

  1. Мы делаем вложенное меню. Каждое подменю обрабатывает клики на своих элементах и делает для них stopPropagation , чтобы не срабатывало внешнее меню.
  2. Позже мы решили отслеживать все клики в окне для какой-то своей функциональности, к примеру, для статистики – где вообще у нас кликают люди. Некоторые системы аналитики так делают. Обычно используют document.addEventListener(‘click’…) , чтобы отлавливать все клики.
  3. Наша аналитика не будет работать над областью, где клики прекращаются stopPropagation . Увы, получилась «мёртвая зона».

Зачастую нет никакой необходимости прекращать всплытие. Задача, которая, казалось бы, требует этого, может быть решена иначе. Например, с помощью создания своего уникального события, о том, как это делать, мы поговорим позже. Также мы можем записывать какую-то служебную информацию в объект event в одном обработчике, а читать в другом, таким образом мы можем сообщить обработчикам на родительских элементах информацию о том, что событие уже было как-то обработано.

Погружение

Существует ещё одна фаза из жизненного цикла события – «погружение» (иногда её называют «перехват»). Она очень редко используется в реальном коде, однако тоже может быть полезной.

Стандарт DOM Events описывает 3 фазы прохода события:

  1. Фаза погружения (capturing phase) – событие сначала идёт сверху вниз.
  2. Фаза цели (target phase) – событие достигло целевого(исходного) элемента.
  3. Фаза всплытия (bubbling stage) – событие начинает всплывать.

Ранее мы говорили только о всплытии, потому что другие стадии, как правило, не используются и проходят незаметно для нас.

Обработчики, добавленные через on -свойство или через HTML-атрибуты, или через addEventListener(event, handler) с двумя аргументами, ничего не знают о фазе погружения, а работают только на 2-ой и 3-ей фазах.

Чтобы поймать событие на стадии погружения, нужно использовать третий аргумент capture вот так:

elem.addEventListener(. ) // или просто "true", как сокращение для elem.addEventListener(. true)

Существуют два варианта значений опции capture :

  • Если аргумент false (по умолчанию), то событие будет поймано при всплытии.
  • Если аргумент true , то событие будет перехвачено при погружении.

Обратите внимание, что хоть и формально существует 3 фазы, 2-ую фазу («фазу цели»: событие достигло элемента) нельзя обработать отдельно, при её достижении вызываются все обработчики: и на всплытие, и на погружение.

Давайте посмотрим и всплытие и погружение в действии:

 body * 
FORM
DIV

P

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

Если вы кликните по

, то последовательность следующая:

  1. HTML → BODY → FORM → DIV (фаза погружения, первый обработчик)
  2. P (фаза цели, срабатывают обработчики, установленные и на погружение и на всплытие, так что выведется два раза)
  3. DIV → FORM → BODY → HTML (фаза всплытия, второй обработчик)

Существует свойство event.eventPhase , содержащее номер фазы, на которой событие было поймано. Но оно используется редко, мы обычно и так знаем об этом в обработчике.

Чтобы убрать обработчик removeEventListener , нужна та же фаза

Если мы добавили обработчик вот так addEventListener(. true) , то мы должны передать то же значение аргумента capture в removeEventListener(. true) , когда снимаем обработчик.

На каждой фазе разные обработчики на одном элементе срабатывают в порядке назначения

Если у нас несколько обработчиков одного события, назначенных addEventListener на один элемент, в рамках одной фазы, то их порядок срабатывания – тот же, в котором они установлены:

elem.addEventListener("click", e => alert(1)); // всегда сработает перед следующим elem.addEventListener("click", e => alert(2));

Итого

При наступлении события – самый глубоко вложенный элемент, на котором оно произошло, помечается как «целевой» ( event.target ).

  • Затем событие сначала двигается вниз от корня документа к event.target , по пути вызывая обработчики, поставленные через addEventListener(. true) , где true – это сокращение для .
  • Далее обработчики вызываются на целевом элементе.
  • Далее событие двигается от event.target вверх к корню документа, по пути вызывая обработчики, поставленные через on и addEventListener без третьего аргумента или с третьим аргументом равным false .

Каждый обработчик имеет доступ к свойствам события event :

  • event.target – самый глубокий элемент, на котором произошло событие.
  • event.currentTarget (= this ) – элемент, на котором в данный момент сработал обработчик (тот, на котором «висит» конкретный обработчик)
  • event.eventPhase – на какой фазе он сработал (погружение=1, фаза цели=2, всплытие=3).

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

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

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

Всплытие и погружение являются основой для «делегирования событий» – очень мощного приёма обработки событий. Его мы изучим в следующей главе.

Обработка клика вне элемента на JS (и также на jQuery)

Прежде, чем начать, сразу скажу, что в этом уроке вы найдёте две реализации обработки клика вне блока – на чистом JavaScript и на jQuery.

Кроме того, если вы хотить прокачать свои знания в JavaScript, jQuery и даже React.js, то хочу порекомендовать вам свой видеокурс!

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

Всплывающее окно на jQuery

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

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

Ну вот зачем усложнять код?

Хотя на самом деле ответ действительно очевиден — разработчики не знают, что можно сделать правильнее и проще, и делают так, как могут. Давайте разберем алгоритм:

  1. У нас открыто всплывающее модальное окно, меню или что-то ещё.
  2. Нам нужно, чтобы оно закрывалось не только на крестик (если таковой вообще имеется), но и по клику где-нибудь за его границами.
  3. Значит нам нужно событие «Когда произошёл клик по странице».
  4. В событие нужно добавить два условия «Если клик был не по нашему элементу» и «Если клик был не по дочерним элементам нашего элемента».
  5. Если оба условия выполняются, скрываем элемент.

Клик вне элемента на чистом JS

const div = document.querySelector( '#popup'); document.addEventListener( 'click', (e) => { const withinBoundaries = e.composedPath().includes(div); if ( ! withinBoundaries ) { div.style.display = 'none'; // скрываем элемент т к клик был за его пределами } })

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

document.addEventListener('keydown', function(e) { if( e.keyCode == 27 ){ // код клавиши Escape, но можно использовать e.key div.style.display = 'none'; } });

Клик вне элемента jQuery

jQuery(function($){ $(document).mouseup( function(e){ // событие клика по веб-документу var div = $( "#popup" ); // тут указываем ID элемента if ( !div.is(e.target) // если клик был не по нашему блоку && div.has(e.target).length === 0 ) { // и не по его дочерним элементам div.hide(); // скрываем его } }); });

Для разнообразия я использовал событие mouseup , т.е. когда кнопка мыши была нажата и уже отпущена. Для данного примера можно ещё добавить событие onclick на крестике, чтобы окно также закрывалось при клике на крестик.

И также в качестве бонуса – закрытие модального окна при нажатии клавиши Escape на клавиатуре:

$(document).on('keyup', function(e) { if ( e.key == "Escape" ) { $( "#popup" ).hide(); } });

Миша

Впервые познакомился с WordPress в 2009 году. Организатор и спикер на конференциях WordCamp. Преподаватель в школе Нетология.

Пишите, если нужна помощь с сайтом или разработка с нуля.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *