react long press event

前端 未结 9 1179
一个人的身影
一个人的身影 2020-12-13 02:01

Is there a way to add long press event in react-web application?

I have list of addresses. On long press on any address, I want to

相关标签:
9条回答
  • 2020-12-13 02:50

    I've created a codesandbox with a hook to handle long press and click. Basically, on mouse down, touch start events, a timer is created with setTimeout. When the provided time elapses, it triggers long press. On mouse up, mouse leave, touchend, etc, the timer is cleared.

    useLongPress.js

    import { useCallback, useRef, useState } from "react";
    
    const useLongPress = (
        onLongPress,
        onClick,
        { shouldPreventDefault = true, delay = 300 } = {}
        ) => {
        const [longPressTriggered, setLongPressTriggered] = useState(false);
        const timeout = useRef();
        const target = useRef();
    
        const start = useCallback(
            event => {
                if (shouldPreventDefault && event.target) {
                        event.target.addEventListener("touchend", preventDefault, {
                        passive: false
                    });
                    target.current = event.target;
                }
                timeout.current = setTimeout(() => {
                    onLongPress(event);
                    setLongPressTriggered(true);
                }, delay);
            },
            [onLongPress, delay, shouldPreventDefault]
        );
    
        const clear = useCallback(
            (event, shouldTriggerClick = true) => {
                timeout.current && clearTimeout(timeout.current);
                shouldTriggerClick && !longPressTriggered && onClick();
                setLongPressTriggered(false);
                if (shouldPreventDefault && target.current) {
                    target.current.removeEventListener("touchend", preventDefault);
                }
            },
            [shouldPreventDefault, onClick, longPressTriggered]
        );
    
        return {
            onMouseDown: e => start(e),
            onTouchStart: e => start(e),
            onMouseUp: e => clear(e),
            onMouseLeave: e => clear(e, false),
            onTouchEnd: e => clear(e)
        };
    };
    
    const isTouchEvent = event => {
    return "touches" in event;
    };
    
    const preventDefault = event => {
    if (!isTouchEvent(event)) return;
    
    if (event.touches.length < 2 && event.preventDefault) {
        event.preventDefault();
    }
    };
    
    export default useLongPress;
    

    To use the hook, App.js

    import useLongPress from "./useLongPress";
    
    export default function App() {
    
        const onLongPress = () => {
            console.log('longpress is triggered');
        };
    
        const onClick = () => {
            console.log('click is triggered')
        }
    
        const defaultOptions = {
            shouldPreventDefault: true,
            delay: 500,
        };
        const longPressEvent = useLongPress(onLongPress, onClick, defaultOptions);
    
        return (
            <div className="App">
                <button {...longPressEvent}>use  Loooong  Press</button>
            </div>
        );
    }
    

    Older answer for class components:

    You can use MouseDown, MouseUp, TouchStart, TouchEnd events to control timers that can act as a long press event. Check out the code below

    class App extends Component {
      constructor() {
        super()
        this.handleButtonPress = this.handleButtonPress.bind(this)
        this.handleButtonRelease = this.handleButtonRelease.bind(this)
      }
      handleButtonPress () {
        this.buttonPressTimer = setTimeout(() => alert('long press activated'), 1500);
      }
      
      handleButtonRelease () {
        clearTimeout(this.buttonPressTimer);
      }
    
      render() {
        return (
          <div 
              onTouchStart={this.handleButtonPress} 
              onTouchEnd={this.handleButtonRelease} 
              onMouseDown={this.handleButtonPress} 
              onMouseUp={this.handleButtonRelease} 
              onMouseLeave={this.handleButtonRelease}>
            Button
          </div>
        );
      }
    }
    
    0 讨论(0)
  • 2020-12-13 02:53

    Based on @Sublime me comment above about avoiding multiple re-renders, my version doesn't use anything that triggers renders:

    export function useLongPress({
      onClick = () => {},
      onLongPress = () => {},
      ms = 300,
    } = {}) {
      const timerRef = useRef(false);
      const eventRef = useRef({});
    
      const callback = useCallback(() => {
        onLongPress(eventRef.current);
        eventRef.current = {};
        timerRef.current = false;
      }, [onLongPress]);
    
      const start = useCallback(
        (ev) => {
          ev.persist();
          eventRef.current = ev;
          timerRef.current = setTimeout(callback, ms);
        },
        [callback, ms]
      );
    
      const stop = useCallback(
        (ev) => {
          ev.persist();
          eventRef.current = ev;
          if (timerRef.current) {
            clearTimeout(timerRef.current);
            onClick(eventRef.current);
            timerRef.current = false;
            eventRef.current = {};
          }
        },
        [onClick]
      );
    
      return useMemo(
        () => ({
          onMouseDown: start,
          onMouseUp: stop,
          onMouseLeave: stop,
          onTouchStart: start,
          onTouchEnd: stop,
        }),
        [start, stop]
      );
    }
    

    It also provides both onLongPress and onClick and passes on the event object received.

    Usage is mostly as described earlier, except arguments are now passed in an object, all are optional:

      const longPressProps = useLongPress({
        onClick: (ev) => console.log('on click', ev.button, ev.shiftKey),
        onLongPress: (ev) => console.log('on long press', ev.button, ev.shiftKey),
      });
    
    // and later:
      return (<button {...longPressProps}>click me</button>);
    
    0 讨论(0)
  • 2020-12-13 02:54

    Just wanted to point out that hooks aren't a great solution here since you can't use them in a call back.

    for example, if you wanted to add long press to a number of elements:

    items.map(item => <button {...useLongPress(() => handle(item))}>{item}</button>)
    

    gets you:

    ... React Hooks must be called in a React function component or a custom React Hook function

    you could however use vanilla JS:

    export default function longPressEvents(callback, ms = 500) {
      let timeout = null
    
      const start = () => timeout = setTimeout(callback, ms)
      const stop = () => timeout && window.clearTimeout(timeout)
    
      return callback ? {
        onTouchStart: start,
        onTouchMove: stop,
        onTouchEnd: stop,
      } : {}
    }
    

    then:

    items.map(item => <button { ...longPressEvents(() => handle(item)) }>{item}</button>)
    

    demo: https://codesandbox.io/s/long-press-hook-like-oru24?file=/src/App.js

    just be aware that longPressEvents will run every render. Probably not a big deal, but something to keep in mind.

    0 讨论(0)
提交回复
热议问题