检测到浏览器没有鼠标且仅触摸

问题描述 投票:135回答:24

我正在开发一个webapp(不是一个有趣文本页面的网站),它有一个非常不同的触摸界面(当你点击时你的手指隐藏了屏幕)和鼠标(严重依赖于悬停预览)。如何检测到我的用户没有鼠标向他显示正确的界面?我计划为鼠标和触摸的人留下一个开关(就像一些笔记本电脑)。

浏览器中的触摸事件功能实际上并不意味着用户正在使用触摸设备(例如,Modernizr不会剪切它)。如果设备有鼠标,正确回答问题的代码应该返回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);
javascript html5 touch mouse
24个回答
56
投票

如何在文档上监听mousemove事件。然后,直到您听到该事件,您才认为该设备仅为触摸或键盘。

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

listenunlisten酌情委托给addEventListenerattachEvent。)

希望这不会导致太多的视觉冲击,如果您需要基于模式的大量重新布局,它会很糟糕...


2
投票

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

// 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);
}

2
投票

在类似的情况下,这对我有用。基本上,假设用户没有鼠标,直到您看到一系列连续的鼠标移动,而不会干预mousedowns或mouseups。不是很优雅,但它的工作原理。

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);

0
投票

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

在我看来,这里不那么痛苦的解决方案是在启动时显示一个菜单(仅对键盘用户进行'alt'管理),并可能将选项存储在localStorage / cookies / serverside中,或者保留下一个salme选项。访客来的时间。


0
投票

看看modenizr的一个功能就是触控

http://modernizr.com/docs/#features-misc

虽然我没有完全测试,但似乎工作得很好

这也是现代化页面http://www.html5rocks.com/en/mobile/touchandmouse/的链接


0
投票

正如其他人所指出的那样,明确地检测他们是否有鼠标是不可靠的。这可以很容易地改变,具体取决于设备。这绝对是你无法使用布尔值true或false来可靠的,至少在文档范围内。

触摸事件和鼠标事件是独占的。所以这有助于采取不同的行动。问题是触摸事件更接近鼠标上/下/移动事件,并且还触发点击事件。

从你的问题,你说你想要悬停预览。除此之外,我不知道有关您的界面的任何其他细节。我假设没有鼠标你需要点击预览,而点击会因为悬停预览而执行不同的操作。

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

onclick事件将始终带有带鼠标的onmouseover事件。因此请注意鼠标位于已单击的元素的顶部。

您可以使用文档范围的onmousemove事件执行此操作。您可以使用event.target记录鼠标所在的元素。然后在您的onclick事件中,您可以检查鼠标是否实际位于被单击的元素上(或元素的子元素)。

从那里,您可以选择依赖于两者的click事件,并根据结果采取A或B动作。如果某些触摸设备不发出点击事件(而您必须依赖ontouch *事件),则B动作可能无效。


0
投票

我不认为识别仅触摸设备是可能的(据我所知)。主要问题是触摸设备也触发了所有鼠标和键盘事件。请参阅以下示例,两个警报对于触摸设备都返回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());

0
投票

在我看来,最好的想法是mousemove听众(目前是最佳答案)。我相信这种方法需要稍微调整一下。确实,基于触摸的浏览器甚至可以模拟mousemove事件,正如你在this iOS discussion中看到的那样,所以我们应该小心一点。

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

那么如何实现呢?这个question表明在鼠标移动期间检查哪个鼠标按钮关闭的最可靠方法是实际监听文档级别中的3个事件:mousemove,mousedown和mouseup。向上和向下只会设置一个全局布尔标志。此举将进行测试。如果你有一个移动并且布尔值为假,我们可以假设存在一个鼠标。查看确切代码示例的问题。

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

为了支持用户配置的更改(即鼠标已断开连接),您可以定期重新测试。虽然我相信在这种情况下更好地通知用户有关这两种模式并允许用户在它们之间手动切换(很像移动/桌面选择,总是可以颠倒)。


0
投票

在各种PC,Linux,iPhone,Android手机和标签上进行一些测试。很奇怪,没有简单的防弹解决方案。当一些具有Touch且没有鼠标的人仍然将Touch和Mouse事件呈现给应用程序时出现问题。由于确实要支持仅鼠标和仅触摸实例,因此希望同时处理这两个实例,但这会导致用户交互的两次出现。如果可以知道设备上没有鼠标,则可以知道忽略伪造/插入的鼠标事件。如果遇到MouseMove,尝试设置标志,但有些浏览器抛出假的MouseMove以及MouseUp和MouseDown。试过检查时间戳,但认为这太冒险了。结论:我发现创建假鼠标事件的浏览器总是在插入的MouseDown之前插入一个MouseMove。在99.99%的情况下,当在具有真实鼠标的系统上运行时,有多个连续的MouseMove事件 - 至少两个。因此,跟踪系统是否遇到两个连续的MouseMove事件,并声明如果从未满足此条件,则不存在鼠标。这可能太简单了,但是我的所有测试设置都在工作。我想我会坚持下去,直到找到更好的解决方案。 - 吉姆W


0
投票

