如何使用JavaScript最佳地检测“触屏”设备?

525

我编写了一个适用于桌面和移动设备的jQuery插件。我想知道是否有一种JavaScript方法可以检测设备是否具有触摸屏功能。我正在使用jquery-mobile.js来检测触摸屏事件,并且它在iOS、Android等设备上工作正常,但是我还想编写基于用户设备是否具有触摸屏的条件语句。

这种可能性存在吗?


这是更好的方法 var x = 'touchstart' in document.documentElement; console.log(x) // 如果支持,则返回true // 否则返回false - Risa__B
为什么即使有新技术出现,这个线程还是被保护了呢? - Andy Hoffman
38个回答

8
这个功能即使在Windows Surface平板电脑上也能良好运行!
function detectTouchSupport {
msGesture = window.navigator && window.navigator.msPointerEnabled && window.MSGesture,
touchSupport = (( "ontouchstart" in window ) || msGesture || window.DocumentTouch &&     document instanceof DocumentTouch);
if(touchSupport) {
    $("html").addClass("ci_touch");
}
else {
    $("html").addClass("ci_no_touch");
}
}

7
检测触摸的最大难点在于支持触摸和轨迹板/鼠标的混合设备。即使您能够正确检测用户的设备是否支持触摸,您实际上需要做的是检测用户当前使用的输入设备。这个挑战有一个详细的解释和可能的解决方案,请参考这里
基本上,确定用户是触摸屏幕还是使用鼠标/轨迹板的方法是在页面上注册touchstartmouseover事件。
document.addEventListener('touchstart', functionref, false) // on user tap, "touchstart" fires first
document.addEventListener('mouseover', functionref, false) // followed by mouse event, ie: "mouseover"

在大多数设备上,触摸操作将触发这两个事件,尽管前者 touchstart 通常会先于后者。因此,根据这种可预测的事件序列,您可以创建一种机制,动态地向文档根添加或删除 can-touch 类,以反映用户当前的输入类型。

;(function(){
    var isTouch = false //var to indicate current input type (is touch versus no touch) 
    var isTouchTimer 
    var curRootClass = '' //var indicating current document root class ("can-touch" or "")
     
    function addtouchclass(e){
        clearTimeout(isTouchTimer)
        isTouch = true
        if (curRootClass != 'can-touch'){ //add "can-touch' class if it's not already present
            curRootClass = 'can-touch'
            document.documentElement.classList.add(curRootClass)
        }
        isTouchTimer = setTimeout(function(){isTouch = false}, 500) //maintain "istouch" state for 500ms so removetouchclass doesn't get fired immediately following a touch event
    }
     
    function removetouchclass(e){
        if (!isTouch && curRootClass == 'can-touch'){ //remove 'can-touch' class if not triggered by a touch event and class is present
            isTouch = false
            curRootClass = ''
            document.documentElement.classList.remove('can-touch')
        }
    }
     
    document.addEventListener('touchstart', addtouchclass, false) //this event only gets called when input type is touch
    document.addEventListener('mouseover', removetouchclass, false) //this event gets called when input type is everything from touch to mouse/ trackpad
})();

更多细节在此处


7
我认为最好的方法是:
var isTouchDevice =
    (('ontouchstart' in window) ||
    (navigator.maxTouchPoints > 0) ||
    (navigator.msMaxTouchPoints > 0));
if(!isTouchDevice){
    /* Code for touch device /*
}else{
    /* Code for non touch device */
}

1
navigator.MaxTouchPoints -> navigator.maxTouchPoints - Jaeho Lee

6

我使用了以上代码的一部分来检测触摸事件,因此我的fancybox iframe只会在台式电脑上出现而不是在触摸设备上出现。但是,当仅使用blmstr的代码时,我发现Android 4.0上的Opera Mini仍然被注册为非触摸设备。(有人知道原因吗?)

最终,我采用了以下代码:

<script>
$(document).ready(function() {
    var ua = navigator.userAgent;
    function is_touch_device() { 
        try {  
            document.createEvent("TouchEvent");  
            return true;  
        } catch (e) {  
            return false;  
        }  
    }

    if ((is_touch_device()) || ua.match(/(iPhone|iPod|iPad)/) 
    || ua.match(/BlackBerry/) || ua.match(/Android/)) {
        // Touch browser
    } else {
        // Lightbox code
    }
});
</script>

能否请您解释一下,为什么不使用单个正则表达式/iPhone|iPod|iPad|Android|BlackBerry/来进行一次匹配呢? - Dmitry Koroliov
Opera Mini在Opera的服务器上进行渲染,而不是在设备本身上进行,所以这种方式有点奇怪。 - bluesmoon

6

实际上,我研究了这个问题并考虑了所有情况。因为这也是我项目中的一个重要问题。所以我找到了以下函数,它适用于所有浏览器的所有版本和所有设备:

