检测浏览器没有鼠标且只能触摸

我正在开发一个网络应用程序(不是一个有趣的文本页面的网站) ,它有一个非常不同的触摸界面(点击时你的手指会隐藏屏幕)和鼠标(主要依赖于悬停预览)。 如何检测用户是否没有鼠标给他显示正确的界面?我计划留一个开关给既有鼠标又有触摸功能的人(比如一些笔记本电脑)。

浏览器中的触摸事件功能实际上并不意味着用户正在使用触摸设备(例如,Modern izr 并没有切断它)。如果设备有鼠标,正确回答问题的代码应该返回 false,否则返回 true。对于带有鼠标和触摸的设备,它应该返回 false (不仅仅是触摸)

顺便说一句,我的触摸界面可能也适用于只使用键盘的设备,所以我更希望检测到的是没有鼠标。

为了更清楚地说明这个需求,下面是我希望实现的 API:

// Level 1




// The current answers provide a way to do that.
hasTouch();


// Returns true if a mouse is expected.
// Note: as explained by the OP, this is not !hasTouch()
// I don't think we have this in the answers already, that why I offer a bounty
hasMouse();


// Level 2 (I don't think it's possible, but maybe I'm wrong, so why not asking)


// callback is called when the result of "hasTouch()" changes.
listenHasTouchChanges(callback);


// callback is called when the result of "hasMouse()" changes.
listenHasMouseChanges(callback);
64271 次浏览

通常,检测是否支持 mouseover 函数比检测 OS/浏览器类型更好。你可以通过以下简单的方法做到这一点:

if (element.mouseover) {
//Supports the mouseover event
}

确保你的 不要做到以下几点:

if (element.mouseover()) {
// Supports the mouseover event
}

后者实际上会调用该方法,而不是检查它是否存在。

你可以在这里了解更多: Http://www.quirksmode.org/js/support.html

如何侦听文档上的 mousemove 事件。然后,直到你听到该事件,你假定该设备是触摸或键盘只。

var mouseDetected = false;
function onMouseMove(e) {
unlisten('mousemove', onMouseMove, false);
mouseDetected = true;
// initializeMouseBehavior();
}
listen('mousemove', onMouseMove, false);

(如 listenunlisten在适当情况下委托给 addEventListenerattachEvent。)

希望这不会导致太多的视觉垃圾,如果你需要大规模的重新布局基于模式..。

Tera-WURFL 可以告诉您访问站点 通过比对浏览器特征的设备对其数据库的能力。看看,免费的!

我强烈反对这种方法。考虑触摸屏、桌面大小的设备,你有一系列不同的问题需要解决。

请让您的应用程序可用没有鼠标(即没有悬停预览)。

既然您计划提供一种在界面之间切换的方法,那么简单地要求用户单击一个链接或一个按钮来“输入”正确的应用程序版本是否可行?这样你就能记住他们对未来访问的偏好。它不是高科技的,但它是100% 可靠的: -)

我在这里看到的主要问题是,大多数触摸设备会触发一个鼠标事件以及相应的触摸事件(touch start-> mousedown,touch move-> mousemove 等)。 对于只使用键盘的设备,最后对于现代设备,它们有一个通用的浏览器,因此您甚至无法检测到 MouseEvent 类的存在。

在我看来,这里不太痛苦的解决方案是,在启动时显示一个菜单(只对键盘用户使用‘ alt’管理) ,或者使用 localStorage/cookies/serverside 存储选项,或者在下次访问者来时保持相同的选项。

你为什么不检测它是否有能力感知触摸和/或对鼠标的动作作出反应?

// This will also return false on
// touch-enabled browsers like Chrome
function has_touch() {
return !!('ontouchstart' in window);
}


function has_mouse() {
return !!('onmousemove' in window);
}

主要的问题是你有以下不同类型的设备/用例:

  1. 鼠标和键盘(桌面)
  2. 只接触(手机/平板电脑)
  3. 鼠标,键盘,触摸(触摸笔记本电脑)
  4. 触摸和键盘(平板电脑上的蓝牙键盘)
  5. 只能用鼠标(禁用用户/浏览首选项)
  6. 仅限键盘(禁用用户/浏览首选项)
  7. 触摸和鼠标(即从 Galaxy Note 2笔悬停事件)