jQuery中用于检测鼠标使用情况的简单解决方案,解决了移动设备也触发“mousemove”事件的问题。只需添加一个touchstart监听器即可删除mousemove监听器,以便在触摸时不会触发它。

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

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


0
投票

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

// 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
    // ...
  }
});

56
投票

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

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

更糟糕的是,人们可以从其中一些类转换到其他类(插入鼠标,连接键盘),或者用户可能会在普通笔记本电脑上显示,直到它们伸出并触摸屏幕。

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

例如,假设您已经发现用户发出了真正的鼠标移动(而不是触摸事件中的错误移动,请参阅http://www.html5rocks.com/en/mobile/touchandmouse/)。

那又怎样?

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

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

但是当你的高贵用户决定拔掉他的鼠标并完全触摸时会发生什么...你是否等待他触摸你现在挤满的界面,然后在他努力确定你现在拥挤的用户界面之后立即改变它?

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

  • 我们想要检测鼠标的存在
  • 在事件发生之前,Ae可能无法检测到
  • 因此,我们正在检测的是,如果在此会话中使用了鼠标 - 它将不会立即从页面加载
  • 我们可能也无法检测到没有鼠标 - 它是真的未定义(我认为这比设置它更有意义,直到证明)
  • 而且我们可能无法检测鼠标是否在会话期间断开连接 - 这与用户放弃鼠标无法区分

抛开:浏览器知道用户何时插入鼠标/连接键盘,但不会将其暴露给JavaScript ..当然!

这应该会引导您进行以下操作:

跟踪给定用户的当前能力是复杂的,不可靠的,并且具有可疑的优点

但是,渐进式增强的想法在这里很适用。无论用户的上下文如何,都可以构建一种平稳运行的体验。然后根据浏览器功能/媒体查询进行假设,以添加在假定上下文中相对的功能。鼠标的存在只是不同设备上不同用户体验您的网站的众多方式之一。在内核中创建具有优点的东西,不要过于担心人们如何点击按钮。


0
投票

我遇到了同样的问题,其中一次触摸也被注册为点击。在我阅读了最高投票答案的评论后,我提出了自己的解决方案:

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

我发现的唯一问题是,你必须能够滚动,以正确检测触摸事件。单个标签(触摸)可能会出问题。


0
投票

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

$(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>

-4
投票

我强烈建议不要这样做。考虑使用触摸屏,桌面大小的设备,您需要解决一组不同的问题。

请在没有鼠标的情况下使您的应用程序可用(即没有悬停预览)。


-4
投票

想推荐一个帮助我的脚本:

我已阅读并尝试了所有建议,但没有足够的结果。

然后我调查了一些,发现这个代码 - device.js

我在客户的网站上使用它来检测鼠标的存在: (<html>应该有desktop类)并且看起来非常好,并且对于touch支持,我只是做常规检查'ontouchend' in document并使用来自两个检测的信息来假设我需要的特定事物。


-7
投票

通常最好检测是否支持鼠标悬停功能,而不是检测OS /浏览器类型。您可以通过以下方式完成此操作:

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

请确保您不执行以下操作:

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

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

你可以在这里阅读更多信息:http://www.quirksmode.org/js/support.html


23
投票

@ Wyatt的答案很棒,给了我们很多思考。

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

考虑到given order in which events are processed

  1. touchstart
  2. touchmove
  3. touchEnd
  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);
});

这对我有用


10
投票

截至2018年,有一种良好可靠的方法来检测浏览器是否有鼠标(或类似的输入设备):CSS4媒体交互功能已被大多数现代浏览器支持(其余部分将很快推出)。

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 sth
}

有关:

官方工作草案:https://drafts.csswg.org/mediaqueries/#mf-interaction

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

类似的问题:Detect if a client device supports :hover and :focus states


9
投票

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

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

要检测跨浏览器的触摸功能:

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这样的检测仅限于此目的,并且必须编写专用于各种OS平台的插件。

总而言之:这种检测只能通过“好猜测”来估算。


7
投票

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

如果我们真的想要将这些信息传达给Javascript,我们可以使用CSS查询根据设备类型设置特定样式,然后在Javascript中使用getComputedStyle来读取该样式,并从中派生原始设备类型。

但是可以随时连接或拔出鼠标,用户可能想要在触摸和鼠标之间切换。因此,我们可能需要检测此更改,并提供更改界面或自动执行此操作。


6
投票

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


4
投票

@SamuelRossille遗憾的是,没有我知道的浏览器暴露了鼠标的存在(或缺乏)。

所以,说到这里,我们只需要尝试使用我们现有的选项...事件做到最好。我知道这并不是你想要的......同意它目前远非理想。

我们可以尽力确定用户是否在任何特定时刻使用鼠标或触摸。这是一个使用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()和usingTouch()和/或使用body.mouse类设置您的界面样式。一旦检测到鼠标光标并且在touchstart上,界面将立即来回切换。

希望我们很快就可以从浏览器供应商处获得更好的选择。


2
投票

Tera-WURFL可以通过比较浏览器签名与其数据库来告诉您访问您网站的设备的功能。看看吧,免费!

© www.soinside.com 2019 - 2024. All rights reserved.