PointerEvents:检测“通过”元素的触摸

Posted

技术标签:

【中文标题】PointerEvents:检测“通过”元素的触摸【英文标题】:PointerEvents: Detect touching "through" an element 【发布时间】:2018-11-19 14:37:44 【问题描述】:

使用指针事件,我找不到合适的事件来触发智能手机上的手指触摸(使用 Chrome android 和带有移动仿真的 Chrome Devtools 进行测试)。

我需要什么:如果您触摸动作穿过一个元素,同时按住手指在屏幕上移动,则发生“悬停”事件。

也就是说,将手指放在元素外面,穿过它,直到完全穿过元素后才向上移动手指。

我附上了一段代码来清除:我不需要蓝色元素的事件,我只需要 sn-p 中红色元素的相应“输入/输出”事件。示例 JS 代码将为鼠标触发,但在移动设备上它不会触发任何 console.infos。

var elem = document.querySelector(".element");

elem.addEventListener("pointerover", function() 
    console.clear();
    console.info("pointerover triggered");
);
elem.addEventListener("pointerenter", function() 
    console.clear();
    console.info("pointerenter triggered");
);
elem.addEventListener("pointerleave", function() 
    console.clear();
    console.info("pointerleave triggered");
);
.outer 
    width: 100px;
    height: 100px;
    border: 3px solid grey;
    font-size: 12px;
    color: white;
    text-align:center;
    touch-action: none;
    


.start 
   position: relative;
   top:0px;
   left:0px;
   width: 100px;
   height: 20px;
   background-color: blue;


.element 
   position: relative;
   top: 20px;
   left: 0px;
   width: 100px;
   height: 20px;
   background-color: red;


.end 
   position: relative;
   top: 40px;
   right: 0;
   width: 100px;
   height: 20px;
   background-color: blue;
<div class="outer">
    <div class="start">Start touch here</div>
    <div class="element">Move over here</div>
    <div class="end">End touch here</div>
</div>

【问题讨论】:

我认为这些活动根本无法在移动设备上运行。试试pointermove 当触摸的开始和结束在红色元素之外时,pointermovepointerover 都不会被触发。 pointerover 也不适用于移动设备。恐怕你必须手动实现这样的逻辑。 我希望我能正确理解您,因为在您的问题中,我们可能会误解任务——没有图像理解任务相对困难。经过很多萨满手鼓的舞蹈(很多时间),我为你找到了两个解决方案。 @Bharata 我认为这是不公平的说法,我提供了一个运行中的 sn-p,它可以在桌面上用鼠标工作,但不能在智能手机上工作。我要求的是如何让它在智能手机上运行,​​所以我认为我的问题很清楚。 【参考方案1】:

希望我能正确理解您。我为您编写并测试了两种不同的解决方案:指针事件和触摸事件。在此事件的每个移动事件中,您可以使用函数 document.elementFromPoint() 检测当前元素。

指针事件解决方案

也许你可以使用指针事件——它们可以在 Chrome Devtools 中使用移动仿真,但不能在我的 Android 设备上工作(我认为我的设备太旧了)。或者,也许您可​​以将它与 Pointer Events Polyfill 一起使用。浏览器对指针事件的兼容性可以看here

var elementFromPoint,
    isFingerDown = false,
    isThroughElemMoved = false,
    elem = document.querySelector('.element'),
    output = document.querySelector('#output');

document.addEventListener('pointerdown', function(e)

    if(elem != e.target)
    
        isFingerDown = true;
        output.innerhtml = 'pointer-START';
    
);

document.addEventListener('pointermove', function(e)

    elementFromPoint = document.elementFromPoint(e.pageX - window.pageXOffset, e.pageY - window.pageYOffset);

    if(elem == elementFromPoint)
    
        isThroughElemMoved = true;
        output.innerHTML = 'pointer-MOVE';
    
);

document.addEventListener('pointerup', function(e)

    if(isFingerDown && isThroughElemMoved && elem != elementFromPoint)
        output.innerHTML = 'It is done!';

    isFingerDown = isThroughElemMoved = false;
);
.outer

    width: 100px;
    height: 100px;
    border: 3px solid grey;
    font-size: 12px;
    color: white;
    text-align: center;
    /*touch-action: none*/

.outer divposition: relative; left: 0; height: 20px
.starttop: 0; background: blue
.elementtop: 20px; background: red
.endtop: 40px; background: blue
<div class="outer">
    <div class="start">Start touch here</div>
    <div class="element">Move over here</div>
    <div class="end">End touch here</div>
</div>
<br><br>
<div id="output">info</div>

触摸事件解决方案

