鼠标事件之外,认识一下指针事件家族?
鼠标事件之外,认识一下指针事件家族?
一、前言
相信大多数开发者,都知道或用过鼠标事件,例如 mousedown 和 mouseup,而指针事件家族,例如 pointerdown 和 pointerup,用到的人则相对比较少了。
我是因为遇到:需要把鼠标事件重新定向到特定元素,从而实现捕获鼠标绑定到特定元素的需求,用到了 setCapture(),而 setCapture 已经被弃用且是非标准的,然后选用了 setPointerCapture。不得了,一下子捅了 PointerEvent 指针事件家族的马蜂窝。
以实现“拖动边界线改变布局”为例,用了 setPointerCapture 就得用 releasePointerCapture,而在实现需求过程中,还会接着使用 pointerdown、pointermove以及pointerup。
本文,将以实现“滑块”和“拖动边界线改变布局”的需求为例,以 setPointerCapture 的使用为引,介绍:
1、MouseEvent 和 PointerEvent 家族的介绍
2、简述 setCapture 和 setPointerCapture
3、PointerEvent 的应用场景
二、MouseEvent 和 PointerEvent 家族的介绍
2.1 MouseEvent 家族
MouseEvent 接口指用户与指针设备(如鼠标)交互时发生的事件。使用此接口的常见事件包括:click、dblclick、mouseup、mousedown。
MouseEvent 派生自 UIEvent,UIEvent 派生自 Event。虽然 MouseEvent.initMouseEvent() 方法保持向后兼容性,但是应该使用 MouseEvent() 构造函数创建一个 MouseEvent 对象。
一些具体的事件都派生自 MouseEvent:WheelEvent 和DragEvent。
它的一些相关事件:
- setCapture 用于把全部的鼠标事件重新定向到指定元素。
- releaseCapture 用来将鼠标从先前通过 setCapture() 绑定的元素身上释放出来。
- mousedown 事件在定点设备(如鼠标或触摸板)按钮在元素内按下时,会在该元素上触发。
- mouseenter 事件在定点设备(通常指鼠标)首次移动到元素的激活区域内时,在该元素上触发。
- mouseleave 事件在定点设备(通常是鼠标)的指针移出某个元素时被触发。
- mousemove 事件在定点设备(通常指鼠标)的光标在元素内移动时,会在该元素上触发。
- mouseout 事件在定点设备(通常是鼠标)移动至元素或其子元素之外时,会在该元素上触发。
- mouseover 当一个定点设备(通常指鼠标)在一个元素本身或者其子元素上移动时,mouseover 事件在该元素上触发。
- mouseup 事件在定点设备(如鼠标或触摸板)按钮在元素内释放时,在该元素上触发。
2.2 PointerEvent 家族
PointerEvent 接口代表了由 指针 引发的 DOM 事件的状态,包括接触点的位置,引发事件的设备类型,接触表面受到的压力等。
指针 是输入设备的硬件层抽象(比如鼠标,触摸笔,或触摸屏上的一个触摸点)。指针 能指向一个具体表面(如屏幕)上的一个(或一组)坐标。
指针的 击中检测 指浏览器用来检测 指针事件的目标元素的过程。大多数情况下,这个目标元素是由 指针的位置和元素在文章中的位置和分层共同决定的。
该接口属性继承自 MouseEvent 和 Event,所以它们有很多相似的属性和方法,只是 PointerEvent 的作用对象更具体,具体到鼠标指针。
它的一些相关事件:
- setPointerCapture 用于将特定元素指定为未来指针事件的捕获目标。
- releasePointerCapture 用来将鼠标指针从先前通过 setPointerCapture() 绑定的元素身上释放出来,还给鼠标指针自由。
- pointerdown 鼠标指针按下时触发该事件
- pointerenter 鼠标指针首次进入到元素的激活区域内时触发该事件
- pointerleave 鼠标指针离开时触发该事件
- pointermove 鼠标指针移动时触发该事件
- pointerout 鼠标指针移动到元素之外时触发该事件
- pointerover 鼠标指针悬浮覆盖时触发该事件
- pointerup 鼠标指针抬起时触发该事件
三、简述 setCapture 和 setPointerCapture
3.1 特征比较
setCapture | setPointerCapture | |
---|---|---|
是否标准规范中的特性 | 否 | 是 |
是否已废弃 | 是 | 否 |
浏览器兼容性 | 好 | 差 |
配套对应事件 | releaseCapture | releasePointerCapture |
3.2 setCapture 和 setPointerCapture
在处理一个 mousedown 事件过程中调用 setCapture() 方法来把全部的鼠标事件重新定向到这个元素,直到鼠标按钮被释放或者 document.releaseCapture() 被调用。
// 语法
// retargetToElement 如果被设置为 true, 所有事件被直接定向到这个元素; 如果是 false, 事件也可以在这个元素的子元素上触发。
element.setCapture(retargetToElement);
setPointerCapture() 方法用于将特定元素指定为未来指针事件的捕获目标。指针的后续事件将以捕获元素为目标,直到捕获被释放(通过Element.releasePointerCapture())。
// 语法
// pointerId 是指 PointerEvent 对象的 pointerId
element.setPointerCapture(pointerId);
注意: 一旦设置了 pointer capture,pointerover、pointerout、pointerenter 和 pointerleave 事件将不会被触发,直到越过设置了 capture 的元素的边界。 这是因为其他元素将不能再作为 pointer 事件的目标了。这会影响到这些事件在其他元素上的触发。
四、PointerEvent 的应用场景
4.1 PointerEvent 应用场景简述
把全部的鼠标事件重新定向到特定元素,常见有 setCapture 和 setPointerCapture,但是 setCapture 是非标准的,那意味着在一些浏览器可能会不被支持, 而且它又被废弃了,所以 setPointerCapture 通常成为了首选。而常见与之配套使用的 PointerEvent 中的事件有 onpointerdown、onpointermove、onpointerup 以及 releasePointerCapture。
setPointerCapture 的应用场景,或者说把全部的鼠标事件重新定向到特定元素的需求场景,挺常见的,只是通常被封装在了组件里面。例如: 1、滑块组件,拖动滑盖,可视化显示拖动结果,例如一个数值或百分比。
2、滑块校验组件,拖动滑块复位,常用于人机验证场景。
3、拖动边界线改变布局,左右拖动改变左右的元素的宽,这种需求非常常见,也非常常用,而且在各种终端都是。
4、其他:其实和鼠标以及鼠标指针有关的动作效果、动画效果、操作行为,PointerEvent 都有很广的应用场景,尤其在游戏领域中。
4.2 PointerEvent 案例:实现滑块
下面是一个 PointerEvent 实现滑块的示例 Demo,Demo 中,使用了 PointerEvent 中的:
①onpointerdown
②onpointermove
③onpointerup
④setPointerCapture
⑤releasePointerCapture
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>滑块拖动 Demo</title>
<style>
.slider-box {
background: #1e87f0;
}
#slider {
width: 140px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background: red;
cursor: pointer;
user-select:none;
border-radius: 20px;
}
</style>
</head>
<body>
<h1> 滑块拖动 setPointerCapture 示例 Demo</h1>
<h1> 在小小的滑槽里,滑呀滑呀滑</h1>
<div class="slider-box">
<div id="slider">滑呀滑</div>
</div>
<script>
function beginSliding(e) {
slider.onpointermove = slide;
slider.setPointerCapture(e.pointerId);
}
function stopSliding(e) {
slider.onpointermove = null;
slider.releasePointerCapture(e.pointerId);
}
function slide(e) {
slider.style.transform = `translate(${e.clientX - 70}px)`;
}
const slider = document.getElementById('slider');
slider.onpointerdown = beginSliding;
slider.onpointerup = stopSliding;
</script>
</body>
</html>
Demo 效果图:
4.3 PointerEvent 案例:拖动边界线改变布局
这是一个通过拖动边界线改变宽度,从而改变布局的 Demo,Demo 中,使用了 PointerEvent 中的:
①onpointerdown
②onpointermove
③onpointerup
④setPointerCapture
⑤releasePointerCapture
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>左右拖动div,改变左右元素的宽度</title>
<style>
#box {
width: 100vw;
height: 100vh;
overflow: hidden;
}
#left {
width: calc(20% - 5px);
height: 100%;
background: #87eb8c;
float: left;
}
#resize {
width: 5px;
height: 100%;
cursor: col-resize;
float: left;
}
#right {
float: right;
width: 80%;
height: 100%;
background: #ffc547;
}
</style>
</head>
<body>
<div id="box">
<div id="left">left</div>
<div id="resize"></div>
<div id="right">right</div>
</div>
<script>
window.onload = function () {
let left = document.getElementById("left");
let resize = document.getElementById("resize");
let right = document.getElementById("right");
let box = document.getElementById("box");
resize.onpointerdown = function (e) {
const startX = e.clientX;
resize.left = resize.offsetLeft;
resize.onpointermove = function (em) {
const endX = em.clientX;
let moveL = resize.left + (endX - startX);
let maxT = box.clientWidth - resize.offsetWidth;
const step = 100
if (moveL < step) moveL = step;
if (moveL > maxT - step) moveL = maxT - step;
resize.style.left = moveL;
left.style.width = moveL + "px";
right.style.width = (box.clientWidth - moveL - 5) + "px";
}
resize.onpointerup = function (et) {
resize.onpointermove = null;
resize.onpointerup = null;
resize.releasePointerCapture(et.pointerId);
}
resize.setPointerCapture(e.pointerId);
return false;
}
}
</script>
</body>
</html>
这是该 Demo 的效果图:
转载自:https://juejin.cn/post/7246316652047679549