Mouse events order mousedown --> mouseup --> click --> mousedown --> mouseup --> click --> dblclick e.button e.button get the exact clicked mouse button e.button = 0 Left button (primary) e.button = 1 Middle button (auxiliary) e.button = 2 Right button (secondary) e.button = 3 X1 button (back) e.button = 4 X2 button (forward) e.which same, but old non-standard way document.addEventListener('click', (e) => console.log(e.button)) // 0 document.addEventListener('contextmenu', (e) => console.log(e.button)) // 2 e.modifiers e.shiftKey Shift e.altKey Alt (or Opt for Mac) e.ctrlKey Ctrl e.metaKey Cmd for Mac true if the key was pressed during the event Detect if press alt+shift during a click... document.addEventListener('click', function (e) { if (e.altKey && e.shiftKey) alert('Hooray!') }) mouseover, mouseout mouseover triggers when pointer comes over an element e.target - element where the mouse came over e.relatedTarget - element from which the mouse came mouseout triggers when pointer leaves an element e.target - element that the mouse left e.relatedTarget - new under-the-pointer element, that mouse left for relatedTarget can be null if mouse came from out of the window or left the window If mouseover triggered, there must be mouseout mousemove doesn't trigger on every pixel, that’s good for performance but some DOM-elements may be skipped it’s possible that the pointer jumps right inside the middle of the page from out of the window In that case relatedTarget is null, because it came from “nowhere” mouseover event bubbles up if parent has mouseover handler, it may seem that the mouse pointer left #parent element, and then immediately came back over it to avoid such mess we need to examine event.target and relatedTarget and if the mouse is still inside the element, then ignore such event or better to use mouseenter & mouseleave events outterDiv.onmouseover = outterDiv.onmouseout = handler function handler(e) { console.log(`${e.type}: target=${e.target.id}, relatedTarget=${e.relatedTarget.id}`) } mouseenter, mouseleave events are similar to mouseover & mouseout triggers when the mouse pointer enters/leaves an element no bubbling transitions inside the element, to/from descendants, are not counted event delegation not possible div.onmouseenter = div.onmouseleave = handler function handler(e) { console.log(`${e.type}: target=${e.target.id}, relatedTarget=${e.relatedTarget.id}`) } Event delegation Highlight cells on click... table.onclick = function(e) { let td = e.target.closest('td') // we may click on some tag inside td, so we find the nearest td if (!td) return // check if was inside any <td> if (!table.contains(td)) return // check if it is out table highlight(td) // do action } Highlight all els in table under mouse... tbl.onmouseover = e => e.target.style.background = 'pink' tbl.onmouseout = e => e.target.style.background = '' Highlight only TDs... let curEl = null tbl.onmouseover = function(e) { if (curEl) return // if currentElem is set, we didn't leave the previous <td> let target = e.target.closest('td') if (!target) return // we moved not into a <td> if (!tbl.contains(target)) return // moved into <td>, but outside of our table (possible in case of nested tables) curEl = target // hooray! we entered a new <td> curEl.style.background = 'pink' } tbl.onmouseout = function(e) { if (!curEl) return // if we're outside of any <td> now let relatedTarget = e.relatedTarget // we're leaving the element – where to? Maybe to a descendant? while (relatedTarget) { // go up the parent chain and check – if we're still inside currentElem if (relatedTarget == curEl) return // internal transition relatedTarget = relatedTarget.parentNode } curEl.style.background = '' // we left the <td>. really curEl = null } Pointer events Pointer events allow handling mouse, touch, pen events simultaneously, with a single piece of code We can replace "mouse" with "pointer" in event names and expect our code to continue working for mouse Pointer events types touchstart , touchend , touchmove pointerdown similar to mousedown pointerup similar to mouseup pointermove similar to mousemove pointerover similar to mouseover pointerout similar to mouseout pointerenter similar to mouseenter pointerleave similar to mouseleave pointercancel gotpointercapture fires when an element uses el.setPointerCapture(pointerId) to enable capturing lostpointercapture fires when the capture is released Pointer events properties Pointer events have the same properties as mouse events, plus... pointerId the unique identifier of the pointer causing the event pointerType pointing device type, “mouse”, “pen” or “touch”, we can use this property to react differently on various pointer types isPrimary = true for the primary pointer (the first finger in multi-touch) width width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it’s always 1 height the height of the area where the pointer touches the device. Where unsupported, it’s always 1 pressure the pressure of the pointer tip, in range from 0 to 1. For devices that don’t support pressure must be either 0.5 (pressed) or 0 tangentialPressure the normalized tangential pressure tiltX , tiltY , twist pen-specific properties that describe how the pen is positioned relative the surface. Multi-touch handling multi-touch can be done with the help of the pointerId and isPrimary properties if we use 5 fingers simultaneously, we have 5 pointerdown events with their respective coordinates and a different pointerId when the user moves and then removes a finger, we get pointermove and pointerup events with the same pointerId as we had in pointerdown isPrimary = true events associated with the first finger and isPrimary = false for others pointerId assigned for each touching finger document.onpointerdown = document.onpointerup = log function log(e) { console.log( `${e.type} isPrimary=${e.isPrimary} pointerId=${e.pointerId}`) } pointercancel event fires when ongoing pointer interaction is aborted ...or pointer device hardware was physically disabled ...or device orientation changed ...or browser decided to handle the interaction on its own Prevent the default browser action to avoid pointercancel after this browser won’t hijack the process and doesn’t emit pointercancel Set CSS to { touch-action: none } ball.ondragstart = () => false setPointerCapture el.setPointerCapture(pointerId) binds events with the given pointerId to el all pointer events with the same pointerId will have elem as the target no matter where in document they really happened in other words, re-targets all subsequent events with the given pointerId to el The binding is removed when pointerup or pointercancel events occur or elem is removed from the document or el.releasePointerCapture(pointerId) called Pointer capturing can be used to simplify drag’n’drop kind of interactions we use pointerdown and pointermove on thumb, but there is a problem …As the pointer moves, it may leave the slider thumb: go above or below it then we need to assign pointermove on the whole document if we do it on document level, there might be some side effects, trigger other event handlers and here the magic comes... We can call thumb.setPointerCapture(event.pointerId) in pointerdown handler Then future pointer events will be re-targeted to thumb When pointerup happens (dragging complete), the binding is removed automatically, we don’t need to care about it if a user moves the pointer around the whole document, events handlers will be called on thumb <div id="slider"> <div id="thumb"></div> </div> let shiftX thumb.onpointerdown = function(e) { e.preventDefault() // prevent selection start (browser action) shiftX = e.clientX - thumb.getBoundingClientRect().left thumb.setPointerCapture(e.pointerId) } thumb.onpointermove = function(e) { if(!e.target.hasPointerCapture(e.pointerId)) return let newLeft = e.clientX - shiftX - slider.getBoundingClientRect().left // if the pointer is out of slider => adjust left to be within the bounaries if (newLeft < 0) newLeft = 0 let rightEdge = slider.offsetWidth - thumb.offsetWidth if (newLeft > rightEdge) newLeft = rightEdge thumb.style.left = newLeft + 'px' } thumb.ondragstart = () => false keydown, keyup keyboard events should be used to handle keyboard actions (virtual keyboard also) use input event to track input into an <input> field keyboard events are not enough coz there are also speech recognition, copy/paste with mouse etc... Event types e.key get the character, language layout specific e.code get the “physical key code”, for hotkey e.keypress , e.keyCode , e.charCode , e.which - legacy, many browser incompatibilities e.repeat - true if an event is triggered by auto-repeat document.addEventListener('keydown', (e) => { console.log('e.key', e.key) console.log('e.code', e.code) console.log('e.repeat', e.repeat) }) // hold shift+D // e.key D // e.code KeyD // e.repeat true Default actions <input> expects a tel number, does not accept keys except digits , + , - , ( , ) <input id="inp" onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel"> function checkPhoneKey(key) { return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace'; } Or... inp.addEventListener('keydown', e => { if ( !( (e.key >= '0' && e.key <= '9') || e.key == '+' || e.key == '(' || e.key == ')' || e.key == '-' || e.key == 'ArrowLeft' || e.key == 'ArrowRight' || e.key == 'Delete' || e.key == 'Backspace' ) ) e.preventDefault() }) Right-click + paste still work, we can track the input event to prevent it. Mobile for virtual keyboards e.keyCode = 229 and e.key = "Unidentified" some of these keyboards might still use the right values keyboard logic might not always work on mobile devices logic for tracking mobile keyboards may be following... function runOnKeys(func, ...codes) { let pressed = new Set() document.addEventListener('keydown', function(e) { pressed.add(e.code) for (let code of codes) { if (!pressed.has(code)) return } pressed.clear() func() }) document.addEventListener('keyup', e => pressed.delete(e.code)) } runOnKeys( () => alert("Hello!"), "KeyQ", "KeyW" ) scroll scroll event works both on the window and on scrollable elements Show the current scroll window.addEventListener('scroll', () => console.log(window.pageYOffset + 'px')) Prevent scrolling Scroll triggers after the scroll has already happened That's why e.preventDefault() in onscroll listener does not work Use CSS, overflow property focus, blur focus event is called on focusing blur – when an element loses the focus element receives the focus when clicked or "Tabbed" from keyboard autofocus - HTML attribute that puts the focus by default when a page load doesn’t bubble up & triggers on input only but they propagate on capturing phase, trange historical thing form.addEventListener("focus", () => alert('focused')) // do not work form.addEventListener("blur", () => alert('blured')) // do not work form.addEventListener("focus", () => alert('focused'), true) // works form.addEventListener("blur", () => alert('blured'), true) // works form.addEventListener("focusin", () => alert('focused')) // works form.addEventListener("focusout", () => alert('blured')) // works Mail validation blur handler checks if the field has an email entered, and if not – shows an error focus handler hides the error message (on blur it will be checked again) can’t “prevent losing focu by e.preventDefault() in on blur because on blur works after the element lost the focus Your email please: <input type="email" id="input"> <div id="error"></div> input.onblur = function() { if (!input.value.includes('@')) { input.classList.add('invalid') error.innerHTML = 'Please enter a correct email.' } } input.onfocus = function() { if (this.classList.contains('invalid')) { this.classList.remove('invalid') error.innerHTML = "" } } focus(), blur() elem.focus() set the focus on the element elem.blur() unset the focus on the element alert() causes focus loose when alert is dismissed, the focus comes back (focus event) If an element is removed from DOM, then it also causes the focus loss If it is reinserted later, then the focus doesn’t return Do not leave input if mail in invalid... input.onblur = function() { if (!this.value.includes('@')) { alert("mail is incorrect") input.focus() // ...and put the focus back return } this.classList.remove("error") } tabindex focus is supported by <button>, <input>, <select>, <a> By default, many elements do not support focusing This can be changed using HTML-attribute tabindex The switch order is: elements with tabindex from 1 and above go first then elements w/o tabindex (e.g. a regular <input> ) Elements w/o tabindex are switched in the document source order tabindex="0" - special value´, puts an element among those w/o tabindex Usually it’s used to make an element focusable, but keep the default switching order tabindex="-1" allows only programmatic focusing on an element Tab ignores such elements, but method elem.focus() works elem.tabIndex works too We can add tabindex from JavaScript by using the elem.tabIndex property focusin, focusout Exactly the same as focus/blur, but they bubble form.addEventListener("focusin", () => alert('focused')) // works form.addEventListener("focusout", () => alert('blured')) // works change change event triggers when the element has finished changing for text inputs that means that the event occurs when it loses focus for <select> , <input type="checkbox"> , <input type="radio"> it triggers right after the selection changes input.addEventListener('change', function () { console.log(this.value) // comes only on focus loose }) <select onchange="alert(this.value)"> <option value="">Select something</option> <option value="1">Option 1</option> <option value="2">Option 2</option> <option value="3">Option 3</option> </select> input input event triggers every time after a value is modified by user triggers on any value change, pasting with a mouse or using speech recognition input event doesn’t trigger on not value change, e.g. arrow keys ⇦ ⇨ e.preventDefault() not working, coz event occurs after the value is modified cut, copy, paste events occur on cutting/copying/pasting a value belong to the ClipboardEvent class and provide access to the data that is copied/pasted it’s possible to copy/paste not just text, but everything (file) The clipboard is a “global” OS-level thing for safety reason clipboard usually accessed only on user action (e.g. onclick ) https://developer.mozilla.org/en-US/docs/Web/API/Clipboard e.preventDefault() // works to abort copied/paste input.oncut = input.oncopy = input.onpaste = function(e) { alert(e.type + ' - ' + e.clipboardData.getData('text/plain')) return false } Prevent copy, selection el.addEventListener('copy', e => e.preventDefault()) el.addEventListener('mousedown', e => e.preventDefault()) submit submit event triggers when the form is submitted e.preventDefault() prevents automatic submission and redirection <form onsubmit="alert('submit!'); return false"> Submit can be done by: First: Enter in the input field <input type="text" value="text"><br> Second: Click "submit": <input type="submit" value="Submit"> </form> When a form is sent using Enter on an input field a click event triggers on the <input type="submit"> <form onsubmit="return false"> <input type="text" size="30" value="Focus here and press enter"> <input type="submit" value="Submit" onclick="alert('click')"> </form> form.submit() { const form = document.createElement('form') form.action = 'https://google.com/search' form.method = 'GET' form.innerHTML = '<input name="q" value="test">' document.body.append(form) // the form must be in the document to submit it form.submit() // the submit event is not generated }