更糟糕的是,人们可以从这些类中的一些转换到其他类(插入鼠标,连接到键盘) ,或者用户可以在普通的笔记本电脑上进行 APPEAR 操作,直到他们伸手触摸屏幕。

假设浏览器中存在事件构造函数不是向前推进的好方法(而且有些不一致) ,这种假设是正确的。此外,除非您正在跟踪一个非常特定的事件,或者只是试图排除上面的几个类,否则使用事件本身并不能完全证明这一点。

例如,假设您发现一个用户发出了一个真正的 mousemove (而不是触摸事件中的假的,参见 http://www.html5rocks.com/en/mobile/touchandmouse/)。

然后呢?

你启用了悬停样式? 你添加了更多的按钮?

无论哪种方式,你都增加了时间玻璃,因为你必须等待一个事件发射。

但是,如果您的高贵用户决定拔掉鼠标并进行全面触摸,那么会发生什么情况呢。.你是否等待他触摸你现在拥挤的界面,然后在他努力精确定位你现在拥挤的用户界面之后立即改变界面?

在子弹形式,引用灰泥在 https://github.com/Modernizr/Modernizr/issues/869#issuecomment-15264101

  • 我们想探测到老鼠的存在
  • 在触发事件之前,我们可能无法检测到它
  • 因此,我们检测的是在这个会话中是否使用了鼠标ーー它不会立即从页面加载
  • 我们很可能也无法检测到没有鼠标ーー如果没有鼠标,它就是未定义的(我认为这比设置。) 除非被证实,否则它是错误的)
  • 而且我们可能无法检测到鼠标是否在会话中断连接ーー这与用户仅仅放弃使用鼠标是无法区分的 老鼠

顺便说一句: 浏览器确实知道用户插入鼠标/连接到键盘,但不会将其暴露给 JavaScript。.该死!

这应该会引导你做到以下几点:

跟踪给定用户的当前能力是复杂的、不可靠的,而且其优点值得怀疑

不过,渐进增强的概念在这里非常适用。无论用户的环境如何,都要建立一个流畅的体验。然后根据浏览器特性/媒体查询做出假设,添加在假设上下文中相关的功能。鼠标的存在只是不同设备上的不同用户体验网站的众多方式之一。在内核中创建一些有价值的东西,不要太担心人们如何点击按钮。

不幸的是,据我所知,没有浏览器暴露出鼠标的存在。

话虽如此,我们只需要尽力利用现有的选项... 事件。我知道这不是你想要的... 我同意现在还远远不够理想。

我们可以尽我们最大的努力来判断用户是否在任何给定的时刻使用鼠标或触摸。下面是一个使用 jQuery & Knockout 的简单示例:

//namespace
window.ns = {};


// for starters, we'll briefly assume if touch exists, they are using it - default behavior
ns.usingTouch = ko.observable(Modernizr.touch); //using Modernizr here for brevity.  Substitute any touch detection method you desire


// now, let's sort out the mouse
ns.usingMouse = ko.computed(function () {
//touch
if (ns.usingTouch()) {
//first, kill the base mousemove event
//I really wish browsers would stop trying to handle this within touch events in the first place
window.document.body.addEventListener('mousemove', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
}, true);


//remove mouse class from body
$('body').removeClass("mouse");


//switch off touch listener
$(document).off(".ns-touch");


// switch on a mouse listener
$(document).on('mousemove.ns-mouse', function (e) {
if (Math.abs(window.lastX - e.clientX) > 0 || window.lastY !== e.clientY) {
ns.usingTouch(false);  //this will trigger re-evaluation of ns.usingMouse() and result in ns.usingMouse() === true
}
});


return false;
}
//mouse
else {
//add mouse class to body for styling
$('body').addClass("mouse");


//switch off mouse listener
$(document).off(".ns-mouse");


//switch on a touch listener
$(document).on('touchstart.ns-touch', function () { ns.usingTouch(true) });


return true;
}
});


