dom events

2022.06.18

dom events in JavaScript

#JavaScript#basics
use client /components/post/reExport dom events 2022.06.18 JavaScript ], imgUrl: , desc: , body: ( <> <H>Mouse events order</H> <p>{ }</p> <H>e.button</H> <ul> <li><code>e.button</code> get the exact clicked mouse button</li> <li><code>e.button = 0</code> Left button (primary)</li> <li><code>e.button = 1</code> Middle button (auxiliary)</li> <li><code>e.button = 2</code> Right button (secondary)</li> <li><code>e.button = 3</code> X1 button (back)</li> <li><code>e.button = 4</code> X2 button (forward)</li> <li><code>e.which</code> same, but old non-standard way</li> </ul> <Code block jsx>{ , (e) => console.log(e.button)) // 0 document.addEventListener( , (e) => console.log(e.button)) // 2 document.addEventListener( , function (e) { if (e.altKey && e.shiftKey) alert( ) }) t trigger on every pixel, that’s good for performance</li> <li>but some DOM-elements may be skipped</li> <li>it’s possible that the pointer jumps right inside the middle of the page from out of the window</li> <li>In that case <code>relatedTarget</code> is null, because it came from “nowhere”</li> <li><i>mouseover</i> event bubbles up</li> <li>if parent has <i>mouseover</i> handler, it may seem that the mouse pointer left #parent element, and then immediately came back over it</li> <li>to avoid such mess we need to examine <code>event.target</code> and <code>relatedTarget</code> and if the mouse is still inside the element, then ignore such event</li> <li>or better to use <i>mouseenter</i> & <i>mouseleave</i> events</li> </ul> <Code block jsx>{ \${e.type}: target=\${e.target.id}, relatedTarget=\${e.relatedTarget.id}\ }</Code> <H>mouseenter, mouseleave</H> <ul> <li>events are similar to <i>mouseover</i> & <i>mouseout</i></li> <li>triggers when the mouse pointer enters/leaves an element</li> <li>no bubbling</li> <li>transitions inside the element, to/from descendants, are not counted</li> <li>event delegation not possible</li> </ul> <Code block jsx>{ \${e.type}: target=\${e.target.id}, relatedTarget=\${e.relatedTarget.id}\ }</Code> <H>Event delegation</H> <p>Highlight cells on click...</p> <Code block jsx>{ ) // 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 } tbl.onmouseover = e => e.target.style.background = tbl.onmouseout = e => e.target.style.background = }</Code> <p>Highlight only TDs...</p> <Code block jsx>{ t leave the previous <td> let target = e.target.closest( ) 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 = } tbl.onmouseout = function(e) { if (!curEl) return // if we re leaving the element – where to? Maybe to a descendant? while (relatedTarget) { // go up the parent chain and check – if we // we left the <td>. really curEl = null } in event names and expect our code to continue working for mouse</li> </ul> <Hs>Pointer events types</Hs> <ul> <li><i>touchstart</i>, <i>touchend</i>, <i>touchmove</i></li> <li><i>pointerdown</i> similar to <i>mousedown</i></li> <li><i>pointerup</i> similar to <i>mouseup</i></li> <li><i>pointermove</i> similar to <i>mousemove</i></li> <li><i>pointerover</i> similar to <i>mouseover</i></li> <li><i>pointerout</i> similar to <i>mouseout</i></li> <li><i>pointerenter</i> similar to <i>mouseenter</i></li> <li><i>pointerleave</i> similar to <i>mouseleave</i></li> <li><i>pointercancel</i></li> <li><i>gotpointercapture</i> fires when an element uses el.setPointerCapture(pointerId) to enable capturing</li> <li><i>lostpointercapture</i> fires when the capture is released</li> </ul> <Hs>Pointer events properties</Hs> <p>Pointer events have the same properties as mouse events, plus...</p> <ul> <li><code>pointerId</code> the unique identifier of the pointer causing the event</li> <li><code>pointerType</code> pointing device type, “mouse”, “pen” or “touch”, we can use this property to react differently on various pointer types</li> <li><code>isPrimary = true</code> for the primary pointer (the first finger in multi-touch)</li> <li><code>width</code> 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</li> <li><code>height</code> the height of the area where the pointer touches the device. Where unsupported, it’s always 1</li> <li><code>pressure</code> 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</li> <li><code>tangentialPressure</code> the normalized tangential pressure</li> <li><code>tiltX</code>, <code>tiltY</code>, <code>twist</code> pen-specific properties that describe how the pen is positioned relative the surface.</li> </ul> <H>Multi-touch</H> <ul> <li>handling multi-touch can be done with the help of the <code>pointerId</code> and <code>isPrimary</code> properties</li> <li>if we use 5 fingers simultaneously, we have 5 pointerdown events</li> <li>with their respective coordinates and a different <code>pointerId</code></li> <li>when the user moves and then removes a finger, we get <i>pointermove</i> and <i>pointerup</i> events with the same <code>pointerId</code> as we had in <code>pointerdown</code></li> <li><code>isPrimary = true</code> events associated with the first finger and <code>isPrimary = false</code> for others</li> <li><code>pointerId</code> assigned for each touching finger</li> </ul> <Code block jsx>{ \${e.type} isPrimary=\${e.isPrimary} pointerId=\${e.pointerId}\ }</Code> <Hs>pointercancel</Hs> <ul> <li>event fires when ongoing pointer interaction is aborted</li> <li>...or pointer device hardware was physically disabled</li> <li>...or device orientation changed</li> <li>...or browser decided to handle the interaction on its own</li> <li>Prevent the default browser action to avoid <i>pointercancel</i></li> <li>after this browser won’t hijack the process and doesn’t emit pointercancel</li> </ul> <p>Set CSS to <Code css>{ }</Code></p> <Code block jsx>{ }</Code> <Hs>setPointerCapture</Hs> <ul> <li><Code js>el.setPointerCapture(pointerId)</Code> binds events with the given pointerId to el</li> <li>all pointer events with the same pointerId will have elem as the target</li> <li>no matter where in document they really happened</li> <li>in other words, re-targets all subsequent events with the given pointerId to el</li> <li>The binding is removed when <i>pointerup</i> or <i>pointercancel</i> events occur</li> <li>or elem is removed from the document</li> <li>or <Code>el.releasePointerCapture(pointerId)</Code> called</li> <li>Pointer capturing can be used to simplify drag’n’drop kind of interactions</li> <li>we use <i>pointerdown</i> and <i>pointermove</i> on thumb, but there is a problem</li> <li>…As the pointer moves, it may leave the slider thumb: go above or below it</li> <li>then we need to assign <i>pointermove</i> on the whole document</li> <li>if we do it on document level, there might be some side effects, trigger other event handlers</li> <li>and here the magic comes...</li> <li>We can call <Code js>thumb.setPointerCapture(event.pointerId)</Code> in <i>pointerdown</i> handler</li> <li>Then future pointer events will be re-targeted to thumb</li> <li>When <i>pointerup</i> happens (dragging complete), the binding is removed automatically, we don’t need to care about it</li> <li>if a user moves the pointer around the whole document, events handlers will be called on thumb</li> </ul> <Code block html>{ > <div id= ></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 + } thumb.ondragstart = () => false }</code> field </li> <li>keyboard events are not enough coz there are also speech recognition, copy/paste with mouse etc...</li> </ul> <Hs>Event types</Hs> <ul> <li><code>e.key</code> get the character, language layout specific</li> <li><code>e.code</code> get the “physical key code”, for hotkey</li> <li><code>e.keypress</code>, <code>e.keyCode</code>, <code>e.charCode</code>, <code>e.which</code> - legacy, many browser incompatibilities</li> <li><code>e.repeat</code> - <code>true</code> if an event is triggered by auto-repeat</li> </ul> <Code block jsx>{ , (e) => { console.log( , e.key) console.log( , e.code) console.log( , e.repeat) }) // hold shift+D // e.key D // e.code KeyD // e.repeat true } expects a tel number, does not accept keys except <kbd>digits</kbd>, <kbd>+</kbd>, <kbd>-</kbd>, <kbd>(</kbd>, <kbd>)</kbd></p> <Code block html>{ onkeydown= placeholder= }</Code> <Code block jsx>{ && key <= ) || key == || key == || key == || key == key == || key == || key == || key == ; } inp.addEventListener( , e => { if ( !( (e.key >= && e.key <= ) || e.key == || e.key == || e.key == || e.key == || e.key == || e.key == || e.key == || e.key == ) ) e.preventDefault() }) Unidentified function runOnKeys(func, ...codes) { let pressed = new Set() document.addEventListener( , function(e) { pressed.add(e.code) for (let code of codes) { if (!pressed.has(code)) return } pressed.clear() func() }) document.addEventListener( , e => pressed.delete(e.code)) } runOnKeys( () => alert( ), , ) window.addEventListener( , () => console.log(window.pageYOffset + }</Code> <Hs>Prevent scrolling</Hs> <ul> <li>Scroll triggers after the scroll has already happened</li> <li>That from keyboard</li> <li><i>autofocus</i> - HTML attribute that puts the focus by default when a page load</li> <li>doesn’t bubble up & triggers on input only</li> <li>but they propagate on capturing phase, trange historical thing</li> </ul> <Code block jsx>{ , () => alert( )) // do not work form.addEventListener( , () => alert( )) // do not work form.addEventListener( , () => alert( ), true) // works form.addEventListener( , () => alert( ), true) // works form.addEventListener( , () => alert( )) // works form.addEventListener( , () => alert( )) // works Your email please: <input type= > <div id= ></div> input.onblur = function() { if (!input.value.includes( )) { input.classList.add( ) error.innerHTML = } } input.onfocus = function() { if (this.classList.contains( )) { this.classList.remove( ) error.innerHTML = } } input.onblur = function() { if (!this.value.includes( )) { alert( ) input.focus() // ...and put the focus back return } this.classList.remove( ) } focus is supported by <button>, <input>, <select>, <a> })</li> <li>Elements w/o <i>tabindex</i> are switched in the document source order</li> <li><code>tabindex= </code> - special value´, puts an element among those w/o <i>tabindex</i></li> <li>Usually it’s used to make an element focusable, but keep the default switching order</li> <li><code>tabindex= </code> allows only programmatic focusing on an element</li> <li><kbd>Tab</kbd> ignores such elements, but method <code>elem.focus()</code> works</li> <li><code>elem.tabIndex</code> works too</li> <li>We can add <i>tabindex</i> from JavaScript by using the <code>elem.tabIndex</code> property</li> </ul> <H>focusin, focusout</H> <p>Exactly the same as focus/blur, but they bubble</p> <Code block jsx>{ , () => alert( )) // works form.addEventListener( , () => alert( )) // works <input type= <input type= } it triggers right after the selection changes</li> </ul> <Code block jsx>{ , function () { console.log(this.value) // comes only on focus loose }) <select onchange= > <option value= >Select something</option> <option value= >Option 1</option> <option value= >Option 2</option> <option value= >Option 3</option> </select> 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( )) return false } el.addEventListener( , e => e.preventDefault()) el.addEventListener( , e => e.preventDefault()) <form onsubmit= ); return false ><br> Second: Click : <input type= > </form> <input type= }</li> </ul> <Code block html>{ return false Focus here and press enter > </form> form.submit() { const form = document.createElement( ) form.action = form.method = form.innerHTML = document.body.append(form) // the form must be in the document to submit it form.submit() // the submit event is not generated }