const isTouchDevice = () => {
  const prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-', ''];
  const mq = query => window.matchMedia(query).matches;

  if (
    'ontouchstart' in window ||
    (window.DocumentTouch && document instanceof DocumentTouch)
  ) {
    return true;
  }
  return mq(['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join(''));
};

提示:毫无疑问,isTouchDevice 只返回 boolean 值。


我认为你需要 const IsTouchDevice = ( () => { ... } )(); - Rob
@Rob,这是一个函数,如何使用取决于开发人员。 - AmerllicA

5

看一下这篇文章,它提供了一个非常好的代码片段,用于在检测到触摸设备时要做什么,或者当触发touchstart事件时要做什么:

$(function(){
  if(window.Touch) {
    touch_detect.auto_detected();
  } else {
    document.ontouchstart = touch_detect.surface;
  }
}); // End loaded jQuery
var touch_detect = {
  auto_detected: function(event){
    /* add everything you want to do onLoad here (eg. activating hover controls) */
    alert('this was auto detected');
    activateTouchArea();
  },
  surface: function(event){
    /* add everything you want to do ontouchstart here (eg. drag & drop) - you can fire this in both places */
    alert('this was detected by touching');
    activateTouchArea();
  }
}; // touch_detect
function activateTouchArea(){
  /* make sure our screen doesn't scroll when we move the "touchable area" */
  var element = document.getElementById('element_id');
  element.addEventListener("touchstart", touchStart, false);
}
function touchStart(event) {
  /* modularize preventing the default behavior so we can use it again */
  event.preventDefault();
}

5
不,这是不可能的。提供的优秀答案只是部分答案,因为任何给定的方法都会产生误报和漏报。即使浏览器也不总是知道触摸屏是否存在,由于操作系统 API 和事实可以在浏览器会话期间更改,特别是在 KVM 类型的安排中。请参阅此优秀文章的进一步细节:

http://www.stucox.com/blog/you-cant-detect-a-touchscreen/

这篇文章建议您重新考虑想要检测触摸屏所需的假设,它们可能是错误的。(我为我的应用程序检查了自己的假设,结果发现我的假设确实是错误的!)
文章得出结论:
对于布局,请假设每个人都有触摸屏。与触摸用户使用小型控件相比,鼠标用户可以更轻松地使用大型UI控件。悬停状态也是如此。
对于事件和交互,请假设任何人都可能有触摸屏。同时实现键盘、鼠标和触摸交互,确保它们不会互相阻塞。

5
我建议不要使用屏幕宽度来确定设备是否为触摸设备,因为有些触摸屏幕比 699 像素大得多,例如 Windows 8。Navigator.userAgent 可以用于覆盖错误的结果。
我建议查看 Modernizr 上的 此问题
您是想测试设备是否支持触摸事件还是仅仅是一个触摸设备。不幸的是,这并不相同。

5
许多方法都能解决这个问题,但是要求使用jQuery或者JavaScript语法检查工具会报错。鉴于你最初的问题是要找到一个“JavaScript”(不是jQuery、也不是Modernizr)的解决方法,以下是一个简单的函数,每次都能正常工作。而且这个函数非常简洁。
function isTouchDevice() {
    return !!window.ontouchstart;
}

console.log(isTouchDevice());

最后要提到的一个好处是,这段代码不依赖于任何框架和设备。祝使用愉快!


4

问题

由于混合设备同时使用触摸和鼠标输入,您需要能够动态更改控制代码是否在用户为触摸用户的情况下运行的状态/变量。

触摸设备也会在轻触时触发mousemove事件。

解决方案

  1. 假定在加载时触摸为false。
  2. 等待touchstart事件被触发后,将其设置为true。
  3. 如果已触发touchstart,则添加mousemove处理程序。
  4. 如果两个mousemove事件之间的时间小于20ms,则假定它们正在使用鼠标作为输入。删除该事件,因为对于鼠标设备而言,mousemove是一种昂贵的事件。
  5. 当再次触发touchstart(用户返回使用触摸)时,变量将立即设置回true。并且重复此过程以便以动态方式确定。如果神奇般地在触摸上极快地连续触发mousemove两次(在我的测试中,几乎不可能在20ms内完成),则下一个touchstart将将其设置为true。

在Safari iOS和Chrome for Android上进行测试。

注意:关于MS Surface等的指针事件不确定。

Codepen演示


const supportsTouch = 'ontouchstart' in window;
let isUsingTouch = false;

// `touchstart`, `pointerdown`
const touchHandler = () => {
  isUsingTouch = true;
  document.addEventListener('mousemove', mousemoveHandler);
};

// use a simple closure to store previous time as internal state
const mousemoveHandler = (() => {
  let time;
  
  return () => {
    const now = performance.now();

    if (now - time < 20) {
      isUsingTouch = false;
      document.removeEventListener('mousemove', mousemoveHandler);
    }

    time = now;
  }
})();

// add listeners
if (supportsTouch) {
  document.addEventListener('touchstart', touchHandler);
} else if (navigator.maxTouchPoints || navigator.msMaxTouchPoints) {
  document.addEventListener('pointerdown', touchHandler);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接