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
}