Event - garevna/js-course GitHub Wiki
В цепочке прототипов любого элемента DOM есть объект ( класс ) EventTarget
Благодаря этому все элементы DOM способны "реагировать" на события
Выведем в консоль этот объект:
console.dir ( EventTarget )Более всего нас интересует, конечно, его свойство prototype
▼ ƒ EventTarget()
arguments: null
caller: null
length: 0
name: "EventTarget"
▼ prototype: EventTarget
► addEventListener: ƒ addEventListener()
► dispatchEvent: ƒ dispatchEvent()
► removeEventListener: ƒ removeEventListener()
► constructor: ƒ EventTarget()
Symbol(Symbol.toStringTag): "EventTarget"
► __proto__: Object
► __proto__: ƒ ()Здесь мы видим три метода, которые унаследуют все объекты, имеющие в цепочке прототипов EventTarget
Мы уже в курсе, что объект Node наследует от объекта EventTarget,
а объект Element наследует от Node,
потому что элементы DOM - это частный случай узла DOM
В свойстве prototype объекта Element мы обнаружим не слишком длинный перечень свойств, начинающихся на on
Однако объект Element является только прототипом объекта HTMLElement
А вот последний как раз и является непосредственным прототипом всех элементов DOM
Поэтому, очевидно, искать события, общие для всех элементов DOM, нужно именно в его свойстве prototype
for ( var prop in HTMLElement.prototype ) {
if ( prop.indexOf ( 'on' ) !== 0 ) continue
console.info ( `Event: ${prop.slice(2)}` )
}Однако элементы DOM значительно отличаются друг от друга
У каждого html-элемента есть собственный конструктор, который "добавляет" специфические" для этого элемента события
( например, события input и change могут произойти только на элементах форм )
![]() |
|---|
document.body.ondomnodeinserted =
function ( event ) {
console.log ( event )
}
document.body.appendChild (
document.createElement ( "div" )
)▼ MutationEvent {isTrusted: true, relatedNode: body, prevValue: "", newValue: "", attrName: "", …}
attrChange: 0
attrName: ""
bubbles: true
cancelBubble: false
cancelable: false
composed: false
currentTarget: null
defaultPrevented: false
eventPhase: 0
isTrusted: true
newValue: ""
► path: (5) [div, body, html, document, Window]
prevValue: ""
► relatedNode: body
returnValue: true
► srcElement: div
► target: div
timeStamp: 12720.100000005914
type: "DOMNodeInserted"
► __proto__: MutationEventкаждое событие создается конструктором Event
У каждого события есть свойство type ( строка ):
✅ click
✅ mouseover
✅ mouseout
✅ mouseenter
✅ mouseleave
✅ mousedown
✅ mouseup
✅ keydown
✅ keyup
...
В примере выше тип события - DOMNodeInserted
Кроме того, у каждого объекта события есть свойство target, которое является ссылкой на элемент, на котором произошло событие
В большинстве случаев это тот элемент, на который был "повешен" обработчик
body,
а свойство target указывает на добавленный в DOM элемент
В следующем примере свойство target будет ссылкой на тот элемент, на котором произошел клик
const pictures = [
"https://www.insidescience.org/sites/default/files/5_heic1808a_crop.jpg",
"https://gobelmont.ca/Portals/0/xBlog/uploads/2017/9/6/dancing-156041_960_720.png",
"https://i2-prod.mirror.co.uk/incoming/article11840943.ece/ALTERNATES/s615/PAY-MATING-BUGS.jpg",
"https://i.redd.it/otqqqga0ip211.jpg"
]
const divs = pictures.map (
picture => {
let div = document.body.appendChild (
document.createElement ( "div" )
)
div.style = `
width: 100px;
height: 100px;
border: solid 1px gray;
`
div.onclick = function ( event ) {
let img = event.target.appendChild (
document.createElement ( "img" )
)
img.src = picture
img.width = 100
}
return div
})Полный перечень событий DOM можно найти в спецификации:
Конструктор, с помощью которого создаются все события DOM
Свойство prototype конструктора Event содержит свойства, которые будут унаследованы всеми событиями
var userEvent = new Event( 'user' )Метод dispatchEvent "отправляет" событие элементу
document.body.onclick = function ( event ) {
this.style.backgroundColor = "#fa0"
}
document.body.dispatchEvent ( new Event ( 'click' ) )Конструктор CustomEvent создает кастомное событие c дополнительными параметрами
function addElement ( tagName, container ) {
var _container =
container && container.nodeType === 1 ?
container : document.body
return _container.appendChild (
document.createElement ( tagName )
)
}
var obj = addElement ( "h1" )
obj.innerText = "Hi"
obj.addEventListener ( "listen", listenHandler )
function listenHandler ( event ) {
this.innerText = event.detail
}
var btn = addElement ( "button" )
btn.innerText = "Change"
btn.onclick = function ( event ) {
var inp = addElement ( "input" )
inp.onchange = function ( event ) {
obj.dispatchEvent (
new CustomEvent ( "listen",
{
detail: this.value
}
)
)
this.parentNode.removeChild ( this )
}
}У всех элементов есть свойства с именами, начинающимися с "on"
Значения этих свойств должны быть ссылкой на функцию, которая будет вызвана при возникновении события ( event handler )
Обработчик события - это функция, которая вызывается тогда, когда событие произошло
Если определить значение свойства onclick элемента как функцию clickCallback, то в момент, когда пользователь кликнет левой кнопкой мышки на этом элементе, будет вызвана функция clickCallback
Функция clickCallback станет обработчиком события click элемента
Поэтому при объявлении функции-обработчика настоятельно рекомендуется в качестве первого параметра указывать имя переменной, в которую будет помещена ссылка на объект события:
elem.onclick = function ( event ) { ... }
elem.onmouseover = function ( mev ) { ... }Таким образом объект события становится доступным внутри обработчика
Координаты указателя мышки относительно левого верхнего угла физического экрана
Координаты указателя мышки относительно верхнего левого края видимой части окна браузера ( viewport )
| ☕ |
|---|
Эти координаты не зависят от положения полосы прокрутки окна браузера
Координаты указателя мышки относительно верхнего левого края страницы
Эти координаты зависят от положения полосы прокрутки окна браузера
| ☕ |
|---|
![]() |
|---|
Методы добавления и удаления прослушивателей событий:
✅ addEventListener
✅ removeEventListener
👀 Свойства "on..." позволяют "повесить" только одного обработчика данного события на данный элемент
👂 eventListener-ов может быть сколько угодно для одного и того же элемента и одного и того же события
Предположим, мы вешаем обработчика события mousemove на все элементы div
Затем вешаем "персонального" обработчика события mousemove на div#sample
На элементе div#sample "сработают" оба обработчика при наведении указателя мышки
Первый аргумент метода addEventListener - это тип события ( строка ), например: "mouseover" "mouseout" "input" "change" ...
Второй аргумент - ссылка на функцию ( обработчика события )
☕ 1️⃣
document.getElementById ( '#sample' )
.addEventListener ( 'click', function ( event ) {
console.log ( 'sample click event: ', event )
})☕ 2️⃣
var elem = document.body.appendChild (
document.createElement ( "p" )
)
elem.innerText = "Hello"
function clickdHandler ( event ) {
this.innerHTML = `
<small>
My content was changed!
</small>
`
}
elem.addEventListener ( 'click', clickdHandler )Третий аргумент - логическое значение - будучи установленным в true, позволяет перехватить событие на фазу погружения ( capturing )
☕ 3️⃣
var btn = document.createElement ( 'button' )
btn.innerText = "OK"
btn.style = `
background-image: url(https://cdn2.iconfinder.com/data/icons/user-23/512/User_Yuppie_2.png);
background-size: contain;
background-repeat: no-repeat;
background-position: left center;
padding: 5px 10px 5px 30px;
`
document.body.appendChild ( btn )
btn.addEventListener ( 'click', function ( event ) {
console.log ( event.currentTarget.tagName, event.eventPhase )
}, true )
document.body.addEventListener ( 'click', function ( event ) {
console.info ( event.currentTarget.tagName, event.eventPhase )
}, true )Прослушивателей событий нужно удалять, поскольку они не убираются автоматически при удалении элемента
При удалении нужно передавать точно такие же аргументы, какие были переданы методу addEventListener при создании прослушивателя
☕ 4️⃣
Такой вариант удаления не сработает:
document.getElementById ( 'sample' )
.addEventListener ( 'click', function ( event ) {
console.log ( 'sample click event: ', event )
})
document.getElementById ( 'sample' )
.removeEventListener ( 'click', function ( event ) {
console.log ( 'sample click event: ', event )
})☕ 5️⃣
А такой - да:
function clickHandler ( event ) {
this.innerHTML = '<small>My content was changed!</small>'
}
elem.addEventListener ( 'click', clickHandler )
elem.removeEventListener ( 'click', clickHandler )☕ 6️⃣
<div id="main-frame" class="wrapper">
<div id="main-content">
<div id="main-message">
<h1>Event Listener</h1>
<p>Тестируем работу eventListener</p>
<div id="list">
<p>Что нужно помнить:</p>
<ul class="single">
<li>eventListener-ов нужно удалять</li>
<li>Для этого есть метод removeEventListener</li>
</ul>
</div>
<div class="error">Error was detected</div>
<div id="diagnose">Печалька</div>
</div>
</div>
</div>
<div id="error">
<p id="details">____________________</p>
</div>var collection = document.querySelectorAll ( 'p ~ *' )
collection.forEach ( x => {
if ( x.nodeType === 1 )
x.addEventListener ( 'mouseover', function ( event ) {
console.warn (
event.target.tagName + (
event.target.id ? '#' + event.target.id :
event.target.className ?
'.' + event.target.className :
' content: ' + event.target.innerHTML )
)
} )
} )
var elem = document.querySelector ( '#list' )
elem.addEventListener ( 'mouseover', function ( event ) {
event.target.innerHTML = `event: ${new Date().toLocaleString()}`
} )
function clickHandler ( event ) {
this.innerHTML = '<small>My content was changed!</small>'
}
elem.addEventListener ( 'click', clickHandler )Иногда мы не хотим, чтобы при наступлении события элемент HTML вел себя так, как он должен себя вести по умолчанию
В таком случае мы можем использовать метод preventDefault()
Например, по умолчанию в результате клика на гиперссылке происходит переход по адресу, указанному атрибутом href
Мы можем внутри обработчика события click элемента a вызвать метод preventDefault(), что предотвратит поведение по умолчанию, и перехода не будет
☕ 7️⃣
var elem = document.body.appendChild (
document.createElement ( 'a' )
)
elem.innerText = "click me"
elem.href = "https://www.w3schools.com/charsets/ref_utf_punctuation.asp"
elem.addEventListener ( 'click',
function ( event ) {
event.preventDefault()
alert ( `href: ${this.href}` )
}
)Почти все события "всплывают" ( но не все, например, событие focus не всплывает )
Предотвращает "всплытие" события, т.е. срабатывание обработчиков этого события на элементах, внутри которых находится целевой элемент
☕ 8️⃣
Запустите код в консоли, кликните на самом маленьком кружке и посмотрите, что будет выведено в консоль
var elemData = {
name: "div",
attrs: {
className: "container",
title: "Контейнер",
style: `
position: absolute;
top: 20px;
left: 20px;
border-radius: 50%;
border: dotted 2px #789;
background-color: #70ff9090;
`
}
}
function clickHandler ( event ) {
// event.stopPropagation()
console.info ( this.num )
}
function insertElement ( elemNum, parentElem ) {
var elem = parentElem.appendChild (
document.createElement ( elemData.name )
)
elem.num = elemNum
for ( var attr in elemData.attrs )
elem [ attr ] = elemData.attrs [ attr ]
elem.style.width = `${400 - elemNum * 50}px`
elem.style.height = `${400 - elemNum * 50}px`
elem.addEventListener ( 'click', clickHandler )
return elem
}
var elems = []
elems [0] = insertElement ( 0, document.body )
for ( var x = 1; x < 5; x++ ) {
elems [x] = insertElement ( x, elems [ x - 1 ] )
}Теперь перезагрузите страницу, опять вставьте код, но раскомментируйте строку
event.stopPropagation()кликните на самом маленьком кружке и посмотрите, что будет выведено в консоль
Если у элемента есть несколько прослушивателей одного и того же события, они будут вызваны в том порядке, в котором они были добавлены
Если один из обработчиков, установленных одним из этих listener-ов, вызовет метод event.stopImmediatePropagation (), то остальные listener-ы, следующие за ним, уже не сработают
☕ 9️⃣
Если выполнить следующий код в консоли:
var elem = document.body.appendChild (
document.createElement ( 'p' )
)
elem.innerHTML = 'Click me, please'
var text = [
'Hello',
'are you happy?',
'what is your favorite language?',
'Bye'
]
elem.addEventListener ( 'click',
function ( event ) {
// event.stopImmediatePropagation()
console.log ( 'Я тут первый, остальные на фиг!' )
}
)
for ( var txt of text ) {
elem.addEventListener ( 'click',
( function ( message ) {
return function () {
console.log ( message )
}
})( txt )
)
}то при клике на элементе сработают все прослушиватели собятия click элемента в той последовательности, в какой мы их определили
Однако если убрать слеши перед строчкой
event.stopImmediatePropagation()то сработает только один прослушиватель, и выведена в консоль будет только одна строчка
Примеры в песочнице:
☕ mouseover & mouseout vs mouseenter & mouseleave

