从手机到桌面设备的屏幕,使用触摸屏的设备越来越多。您的应用应以直观且美观的方式响应用户触摸动作。
触摸屏适用于越来越多的设备,从手机到 桌面设备屏幕当用户选择与您的界面进行交互时,您的应用 应以直观的方式响应用户触摸动作。
响应元素状态
您是否曾经有过这样的经历:触摸或点击网页上的某个元素, 该网站是否真的检测到它?
只需在用户触摸零部件或与其互动时改变元素的颜色 基本上保证您的网站正常运行。不仅 这样既能减轻用户的失望感,又能使图片呈现简洁明快、响应迅速的感觉
DOM 元素可继承以下任意状态:default、focus、hover
有效。若要针对以上每种状态更改界面,我们需要应用样式
添加到以下伪类 :hover
、:focus
和 :active
,如下所示:
.btn {
background-color: #4285f4;
}
.btn:hover {
background-color: #296cdb;
}
.btn:focus {
background-color: #0f52c1;
/* The outline parameter suppresses the border
color / outline when focused */
outline: 0;
}
.btn:active {
background-color: #0039a8;
}
在大多数移动浏览器中,悬停和/或焦点状态会应用于元素 并在其被点按后显示在屏幕上
仔细考虑您设置的样式,以及用户在使用更改后的样式 完成相应操作
禁止使用默认浏览器样式
为不同状态添加样式后,您会注意到
实现自己的样式来响应用户触摸。这在很大程度上
因为当移动设备首次发布时,许多网站
具有 :active
状态的样式。因此,许多浏览器
提供额外的突出显示颜色或样式,以向用户提供反馈。
大多数浏览器使用 outline
CSS 属性在
元素。您可以使用以下命令禁止该消息:
.btn:focus {
outline: 0;
/* Add replacement focus styling here (i.e. border) */
}
Safari 和 Chrome 增加了点按突出显示颜色,使用
-webkit-tap-highlight-color
CSS 属性:
/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
-webkit-tap-highlight-color: transparent;
}
Windows Phone 上的 Internet Explorer 也有类似行为,但会被禁用 :
<meta name="msapplication-tap-highlight" content="no">
Firefox 有两个副作用需要处理。
-moz-focus-inner
伪类,会在
可触摸元素,可通过设置 border: 0
来移除。
如果您在 Firefox 上使用 <button>
元素,则会获得渐变效果
已应用,您可以通过设置 background-image: none
将其移除。
/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
background-image: none;
}
.btn::-moz-focus-inner {
border: 0;
}
停用用户选择功能
在创建界面时,您可能希望用户 但您希望禁止默认行为 就是通过长按或拖动鼠标在界面上选择文本
您可以使用user-select
CSS 属性来实现此目的,但请注意,
对内容做这类事情可能极其令人抓狂
如果用户想选择元素中的文字,则可以为其指定此属性。
因此,请务必谨慎使用。
/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
user-select: none;
}
实现自定义手势
如果您想到了一个网站自定义互动和手势建议, 需要注意两个主题:
- 如何支持所有浏览器。
- 如何保持较高的帧速率。
在本文中,我们将具体探讨这些主题,涵盖 然后介绍我们如何使用这些事件 。
根据您希望手势执行的操作,您可能需要使用 用户一次只与一个元素进行互动或者您希望他们能够 可以同时与多个元素互动
在本文中,我们将通过两个示例来介绍 以及如何保持较高的帧速率。
第一个示例允许用户与某个元素互动。在本课中, 那么您可能希望将所有触摸事件都提供给这一个元素, 手势最初是在元素本身上开始的。例如,将一个 手指离开可滑动元素时,仍然可以控制该元素。
这种做法非常有用,因为它能为用户提供极大的灵活性, 会对用户与界面的交互方式施加限制。
不过,如果您希望用户能同时与多个元素互动 时间(使用多点触控),则应将触控范围限制在特定的 元素。
这对用户来说更灵活,但会使操作的逻辑复杂化 并且对用户错误的适应能力较差。
添加事件监听器
在 Chrome(版本 55 及更高版本)、Internet Explorer 和Edge、
PointerEvents
是实现自定义手势的推荐方法。
在其他浏览器中,TouchEvents
和 MouseEvents
是正确的方法。
PointerEvents
的一大特色是,它可以合并多种类型的输入,
包括鼠标、触摸和笔事件,
回调。要监听的事件包括 pointerdown
、pointermove
、
pointerup
和pointercancel
。
在其他浏览器中,等效项是 touchstart
、touchmove
、
touchend
和 touchcancel
,用于触摸事件;如果您想要实现
与实现 mousedown
所需的鼠标输入相同的手势,
mousemove
和mouseup
。
如果您对要使用哪些事件有疑问,请查看此表格, 触摸、鼠标和指针事件。
使用这些事件需要对 DOM 调用 addEventListener()
方法
元素,以及事件名称、回调函数和布尔值。
这个布尔值决定了您要在事件之前还是之后捕获事件
其他元素则有机会捕捉并解读
事件。(true
表示您希望该事件排在其他元素之前。)
下面是一个监听互动开始的示例。
// Check if pointer events are supported.
if (window.PointerEvent) {
// Add Pointer Event Listener
swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
// Add Touch Listener
swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);
// Add Mouse Listener
swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}
处理单元素交互
在上面的简短代码段中,我们只添加了起始事件监听器 鼠标事件。这样做的原因是,鼠标事件只会 。
无论手势在哪里,TouchEvents
都会在手势启动后跟踪手势
发生触摸事件,并且无论触摸事件发生在何处,PointerEvents
都会跟踪事件
发生在对 DOM 元素调用 setPointerCapture
之后。
对于鼠标移动和结束事件,我们在 手势启动方法,并将监听器添加到文档中,这意味着 直至手势完成。
实现此功能的步骤如下:
- 添加所有 TouchEvent 和 PointerEvent 监听器。对于 MouseEvents,添加只 启动事件
- 在开始手势回调内,将鼠标移动和结束事件绑定到
文档。这样便可收到所有鼠标事件
事件是否发生在原始元素上。对于 PointerEvents,
需要对原始元素调用
setPointerCapture()
才能接收 所有后续事件然后处理手势的开头。 - 处理移动事件。
- 在结束事件时,从文档中移除鼠标移动和结束监听器 然后结束手势
以下代码段中的 handleGestureStart()
方法用于添加移动操作
和结束事件:
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if(evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
我们添加的结束回调是 handleGestureEnd()
,它会移除移动操作
和结束事件监听器,并释放指针捕获
,如下所示:
// Handle end gestures
this.handleGestureEnd = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 0) {
return;
}
rafPending = false;
// Remove Event Listeners
if (window.PointerEvent) {
evt.target.releasePointerCapture(evt.pointerId);
} else {
// Remove Mouse Listeners
document.removeEventListener('mousemove', this.handleGestureMove, true);
document.removeEventListener('mouseup', this.handleGestureEnd, true);
}
updateSwipeRestPosition();
initialTouchPos = null;
}.bind(this);
按照上述将移动事件添加到文档的方式,如果 用户开始与某个元素互动,并将手势移至 那么无论鼠标指针位于何处,我们都会继续获知 ,因为事件是从文档接收的。
此图显示了我们添加 移动和结束事件到文档。
高效响应轻触操作
现在我们已经完成了开始和结束事件 响应触摸事件。
对于任何开始和移动事件,您都可以轻松提取 x
和 y
事件。
以下示例会按以下方法检查事件是否来自 TouchEvent
:
检查 targetTouches
是否存在。如果包含,则会提取
clientX
和clientY
。
如果事件是 PointerEvent
或 MouseEvent
,则提取 clientX
,
clientY
。
function getGesturePointFromEvent(evt) {
var point = {};
if (evt.targetTouches) {
// Prefer Touch Events
point.x = evt.targetTouches[0].clientX;
point.y = evt.targetTouches[0].clientY;
} else {
// Either Mouse event or Pointer Event
point.x = evt.clientX;
point.y = evt.clientY;
}
return point;
}
TouchEvent
有三个包含触摸数据的列表:
touches
:屏幕上所有当前轻触操作的列表,无论 其所在的 DOM 元素。targetTouches
:事件中当前 DOM 元素上的触摸列表 目标changedTouches
:因发生变化而导致事件的触摸列表 触发。
在大多数情况下,targetTouches
可以满足您的所有需求。(
请参阅触摸列表)。
使用 requestAnimationFrame
由于事件回调是在主线程上触发的,因此我们希望以 尽可能减少事件回调中的代码, 从而防止出现卡顿
使用 requestAnimationFrame()
,我们只需更新界面,
就可以帮助我们将一些
无需处理事件回调
如果您不熟悉 requestAnimationFrame()
,可以
可以点击此处了解详情。
典型的实现是保存 x
和 y
坐标
启动和移动事件,并请求移动事件内的动画帧
回调。
在演示中,我们将初始触摸位置存储在 handleGestureStart()
中(查找 initialTouchPos
):
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
handleGestureMove()
方法会存储其事件的位置
在请求动画帧之前,传递我们的
onAnimFrame()
函数作为回调:
this.handleGestureMove = function (evt) {
evt.preventDefault();
if (!initialTouchPos) {
return;
}
lastTouchPos = getGesturePointFromEvent(evt);
if (rafPending) {
return;
}
rafPending = true;
window.requestAnimFrame(onAnimFrame);
}.bind(this);
onAnimFrame
值是一个函数,被调用时会更改界面。
来移动它将此函数传递到 requestAnimationFrame()
后,
告知浏览器在即将更新网页之前进行调用
(即绘制对页面所做的任何更改)。
在 handleGestureMove()
回调中,我们最初检查 rafPending
是否为 false,
指示 requestAnimationFrame()
是否已调用 onAnimFrame()
自上次移动事件以来。这意味着我们只有一个 requestAnimationFrame()
在任一时间等待运行
执行 onAnimFrame()
回调时,我们会在任何
在将 rafPending
更新为 false
之前要移动的元素,以允许
请求新动画帧的下一个触摸事件。
function onAnimFrame() {
if (!rafPending) {
return;
}
var differenceInX = initialTouchPos.x - lastTouchPos.x;
var newXTransform = (currentXPosition - differenceInX)+'px';
var transformStyle = 'translateX('+newXTransform+')';
swipeFrontElement.style.webkitTransform = transformStyle;
swipeFrontElement.style.MozTransform = transformStyle;
swipeFrontElement.style.msTransform = transformStyle;
swipeFrontElement.style.transform = transformStyle;
rafPending = false;
}
使用触摸操作控制手势
通过 CSS 属性 touch-action
,您可以控制默认轻触操作
元素的行为方式。在我们的示例中,我们使用 touch-action: none
来
阻止浏览器对用户的这使得我们能够
来拦截所有触摸事件。
/* Pass all touches to javascript: */
button.custom-touch-logic {
touch-action: none;
}
使用 touch-action: none
在一定程度上是一种核选项,因为它会阻止所有
默认浏览器行为在许多情况下
是一种更好的解决方案。
touch-action
可让您停用浏览器实现的手势。
例如,IE10 及更高版本支持点按两次进行缩放手势。通过将
您阻止了默认的点按两次,共 manipulation
项,其中 touch-action
项
行为
这样,您就可以自行实现点按两次手势。
下面列出了常用的 touch-action
值:
支持旧版 IE
如果您想支持 IE10,则需要处理带供应商前缀的
PointerEvents
。
要确认是否支持 PointerEvents
,您通常需要查找
window.PointerEvent
,但在 IE10 中,您应查找
window.navigator.msPointerEnabled
。
带供应商前缀的事件名称为:'MSPointerDown'
、'MSPointerUp'
和
'MSPointerMove'
。
以下示例展示了如何检查支持情况并切换 事件名称。
var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';
if (window.navigator.msPointerEnabled) {
pointerDownName = 'MSPointerDown';
pointerUpName = 'MSPointerUp';
pointerMoveName = 'MSPointerMove';
}
// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
window.PointerEventsSupport = true;
}
有关详情,请参阅这篇于 Microsoft。
参考
用于触摸状态的伪类
权威触摸事件参考文档可在以下位置找到: W3C Touch Events。
触摸、鼠标和指针事件
这些事件是将新手势添加到 应用:
触摸列表
每个触摸事件都包括三个列表属性:
在 iOS 上启用活跃状态支持
遗憾的是,默认情况下,iOS 版 Safari 不会将 active 状态应用于
要使其正常运行,您需要将 touchstart
事件监听器添加到文档
正文或每个元素。
此操作应在用户代理测试之后进行,以便它只能在 iOS 设备上运行。
向正文添加触摸起始点的优势在于,该点触点可应用于所有元素 ,但在滚动页面时可能会出现性能问题。
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
document.body.addEventListener('touchstart', function() {}, false);
}
};
另一种方法是向所有可交互操作添加触摸启动监听器 元素,从而缓解一些性能问题。
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
var elements = document.querySelectorAll('button');
var emptyFunction = function() {};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('touchstart', emptyFunction, false);
}
}
};