`pointermove` event not working with touch. Why not?

12,024

On the MDN documentation page about pointermove, there's this line:

The pointermove event is fired when a pointer changes coordinates, and the pointer has not been canceled by a browser touch-action.

source, emphasis mine

After a short period of time, the (mobile) browser will claim the pointermove event for "native" behavior like panning the page.

The designed, simple solution is to use the css property touch-action and set it to none on the container that has the event handler.

Here's the css property added to your codepen: https://codepen.io/anon/pen/XVBMvL

Or in a simplified example:

  • Set your browser to emulate touch (in Chrome, dev. tools > Sensors > Touch)
  • Start an interaction in the left part, and the dot will follow your finger
  • Start an interaction in the right part, and you'll notice it will quickly fail like in the provided example

var dot = document.querySelector(".dot")
document.body.addEventListener("pointermove", function(ev) {
  dot.style.transform = `translate3d(${ev.clientX}px, ${ev.clientY}px, 0)`;

}, false);
* { margin: 0; padding: 0 }

.wrapper { 
  display: flex; 
  height: 100vh;
}

.hasTouchAction, 
.noTouchAction {
  flex-grow: 1;
  text-align: center;
  background: #efefef;
}

.hasTouchAction {
  touch-action: none;
}

.noTouchAction {
  background: #ccc;
}

.dot {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: red;
  position: absolute;
  top: -8px;
  left: -8px;
}
<div class="wrapper">
  <div class="hasTouchAction">
    With <code>touch-action: none</code>
  </div>

  <div class="noTouchAction">
    Without <code>touch-action</code>
  </div>
</div>

<div class="dot"></div>

Make sure you don't break important things and hurt accessibility. Also spend some time to investigate browser support. This worked for me with touch emulated events in Chrome, but might not work in every browser...

Share:
12,024
trusktr
Author by

trusktr

Joe Pea trusktr.io (personal site) lume.io (open source 3D toolkit I'm working on) twitter github codepen

Updated on June 14, 2022

Comments

  • trusktr
    trusktr almost 2 years

    I have this pen:

    https://codepen.io/anon/pen/eyKeqK

    If you try it on a touch-screen device (f.e. visit the pen on your phone) you'll notice that when you drag, the white light (the little sphere) only moves for a tiny bit then it stops working.

    The logic for the movement is in the pointermove event handler. It works fine on Desktop using a mouse, just not with touch.

    How do we fix this so the light keeps moving while touch dragging (not just for a moment), and as bonus how do we prevent it from refreshing the page when we pull down?


    Here's the code for the pen:

    HTML (Slim):

    /! Made with http://github.com/trusktr/infamous
    
    script src="https://cdn.rawgit.com/trusktr/e37dbc24c51b9d3e2f9e508e75cf8f99/raw/2a3fee4ee506a05cc4ac509f592f0c3af1ddfed4/infamous-mixed-mode-3.js"
    script src="https://unpkg.com/[email protected]/src/Tween.js"
    
    i-scene experimental-webgl="true" id="scene" TODO-perspective="800" backgroundColor="0 0 0" backgroundOpacity="0" style="perspective: 800px" shadowmap-type="pcfsoft"
    
        i-ambient-light color="#404040" intensity="1"
    
        i-dom-plane id="bg" sizeMode="proportional proportional" size="1 1 0"
    
            i-node id="button-container" position="0 0 6" size="600 31 0" align="0.5 0.5 0" mountPoint="0.5 0.5 0"
    
                - for n in (0..4)
                    i-dom-plane sizeMode="literal proportional" size="100 1 0" align="#{n*0.25} 0 0" mountPoint="#{n*0.25} 0 0"
                        button button #{n+1}
    
            i-point-light id="light" color="white" position="300 300 120" size="0 0 0" cast-shadow="true" intensity="1"
                i-mesh has="sphere-geometry basic-material" size="10 10 10" color="white" receive-shadow="false" cast-shadow="false" style="pointer-events: none"
    

    CSS (Stylus):

    body, html
        width 100%
        height 100%
        margin 0
        padding 0
        font-family sans-serif
    
    i-node
        text-align center
    
    #bg
        background #62B997
    
    button
        width 100%
        height 100%
        white-space nowrap
        border-radius 0px
        border 1px solid #534334
        background lighten(#FB752C, 20%)
        color darken(#534334, 10%)
        outline none // remove those darn ugly browser-specific outlines
        &:focus, &:hover
            background #FB752C
            color darken(#534334, 20%)
    

    JavaScript:

    infamous.html.useDefaultNames()
    const Motor = infamous.core.Motor
    
    light.threeObject3d.shadow.radius = 3
    light.threeObject3d.distance = 20000
    light.threeObject3d.shadow.bias = 0.00001
    
    document.addEventListener('pointermove', e => {
        e.preventDefault()
        light.position.x = e.clientX
        light.position.y = e.clientY
    })
    
    let downTween, upTween, pressedButton
    
    // On mouse down animate the button downward
    document.addEventListener('pointerdown', e => {
        if ( is( e.target, 'button' ) ) {
    
            pressedButton = e.target
    
            if (upTween) {
                upTween.stop()
                upTween = null
            }
    
            downTween = new TWEEN.Tween(e.target.parentNode.position)
                .to({z: -6}, 75)
                .start()
                .onComplete(() => downTween = null)
    
            Motor.addRenderTask(time => {
                if (!downTween) return false
                downTween.update(time)
            })
    
        }
    })
    
    // On mouse up animate the button upward
    document.addEventListener('pointerup', e => {
        if ( pressedButton ) {
    
            if (downTween) {
                downTween.stop()
                downTween = null
            }
    
            upTween = new TWEEN.Tween(pressedButton.parentNode.position)
                .to({z: 0}, 75)
                .start()
                .onComplete(() => upTween = null)
    
            Motor.addRenderTask(time => {
                if (!upTween) return false
                upTween.update(time)
            })
    
        }
    })
    
    // The following is a temporary hack because opacity isn't
    // exposed through the HTML API yet. work-in-progress...
    setTimeout(() => {
        Array.from( document.querySelectorAll('i-dom-plane') ).forEach(n => {
            n.threeObject3d.material.opacity = 0.3
        })
    
        scene._needsToBeRendered()
    }, 0)
    
    function is( el, selector ) {
        if ( [].includes.call( document.querySelectorAll( selector ), el ) ) return true
        return false
    }
    
  • trusktr
    trusktr over 6 years
    Thanks!! I missed that part. It would've been simply intuitive for e.preventDefault() to do the trick, dang web APIs! With the PEP.js polyfill, it's working in Safari, Chrome, and Firefox (though PEP.js requires a touch-action attribute instead of a style property). I gotta get my hands on Edge with touch...
  • Maciej Krawczyk
    Maciej Krawczyk over 3 years
    It doesn't seem to work if it's set dynamically after pointerdown is fired.