//tests:
//ns.usingMouse()
//$('body').hasClass('mouse');

您现在可以 绑定/订阅到 usingMouse () & 使用 Touch ()和/或样式与 身体,老鼠类的接口。一旦检测到鼠标光标并在触摸启动时,该界面将来回切换。

希望我们很快就能从浏览器供应商那里得到一些更好的选择。

正如其他人指出的那样,明确地检测他们是否拥有鼠标是不可靠的。根据设备的不同,这很容易改变。对于布尔值 true 或 false,您肯定无法可靠地执行此操作,至少在文档级别上是如此。

触摸事件和鼠标事件是独占的。因此,这可以在一定程度上帮助采取不同的行动。问题在于触摸事件更接近鼠标的上/下/移动事件,而且还会触发一个单击事件。

从你的问题,你说你想有一个悬停预览。除此之外,我不知道你界面的其他细节。我是 假设,因为没有鼠标,你想点击预览,而点击做了不同的动作,因为悬停预览。

如果是这种情况,你可以采取一些懒惰的方法来检测:

Onclick 事件之前总是有一个带鼠标的 onmouseover 事件。所以请注意,鼠标位于被单击的元素的顶部。

您可以通过一个文档范围的 onmousemove 事件来实现这一点。您可以使用 event.target来记录鼠标驻留在哪个元素上。然后,在 onclick 事件内部,可以检查鼠标是否实际位于被单击的元素上方(或者该元素的子元素上方)。

从这里开始,您可以选择依赖于两者的 click 事件,并根据结果执行 A 或 B 操作。如果一些触摸设备不发出点击事件(相反,你将不得不依赖于 ontouch * 事件) ,那么 B 动作可能什么都不是。

我不认为有可能识别只有触摸的设备(当然,据我所知)。主要问题是所有的鼠标和键盘事件都是由触摸设备触发的。 请参阅下面的示例,对于触摸设备,两个警报都返回 true。

function is_touch_present() {
return ('ontouchstart' in window) || ('onmsgesturechange' in window);
}


function is_mouse_present() {
return (('onmousedown' in window) && ('onmouseup' in window) && ('onmousemove' in window) && ('onclick' in window) && ('ondblclick' in window) && ('onmousemove' in window) && ('onmouseover' in window) && ('onmouseout' in window) && ('oncontextmenu' in window));
}


alert("Touch Present: " + is_touch_present());
alert("Mouse Present: " + is_mouse_present());

我认为最好的方法是 mousemove监听器(目前是最好的答案)。我认为这个方法需要稍微调整一下。的确,基于触摸的浏览器甚至模拟 mousemove 事件,正如您在 关于 iOS 的讨论中看到的,所以我们应该小心一点。

基于触摸的浏览器只有在用户点击屏幕(用户的手指向下)时才会模拟这个事件,这是有道理的。这意味着我们应该在 mousemove 处理程序期间添加一个测试,以查看事件期间哪个鼠标按钮被关闭(如果有的话)。如果没有鼠标按钮按下,我们可以安全地假设一个真正的鼠标是存在的。如果鼠标按钮被关闭,测试仍然是不确定的。

那么,这将如何实施呢?这个 有个问题展示了检查在鼠标移动过程中哪个鼠标按钮被关闭的最可靠的方法是在文档级别实际监听3个事件: mousemove、 mousedown 和 mouseup。向上和向下只会设置一个全局布尔标志。此举将执行测试。如果您有一个移动并且布尔值为 false,那么我们可以假设存在一个鼠标。有关确切的代码示例,请参见问题。

最后一条评论。.这个测试并不理想,因为它不能在加载时执行。因此,我将使用前面提到的渐进增强方法。默认情况下,显示不支持鼠标特定悬停界面的版本。如果发现鼠标,请在运行时使用 JS 启用此模式。对于用户来说,这应该看起来尽可能无缝。

