Detecting that the browser has no mouse and is touch-only
我正在开发一个webapp(不是一个有有趣文本页面的网站),它有一个非常不同的触摸界面(当你点击时,你的手指会隐藏屏幕)和鼠标(很大程度上依赖于鼠标悬停预览)。我怎样才能发现我的用户没有鼠标来呈现给他正确的界面?我计划给有鼠标和触摸功能的人留一个开关(比如笔记本)。
浏览器中的触摸事件功能实际上并不意味着用户正在使用触摸设备(例如,Modernizer不会切断它)。如果设备有鼠标,则正确回答问题的代码应返回false,否则返回true。对于带有鼠标和触摸的设备,它应返回"假"(而不是仅触摸)
作为旁注,我的触摸界面可能也适用于只使用键盘的设备,所以我更希望检测到的是缺少鼠标。
为了使需求更加明确,下面是我要实现的API:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 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); |
主要问题是,您有以下不同类型的设备/用例:
更糟糕的是,你可以从这些类中的一部分过渡到另一类(插入鼠标,连接到键盘),或者一个用户可能出现在一台普通的笔记本电脑上,直到他们伸出手触摸屏幕。
假设浏览器中存在事件构造器不是向前移动的好方法(而且有些不一致),这是正确的。此外,除非您正在跟踪一个非常具体的事件,或者只是试图排除上面的一些类,否则使用事件本身并不是完全的证明。
例如,假设您发现一个用户发出了一个真正的mousemove(不是touch事件中的错误mousemove,请参见http://www.html5rocks.com/en/mobile/touchandmouse/)。
那又怎样?
是否启用悬停样式?添加更多按钮?
无论哪种方式,你都在增加玻璃化的时间,因为你必须等待一个事件发生。
但是当你的高贵用户决定拔下鼠标并进行完全触摸时会发生什么。你是否等待他触摸你现在拥挤的界面,然后在他努力精确定位你现在拥挤的用户界面之后马上改变它?
以项目符号形式引用Stucox,网址为:https://github.com/modernizer/modernizer/issues/869 issuecomment-15264101
- We want to detect the presence of a mouse
- Ae probably can't detect before an event is fired
- As such, what we're detecting is if a mouse has been used in this session — it won't be immediately from page load
- We probably also can't detect that there isn't a mouse — it'd be undefined until true (I think this makes more sense than setting
it false until proven)- And we probably can't detect if a mouse is disconnected mid-session — that'll be indistinguishable from the user just giving up with their
mouse
号
旁白:浏览器知道用户何时插入鼠标/连接到键盘,但不会将其暴露在javascript中。当当!
这将引导您实现以下目标:
Tracking the current capabilities of a given user is complex, unreliable, and of dubious merit
号
不过,渐进增强的思想在这里很适用。建立一种无论用户上下文如何都能顺利工作的体验。然后根据浏览器功能/媒体查询进行假设,以添加在假定上下文中相对应的功能。鼠标的出现只是不同设备上不同用户体验网站的众多方式之一。创建一个核心有价值的东西,不要太担心人们如何点击按钮。
听文档上的mousemove事件怎么样?然后,在听到该事件之前,您假定设备只是触摸或键盘。
1 2 3 4 5 6 7 | var mouseDetected = false; function onMouseMove(e) { unlisten('mousemove', onMouseMove, false); mouseDetected = true; // initializeMouseBehavior(); } listen('mousemove', onMouseMove, false); |
(如果
希望这不会导致太多的视觉效果,如果你需要基于模式的大规模重新布局,这会很糟糕…
@怀亚特的回答很好,给了我们很多思考。
在我的案例中,我选择倾听第一次交互,然后只设置一个行为。所以,即使用户有鼠标,如果第一次交互是触摸,我也会把它当作触摸设备。
考虑到事件处理的给定顺序:
我们可以假设,如果鼠标事件在触摸前被触发,它是一个真正的鼠标事件,而不是一个模拟的事件。示例(使用jquery):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $(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); }); |
。
那对我有用
截至2018年,有一种良好可靠的方法来检测浏览器是否有鼠标(或类似的输入设备):CSS4媒体交互功能,现在几乎所有现代浏览器都支持这种功能(除IE 11和特殊的移动浏览器)。
W3C:
The pointer media feature is used to query the presence and accuracy
of a pointing device such as a mouse.
号
请参见以下选项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /* 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:
1 2 3 | if(window.matchMedia("(any-hover: none)").matches) { // do sth } |
相关:
官方工作草案:https://drafts.csswg.org/mediaqueries/mf interaction
浏览器支持:https://caniuse.com/search=media%20功能
类似问题:检测客户端设备是否支持:悬停和:焦点状态
只能检测浏览器是否具有触摸功能。无法知道它是否真的连接了触摸屏或鼠标。
但是,如果检测到触摸功能,可以通过监听触摸事件而不是鼠标事件来优先使用。
要检测触摸功能,请跨浏览器:
1 2 3 4 5 | function hasTouch() { return (('ontouchstart' in window) || // html5 browsers (navigator.maxTouchPoints > 0) || // future IE (navigator.msMaxTouchPoints > 0)); // current IE10 } |
然后可以用这个来检查:
1 | if (!hasTouch()) alert('Sorry, need touch!); |
号
或者选择要收听的事件,或者:
1 2 | var eventName = hasTouch() ? 'touchend' : 'click'; someElement.addEventListener(eventName , handlerFunction, false); |
或使用单独的方法进行触摸与非触摸:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 } |
。
对于鼠标,只能检测鼠标是否正在使用,而不是是否存在。可以设置一个全局标志来指示鼠标是通过使用检测到的(类似于现有答案,但简化了一点):
1 2 3 4 5 | var hasMouse = false; window.onmousemove = function() { hasMouse = true; } |
(不能包括
浏览器限制对低级系统API的访问,这些API需要能够检测其所使用系统的硬件功能等功能。
有可能编写一个插件/扩展来访问这些插件,但是通过javascript和dom这样的检测是有限的,因此必须为各种操作系统平台编写一个特定的插件。
综上所述:这种检测只能用"好的猜测"来估计。
当媒体查询级别4在浏览器中可用时,我们将能够使用"指针"和"悬停"查询来用鼠标检测设备。
如果我们真的想把这些信息传递给javascript,我们可以使用css查询根据设备类型设置特定的样式,然后使用javascript中的
但鼠标可以随时连接或拔出,用户可能希望在触摸和鼠标之间切换。因此,我们可能需要检测到这种变化,并提供更改接口或自动进行更改。
既然您打算提供一种在界面之间切换的方法,那么简单地让用户单击一个链接或按钮"输入"应用程序的正确版本是可行的吗?然后你就可以记住他们对未来访问的偏好。这不是高科技,但100%可靠
@不幸的是,Samuellossille没有一个我所知道的浏览器公开了鼠标的存在(或缺少鼠标)。
因此,有了这样的说法,我们只需要尽我们所能,尽我们现有的选择…事件。我知道这不是你想要的…同意这一点,目前还远远不够理想。
我们可以尽最大努力找出用户在任何特定时刻是使用鼠标还是触摸。下面是一个使用jquery&knockout的快速而肮脏的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //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()和/或使用body.mouse类设置接口样式。一旦检测到鼠标光标并在TouchStart上,界面将来回切换。
希望我们很快能从浏览器供应商那里得到更好的选择。
TeraWurfl可以通过将浏览器签名与其数据库进行比较来告诉您访问站点的设备的功能。看看,免费的!
在类似的情况下,这对我也很有效。基本上,假设用户没有鼠标,直到您看到一系列连续的鼠标移动,而不干预鼠标窗口或鼠标组。不太优雅,但很管用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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); |
号
为什么你不直接检测它是否有感知触摸和/或对鼠标移动做出反应的能力?
1 2 3 4 5 6 7 8 9 | // 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); } |
。
在我看来,最好的办法是听
基于触摸的浏览器只在用户点击屏幕(用户的手指向下)时模拟此事件是有意义的。这意味着我们应该在mousemove处理程序期间添加一个测试,以查看事件期间哪个鼠标按钮被按下(如果有的话)。如果没有鼠标按钮被按下,我们可以安全地假设存在一个真正的鼠标。如果鼠标按钮按下,测试仍不确定。
那么,这将如何实现呢?这个问题表明,在mousemove期间检查哪个鼠标按钮被关闭的最可靠的方法是在文档级别实际监听3个事件:mousemove、mouse down和mouseup。向上和向下仅设置全局布尔标志。移动将执行测试。如果您有一个移动并且布尔值为假,我们可以假设存在一个鼠标。有关确切的代码示例,请参见问题。
最后一条评论……此测试不理想,因为它不能在加载时间内执行。因此,我将使用前面建议的渐进增强方法。默认情况下,显示不支持鼠标特定悬停界面的版本。如果发现鼠标,请使用JS在运行时启用此模式。这对用户来说应该是尽可能无缝的。
为了支持用户配置中的更改(即鼠标已断开连接),可以定期重新测试。尽管我认为在这种情况下,最好是简单地通知用户这两种模式,并让用户手动在两种模式之间切换(就像移动/桌面选择一样,总是可以颠倒)。
我在这里看到的主要问题是,大多数触摸设备都会触发鼠标事件以及相应的触摸事件(TouchStart->MouseDown、TouchMove->MouseMove等)。对于只有键盘的,最后对于现代的,他们有一个通用的浏览器,所以你甚至不能检测到mouseEvent类的存在。
在我看来,这里不那么痛苦的解决方案是,在启动时显示一个菜单(只对键盘用户使用"alt"管理),或者将选项存储在localstorage/cookies/serverside中,或者在下次访客到来时保留salme选项。
看看Modenizer的一个特点是触摸
http://modernizer.com/docs/功能杂项
虽然我没有完全测试过,但它似乎工作得很好
这也是从"现代化"页面链接的http://www.html5rocks.com/en/mobile/touchandmouse/
我认为(当然,据我所知)识别触摸式设备是不可能的。主要问题是所有鼠标和键盘事件都是由触摸设备触发的。请参阅以下示例,两个警报对于触摸设备都返回true。
1 2 3 4 5 6 7 8 9 10 | 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()); |
我花了好几个小时为我的PhoneGap应用程序找出这个问题,然后我想出了这个方法。如果触发的事件是"被动"事件,它将生成控制台警告,这意味着它不会启动任何更改,但它可以工作!我会对任何改进的想法或更好的方法感兴趣。但这有效地允许我通用地使用$.touch()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | $(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); } }); } |
。
1 2 3 | <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"> <button id="aButton">A Button</button> |
在各种PC、Linux、iPhone、Android手机和标签上运行了一些测试。奇怪的是没有简单的防弹方案。当一些有触摸但没有鼠标的用户仍然在应用程序中显示触摸和鼠标事件时,问题就出现了。因为只想支持鼠标和触摸实例,所以要同时处理这两个实例,但这会导致用户交互出现两次。如果可以知道设备上没有鼠标,则可以知道忽略假/插入的鼠标事件。如果遇到mousemove,尝试设置标志,但有些浏览器会抛出假mousemove以及mouseup和mousedown。尝试检查时间戳,但认为这太冒险了。底线:我发现创建假鼠标事件的浏览器总是在插入鼠标前插入一个鼠标。在99.99%的情况下,当在一个有真正鼠标的系统上运行时,会有多个连续的鼠标移动事件-至少两个。因此,跟踪系统是否遇到两个连续的mousemove事件,并声明如果永远不满足此条件,则不存在鼠标。这可能太简单了,但它在我的所有测试设置中都起作用。我会坚持下去直到找到更好的解决方案。-吉姆W
jquery中检测鼠标使用情况的简单解决方案,解决了移动设备也会触发"mousemove"事件的问题。只需添加一个TouchStart监听器来删除鼠标移动监听器,这样它就不会在触摸时被触发。
1 2 3 4 5 6 | $('body').one('touchstart.test', function(e) { // Remove the mousemove listener for touch devices $('body').off('mousemove.test'); }).one('mousemove.test', function(e) { // MOUSE! }); |
当然,该设备仍然可以触摸和鼠标,但以上将保证使用的是真正的鼠标。
刚刚找到了一个我认为很优雅的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // 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 // ... } }); |
我遇到了同样的问题,一次触摸也被注册为点击。在我阅读了投票结果最高的答案后,我提出了自己的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | 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); |
。
1 2 3 4 5 6 | /* fix, so that scrolling is possible */ html, body { height: 110%; } |
1 | Mouse or touch me |
。
我发现的唯一问题是,你必须能够滚动,才能正确地检测到触摸事件。一个标签(触摸)可能会产生问题。
正如其他人所指出的,确定地检测他们是否有老鼠是不可靠的。这很容易改变,取决于设备。这绝对是你不能用布尔真或假来可靠地做的事情,至少在文档规模上是这样。
触摸事件和鼠标事件是独占的。所以这对采取不同的行动有所帮助。问题是触摸事件更接近鼠标上/下/移动事件,也会触发单击事件。
从你的问题,你说你想有一个悬停预览。除此之外,我不知道关于您的界面的任何其他细节。我假设在缺少鼠标的情况下,您希望点击进行预览,而点击则因悬停预览而执行不同的操作。
如果是这种情况,您可以采取一些懒惰的检测方法:
onclick事件之前总是有一个onmouseover事件和一个鼠标。因此,请注意,鼠标位于已单击的元素的顶部。
您可以通过一个文档范围的onmousemove事件来完成此操作。您可以使用
从这里,您可以选择两者都依赖于单击事件,并根据结果采取A或B操作。如果某些触摸设备不发出单击事件(相反,您必须依赖于touch*事件),则b操作可能是零。
我想推荐一个帮助我的脚本:
我已经阅读并尝试了所有建议,但没有足够的结果。
然后我调查了更多,发现了这个代码-device.js
我在我客户的网站上使用这个来检测鼠标的存在:(
我强烈建议不要采用这种方法。考虑一下触摸屏,桌面大小的设备,你有一套不同的问题要解决。
请使您的应用程序可用,无需鼠标(即无悬停预览)。
通常,最好检测mouseover函数是否受支持,而不是检测OS/浏览器类型。您可以通过以下方法来实现这一点:
1 2 3 | if (element.mouseover) { //Supports the mouseover event } |
请确保不要执行以下操作:
1 2 3 | if (element.mouseover()) { // Supports the mouseover event } |
号
后者实际上会调用该方法,而不是检查其是否存在。
您可以在这里阅读更多信息:http://www.quirksmode.org/js/support.html