PointerEvents:检测穿过元素的触摸

3
使用指针事件时,在智能手机上无法找到适当的事件来触发基于手指的触摸操作(在Chrome Android和Chrome Devtools中进行了测试)。
我需要的是:如果您通过保持手指按下并在屏幕上移动时“穿过”元素,则需要“悬停”事件。
也就是说,将手指放在元素外面,通过它移动,并且只有在完全穿过元素后才将手指抬起。
我附加了一个代码片段以清楚说明:我不需要蓝色元素的事件,我只需要片段中红色元素的相应“进入/离开”事件。示例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”。 - Roberto Zvjerković
当触摸的起点和终点都在红色元素之外时,既不会触发pointermove也不会触发pointerover - Dynalon
pointerover 在移动设备上也不起作用。恐怕您需要手动实现这样的逻辑。 - Roberto Zvjerković
希望我理解你的问题是正确的,因为在你的问题中我们可能会误解任务 - 没有图像的情况下很难理解任务。经过很多次尝试(花费了很多时间),我为你找到了两个解决方案。 - Bharata
@Bharata,我认为这样说不公平,我提供了一个在桌面上使用鼠标可以运行的代码片段,但在智能手机上无法运行。我的问题是如何在智能手机上使其正常工作,所以我认为我的问题很明确。 - Dynalon
2个回答

6

我希望我理解你的意思。我为您编写并测试了两种不同的解决方案:pointerevents和touch events。在每个事件的移动事件中,您可以使用函数document.elementFromPoint()检测当前元素。

使用pointerevents的解决方案

也许您可以使用pointerevents-它们在Chrome Devtools中与移动仿真一起使用,但在我的Android设备上无法使用(我认为我的设备太旧了)。或者您可以使用Pointer Events Polyfill。 pointerevents的浏览器兼容性可以在此处查看。

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 div{position: relative; left: 0; height: 20px}
.start{top: 0; background: blue}
.element{top: 20px; background: red}
.end{top: 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>

使用触摸事件的解决方案

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

以下代码片段仅适用于移动仿真(在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 div{position: relative; left: 0; height: 20px}
.start{top: 0; background: blue}
.element{top: 20px; background: red}
.end{top: 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>

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

1
接受您的 elementFromPoint() 解决方案。这比边界框命中测试要好得多,因为它适用于任何SVG形状/路径元素,也适用于仅具有描边属性而没有填充的SVG线条。 - Dynalon

1

试试这个

<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 不存在。此外,就其价值而言,这位专家建议尝试限制触摸处理程序的范围,以避免对滚动造成破坏:https://www.html5rocks.com/en/mobile/touchandmouse/ -- 但是尝试使用触摸事件是个好主意! - jacob.mccrumb
我特别想要pointerevents,但我也认为我的具体代码示例不适用于触摸事件。请随意提供一个可行的示例。 - Dynalon
好的...我想现在它能工作了;) 我刚刚更新了答案。我只在Chrome上使用设备模式测试过这个。 - Shawn Northrop
似乎移动设备上并不完全支持指针事件:https://caniuse.com/#search=pointerover - Shawn Northrop
如果您使用polyfills,那么您可以使用elem.addEventListener("pointerenter...elem.addEventListener("pointerout...来设置passedThroughStart/End值,而不是window.addEventListener("pointermove..."。如果您这样做,那么您可能可以利用上面的代码。 - Shawn Northrop
显示剩余3条评论

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