为了支持用户配置中的更改(即鼠标已断开连接) ,您可以定期重新测试。虽然我相信在这种情况下,最好是简单地通知用户这两种模式,并让用户手动切换它们(就像移动/桌面的选择总是可以反过来)。

只能检测浏览器是否触摸 有能力。没有办法知道它是否实际上有一个触摸屏或鼠标连接。

如果检测到触摸功能,可以通过监听触摸事件而不是鼠标事件来优先使用。

跨浏览器检测触摸能力:

function hasTouch() {
return (('ontouchstart' in window) ||       // html5 browsers
(navigator.maxTouchPoints > 0) ||   // future IE
(navigator.msMaxTouchPoints > 0));  // current IE10
}

然后我们可以用这个来检查:

if (!hasTouch()) alert('Sorry, need touch!);

或者选择听哪个事件,或者:

var eventName = hasTouch() ? 'touchend' : 'click';
someElement.addEventListener(eventName , handlerFunction, false);

或者使用不同的方法进行触摸和非触摸:

if (hasTouch() === true) {
someElement.addEventListener('touchend' , touchHandler, false);


} else {
someElement.addEventListener('click' , mouseHandler, false);


}
function touchHandler(e) {
/// stop event somehow
e.stopPropagation();
e.preventDefault();
window.event.cancelBubble = true;
// ...
return false; // :-)
}
function mouseHandler(e) {
// sorry, touch only - or - do something useful and non-restrictive for user
}

对于鼠标,人们只能检测鼠标是否被使用,而不能检测它是否存在。可以设置一个全局标志来指示鼠标是通过使用检测到的(类似于现有的答案,但稍微简化了一点) :

var hasMouse = false;


window.onmousemove = function() {
hasMouse = true;
}

(不能包括 mouseupmousedown,因为这些事件也可以通过触摸触发)

浏览器限制访问低级系统 API,这些 API 需要能够检测特性,例如它所使用的系统的硬件功能。

也许可以编写一个插件/扩展来访问这些内容,但是通过 JavaScript 和 DOM 这种检测在这方面是有限的,而且必须编写一个针对不同操作系统平台的插件。

因此,总的来说: 这样的检测只能通过一个“好的猜测”来估计。

在不同的 PC、 Linux、 iPhone、 Android 手机和标签上做了一些测试。奇怪的是没有简单的防弹方法。问题出现时,一些有触摸和没有鼠标仍然呈现触摸和鼠标事件的应用程序。既然要支持鼠标只和触摸只实例,就要同时处理两者,但这会导致用户交互的双重发生。如果可以知道鼠标不在设备上,那么可以知道忽略假的/插入的鼠标事件。如果遇到鼠标移动,尝试设置标志,但是一些浏览器抛出假的鼠标移动以及鼠标向上和向下。尝试检查时间戳,但觉得这太冒险了。底线: 我发现创建假鼠标事件的浏览器总是在插入的 MouseDown 之前插入一个 MouseMove。在我的案例中,99.99% 的情况下,当在一个有真正的鼠标的系统上运行时,会有多个连续的 MouseMove 事件——至少两个。因此,跟踪系统是否遇到两个连续的 MouseMove 事件,并声明如果从未满足此条件,则不存在鼠标。这可能太简单了,但是它可以在我所有的测试设置中工作。在找到更好的解决办法之前,我会一直坚持下去。吉姆 W

@ 怀亚特的回答很棒,给了我们很多思考的机会。

在我的情况下,我选择听第一次互动,只有在那时设置一个行为。因此,即使用户有一个鼠标,我将视为触摸设备,如果第一次交互是一个触摸。

考虑到 给定事件处理的顺序:

  1. 开始
  2. 触摸动作
  3. 说得好
  4. 鼠标悬停
  5. 鼠标移动
  6. 鼠标向下
  7. 小老鼠
  8. 点击

我们可以假设,如果鼠标事件在触摸之前被触发,那么它就是一个真正的鼠标事件,而不是一个模拟事件。示例(使用 jQuery) :

$(document).ready(function() {
var $body = $('body');
var detectMouse = function(e){
if (e.type === 'mousedown') {
alert('Mouse interaction!');
}
else if (e.type === 'touchstart') {
alert('Touch interaction!');
}
// remove event bindings, so it only runs once
$body.off('mousedown touchstart', detectMouse);
}
// attach both events to body
$body.on('mousedown touchstart', detectMouse);
});

这招对我管用

媒体查询第4级在浏览器中可用时,我们将能够使用“指针”和“悬停”查询来用鼠标检测设备。

如果我们真的想把这些信息传递给 Javascript,我们可以使用一个 CSS 查询来根据设备类型设置特定的样式,然后使用 Javascript 中的 getComputedStyle来读取这个样式,并从中获得原始的设备类型。

但是鼠标可以是 有联系拔掉插头在任何时候,并且用户可能想要在触摸和鼠标之间切换。所以我们可能需要 侦查这个变化,并提供更改界面或自动这样做。

JQuery 中检测鼠标使用情况的一个简单解决方案,解决了移动设备也触发“ mousemove”事件的问题。只需添加一个 touch start 侦听器来删除 mousemove 侦听器,这样它就不会在触摸时被触发。

$('body').one('touchstart.test', function(e) {
// Remove the mousemove listener for touch devices
$('body').off('mousemove.test');
}).one('mousemove.test', function(e) {
// MOUSE!
});

当然,该设备仍然可以是触摸和鼠标,但以上将保证一个真正的鼠标被使用。

在类似的情况下,这对我很有效。基本上,假设用户没有鼠标,直到您看到一系列连续的鼠标移动,没有中间的鼠标下拉或鼠标上移。不是很优雅,但很有效。

var mousedown = false;
var mousemovecount = 0;
function onMouseDown(e){
mousemovecount = 0;
mousedown = true;
}
function onMouseUp(e){
mousedown = false;
mousemovecount = 0;
}
function onMouseMove(e) {
if(!mousedown) {
mousemovecount++;
if(mousemovecount > 5){
window.removeEventListener('mousemove', onMouseMove, false);
console.log("mouse moved");
$('body').addClass('has-mouse');
}
} else {
mousemovecount = 0;
}
}
window.addEventListener('mousemove', onMouseMove, false);
window.addEventListener('mousedown', onMouseDown, false);
window.addEventListener('mouseup', onMouseUp, false);

刚刚找到了一个我认为非常优雅的解决方案。

// flag as mouse interaction by default
var isMouse = true;


// detect a touch event start and flag it
$(window).on('touchstart', function () {
// if triggers touchstart, toggle flag
// since touch events come before mouse event / click
// callback of mouse event will get in callback
// `isMouse === false`
isMouse = false;
});


// now the code that you want to write
// should also work with `mouse*` events
$('a.someClass').on('click', function () {
if (isMouse) {
// interaction with mouse event
// ...
} else {
// reset for the next event detection
// this is crucial for devices that have both
// touch screen and mouse
isMouse = true;


// interaction with touch event
// ...
}
});

我遇到了同样的问题,单次触摸也被注册为一次点击。 在阅读了评选结果后,我想出了自己的解决方案:

var body = document.getElementsByTagName('body')[0];
var mouseCount = 0;


// start in an undefined state
// (i use this to blend in elements once we decide what input is used)
var interactionMode = 'undefined';




var registerMouse = function() {
// count up mouseCount every time, the mousemove event is triggered
mouseCount++;


// but dont set it instantly.
// instead wait 20 miliseconds (seems to be a good value for multiple move actions),
// if another mousemove event accoures switch to mouse as interaction
setTimeout(function() {
// a touch event triggers also exactly 1 mouse move event.
// So only if mouseCount is higher than 1 we are really moving the cursor by mouse.
if (mouseCount > 1) {
body.removeEventListener('mousemove', registerMouse);
body.removeEventListener('touchend', registerTouch);


interactionMode = 'mouse';
console.log('now mousing');
listenTouch();
}


// set the counter to zero again
mouseCount = 0;
}, 20);
};


var registerTouch = function() {
body.removeEventListener('mousemove', registerMouse);
body.removeEventListener('touchend', registerTouch);


interactionMode = 'touch';
console.log('now touching');
mouseCount = 0;


listenMouse();
};


var listenMouse = function() {
body.addEventListener("mousemove", registerMouse);
};
var listenTouch = function() {
body.addEventListener("touchend", registerTouch);
};


listenMouse();
listenTouch();


// after one second without input, assume, that we are touching
// could be adjusted to several seconds or deleted
// without this, the interactionMode would stay 'undefined' until first mouse or touch event
setTimeout(function() {
if (!body.classList.contains('mousing') || body.classList.contains('touching')) {
registerTouch();
}
}, 1000);
/* fix, so that scrolling is possible */


html,
body {
height: 110%;
}
Mouse or touch me

The only problem i found is, that you have to be able to scroll, to properly detect a touch event. a single tab(touch) might make problems.

到2018年,有一个很好的可靠的方法来检测一个浏览器是否有一个鼠标(或类似的输入设备) : CSS4媒体交互功能,现在几乎所有的现代浏览器都支持它(除了 IE 11和特殊的移动浏览器)。

W3C:

指针媒体特性用于查询存在性和准确性 指向一个指向设备,如鼠标。

请参阅以下选项:

    /* The primary input mechanism of the device includes a
pointing device of limited accuracy. */
@media (pointer: coarse) { ... }
    

/* The primary input mechanism of the device
includes an accurate pointing device. */
@media (pointer: fine) { ... }
    

/* The primary input mechanism of the
device does not include a pointing device. */
@media (pointer: none) { ... }


/* Primary input mechanism system can
hover over elements with ease */
@media (hover: hover) { ... }
    

/* Primary input mechanism cannot hover
at all or cannot conveniently hover
(e.g., many mobile devices emulate hovering
when the user performs an inconvenient long tap),
or there is no primary pointing input mechanism */
@media (hover: none) { ... }
    

/* One or more available input mechanism(s)
can hover over elements with ease */
@media (any-hover: hover) { ... }
    

    

/* One or more available input mechanism(s) cannot
hover (or there are no pointing input mechanisms) */
@media (any-hover: none) { ... }

媒体查询也可以在 JS 中使用:

if(window.matchMedia("(any-hover: none)").matches) {
// do something
}

相关阅读:

W3文档: https://www.w3.org/TR/mediaqueries-4/#mf-interaction

浏览器支持: https://caniuse.com/#search=media%20features

类似问题: 检测客户端设备是否支持: hover 和: focus 状态

我花了好几个小时在我的 Phonegap 应用程序上解决这个问题然后我想出了这个黑客技术。如果触发的事件是“被动”事件,它将生成一个控制台警告,这意味着它不会激活任何更改,但是它可以工作!我会对任何改进的想法或更好的方法感兴趣。但是这有效地允许我使用 $。普遍接触。

$(document).ready(function(){
$("#aButton").touch(function(origElement, origEvent){
console.log('Original target ID: ' + $(origEvent.target).attr('id'));
});
});


$.fn.touch = function (callback) {
var touch = false;
$(this).on("click", function(e){
if (!touch)
{
console.log("I click!");
let callbackReal = callback.bind(this);
callbackReal(this, e);
}else{
touch = true;
}
touch = false;
});
$(this).on("touchstart", function(e){
if (typeof e.touches != typeof undefined)
{
e.preventDefault();
touch = true;
console.log("I touch!");
let callbackReal = callback.bind(this);
callbackReal(this, e);
}
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<button id="aButton">A Button</button>

运行下面的代码片段来尝试一下:

var msg = (window.matchMedia("(any-pointer: coarse)").matches ? "Touchscreen" : "Mouse");

var msg = (window.matchMedia("(any-pointer: coarse)").matches ? "Touchscreen" : "Mouse");
document.getElementById('info').innerHTML = msg;
body {
background: black;
color: cornflowerblue;
}


#info {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 30vmin;
font-family: verdana, arial, helvetica;
}
<div id='info'></div>

(Here's more info about window.matchMedia.)