但您也可以使用 touch events。不幸的是,事件 touchentertouchleave 已从规范中删除,因此我们也必须使用 document.elementFromPoint() 编写解决方法。

以下 sn-p 仅适用于移动仿真(使用 Chrome Devtools 测试)或支持触摸事件的设备(使用 Android 测试)。

var elementFromPoint,
    isFingerDown = false,
    isThroughElemMoved = false,
    elem = document.querySelector('.element'),
    output = document.querySelector('#output');

document.addEventListener('touchstart', function(e)

    if(elem != e.target)
    
        isFingerDown = true;
        output.innerHTML = 'touch-START';
    
);

document.addEventListener('touchmove', function(e)

    var touch = e.touches[0];
    
    elementFromPoint = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset);

    if(elem == elementFromPoint)
    
        isThroughElemMoved = true;
        output.innerHTML = 'touch-MOVE';
    
);

document.addEventListener('touchend', function(e)

    if(isFingerDown && isThroughElemMoved && elem != elementFromPoint)
        output.innerHTML = 'It is done!';

    isFingerDown = isThroughElemMoved = false;
);
.outer

    width: 100px;
    height: 100px;
    border: 3px solid grey;
    font-size: 12px;
    color: white;
    text-align: center;
    /*touch-action: none*/

.outer divposition: relative; left: 0; height: 20px
.starttop: 0; background: blue
.elementtop: 20px; background: red
.endtop: 40px; background: blue
<div class="outer">
    <div class="start">Start touch here</div>
    <div class="element">Move over here</div>
    <div class="end">End touch here</div>
</div>
<br><br>
<div id="output">info</div>

也许以下链接可以帮助您:

Get element from point when you have overlapping elements? How to find out the actual event.target of touchmove javascript event?

【讨论】:

接受elementFromPoint() 的解决方案。这大大优于边界框命中测试,因为它适用于任何 SVG 形状/路径元素,也适用于没有填充但只有描边属性的 SVG 线条。【参考方案2】:

试试这个

<script>
    var startElem = document.querySelector(".start");
    var endElem = document.querySelector(".end");
    var elem = document.querySelector(".element");

    var started = false;
    var passedThroughStart = false;
    var passedThroughEnd = false;
    var ended = false;

    startElem.addEventListener("pointerdown", function(e)
        started = true;
    );

    window.addEventListener("pointermove", function(e) 
        var x = e.clientX;
        var y = e.clientY;
        var bounds = elem.getBoundingClientRect();

        if( !passedThroughStart &&
            x > bounds.left && x < bounds.left + bounds.width &&
            y > bounds.top && y < bounds.top + bounds.height
        )
            passedThroughStart = true;
        

        if( passedThroughStart && !passedThroughEnd &&
            x > bounds.left && x < bounds.left + bounds.width &&
            y > bounds.top + bounds.height
        )
            passedThroughEnd = true;
        
    )

    window.addEventListener("pointerup", function(e) 
        var x = e.clientX;
        var y = e.clientY;
        var bounds = endElem.getBoundingClientRect();

        ended = ( x > bounds.left && x < bounds.left + bounds.width && y > bounds.top && y < bounds.top + bounds.height)

        if( started && passedThroughStart && passedThroughEnd && ended )
            console.log("Hooray!");
        

        started = false;
        passedThroughStart = false;
        passedThroughEnd = false;
        ended = false;
    );
</script>

或者使用pointerenterpointerleave 而不是pointermove

elem.addEventListener('pointenter', function(e) 
    passedThroughStart = true;

elem.addEventListener('pointleave', function(e) 
    passedThroughEnd = true;

【讨论】:

我认为touchenter / touchleave 不存在。此外,对于它的价值,这家伙建议尝试限制触摸处理程序的范围,以免它对滚动造成严重破坏:html5rocks.com/en/mobile/touchandmouse - 但是尝试触摸事件的好方法! 我特别想要指针事件,但我也认为我的特定代码示例不适用于触摸事件。随意提供一个工作示例 我已经更新,但我正在寻找更简洁的答案 好的...我想我现在可以工作了;)刚刚再次更新了答案。我只使用设备模式在 chrome 上测试过这个 我曾希望避免手动边界框命中测试,但我似乎没有办法绕过它。适用于矩形和圆形,但对于非规则元素变得非常复杂(我使用 SVG...)

以上是关于PointerEvents:检测“通过”元素的触摸的主要内容,如果未能解决你的问题,请参考以下文章

在允许孩子捕捉触摸的同时防止父母被点击

检测对子层元素的触摸

检测 UIWebView 上的触摸,但忽略滑动或触摸交互式元素(链接或点击)

检测触摸点的控制/视图

Arduino UNO通过电容的直接检测实现简易触摸开关

Chrome 55 Beta发布