Search code examples
javascriptreactive-programmingdom-eventssvelte

Svelte Long Press


I need a long press event to bind to buttons in svelte 3. I want to do this in the least "boilerplaty" way possible.

I've tried with a long press function but this seems a little convoluted and hacky, also seems a little slow.

function longPress(node, callback) {
     console.log(node)
    function onmousedown(event) {
      const timeout = setTimeout(() => callback(node.innerHTML), 1000);

      function cancel() {
        clearTimeout(timeout);
        node.removeEventListener("mouseup", cancel, false);
      }

      node.addEventListener("mouseup", cancel, false);
    }

    node.addEventListener("mousedown", onmousedown, false);

    return {
      destroy() {
        node.removeEventListener("mousedown", onmousedown, false);
      }
    };
  }
</script>

<div>
  <Video />
  {#each Object.entries(bindings) as [id, value]}
    <button on:click = {()=>longPress(this,addImage)}> {id} </button>
  {/each}
</div>

This works but I'm sure there is a better way.


Solution

  • For this sort of thing I would use an action, which is a function that runs when an element is created (and can return functions that are run when parameters are changed, or the element is destroyed): https://svelte.dev/tutorial/actions

    In this case you could create a reusable longpress action, much like your function above, which dispatches a custom longpress event on the target element that you can listen for like a native DOM event:

    <script>
      import { longpress } from './actions.js';
      let pressed;
    </script>
    
    <button use:longpress on:longpress="{e => pressed = true}">
      longpress me
    </button>
    
    export function longpress(node, threshold = 500) {
      // note — a complete answer would also consider touch events
    
      const handle_mousedown = () => {
        let start = Date.now();
    
        const timeout = setTimeout(() => {
          node.dispatchEvent(new CustomEvent('longpress'));
        }, threshold);
    
        const cancel = () => {
          clearTimeout(timeout);
          node.removeEventListener('mousemove', cancel);
          node.removeEventListener('mouseup', cancel);
        };
    
        node.addEventListener('mousemove', cancel);
        node.addEventListener('mouseup', cancel);
      }
    
      node.addEventListener('mousedown', handle_mousedown);
    
      return {
        destroy() {
          node.removeEventListener('mousedown', handle_mousedown);
        }
      };
    }
    

    The advantage of this approach is that you've separated the definition of 'longpress' from the thing that handles it, so the addImage/node.innerHTML logic can be cleanly separated, and you can re-use the action elsewhere in your app.

    Full demo, including passing a parameter to the action: https://svelte.dev/repl/f34b6159667247e6b6abb5142b276483?version=3.6.3