react long press event

前端 未结 9 1178
一个人的身影
一个人的身影 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:31

    Here's a component that provides onClick and onHold events - adapt as needed...

    CodeSandbox: https://codesandbox.io/s/hold-press-event-r8q9w

    Usage:

    import React from 'react'
    import Holdable from './holdable'
    
    function App() {
    
      function onClick(evt) {
        alert('click ' + evt.currentTarget.id)
      }
    
      function onHold(evt) {
        alert('hold ' + evt.currentTarget.id)
      }
    
      const ids = 'Label1,Label2,Label3'.split(',')
    
      return (
        <div className="App">
          {ids.map(id => (
            <Holdable
              onClick={onClick}
              onHold={onHold}
              id={id}
              key={id}
            >
              {id}
            </Holdable>
          ))}
        </div>
      )
    }
    

    holdable.jsx:

    import React from 'react'
    
    const holdTime = 500 // ms
    const holdDistance = 3**2 // pixels squared
    
    export default function Holdable({id, onClick, onHold, children}) {
    
      const [timer, setTimer] = React.useState(null)
      const [pos, setPos] = React.useState([0,0])
    
      function onPointerDown(evt) {
        setPos([evt.clientX, evt.clientY]) // save position for later
        const event = { ...evt } // convert synthetic event to real object
        const timeoutId = window.setTimeout(timesup.bind(null, event), holdTime)
        setTimer(timeoutId)
      }
    
      function onPointerUp(evt) {
        if (timer) {
          window.clearTimeout(timer)
          setTimer(null)
          onClick(evt)
        }
      }
    
      function onPointerMove(evt) {
        // cancel hold operation if moved too much
        if (timer) {
          const d = (evt.clientX - pos[0])**2 + (evt.clientY - pos[1])**2
          if (d > holdDistance) {
            setTimer(null)  
            window.clearTimeout(timer)
          }
        }
      }
    
      function timesup(evt) {
        setTimer(null)
        onHold(evt)
      }
    
      return (
        <div
          onPointerDown={onPointerDown}
          onPointerUp={onPointerUp}
          onPointerMove={onPointerMove}
          id={id}
        >
          {children}
        </div>
      )
    }
    

    Note: this doesn't work with Safari yet - pointer events are coming in v13 though - https://caniuse.com/#feat=pointer

    0 讨论(0)
  • 2020-12-13 02:32

    Brian's solution allows you to pass params to the children which I think is not doable with the Hook. Still, if I may suggest a bit cleaner solution for most common case where you want to add onHold behavior to a single component and you also want to be able to change the onHold timeout.

    Material-UI example with Chip component:

    'use strict';
    
    const {
      Chip
    } = MaterialUI
    
    function ChipHoldable({
      onClick = () => {},
      onHold = () => {},
      hold = 500,
      ...props
    }) {
      const [timer, setTimer] = React.useState(null);
    
      function onPointerDown(evt) {
        const event = { ...evt
        }; // convert synthetic event to real object
        const timeoutId = window.setTimeout(timesup.bind(null, event), hold);
        setTimer(timeoutId);
      }
    
      function onPointerUp(evt) {
        if (timer) {
          window.clearTimeout(timer);
          setTimer(null);
          onClick(evt);
        }
      }
    
      const onContextMenu = e => e.preventDefault();
    
      const preventDefault = e => e.preventDefault(); // so that ripple effect would be triggered
    
      function timesup(evt) {
        setTimer(null);
        onHold(evt);
      }
    
      return React.createElement(Chip, {
        onPointerUp,
        onPointerDown,
        onContextMenu,
        onClick: preventDefault,
        ...props
      });
    }
    
    const App = () =>  <div> {[1,2,3,4].map(i => < ChipHoldable style={{margin:"10px"}}label = {`chip${i}`}
        onClick = {
          () => console.log(`chip ${i} clicked`)
        }
        onHold = {
          () => console.log(`chip ${i} long pressed`)
        }
        />)}
        </div>
    
    
    ReactDOM.render( <App/>, document.querySelector('#root'));
    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="UTF-8" />
    </head>
    
    <body>
      <div id="root"></div>
      <script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
      <script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
      <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
      <script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>
    </body>
    
    </html>

    0 讨论(0)
  • 2020-12-13 02:35

    An adaptation of David's solution: a React hook for when you want to repeatedly fire the event. It uses setInterval instead.

    export function useHoldPress(callback = () => {}, ms = 300) {
      const [startHoldPress, setStartHoldPress] = useState(false);
    
      useEffect(() => {
        let timerId;
        if (startHoldPress) {
          timerId = setInterval(callback, ms);
        } else {
          clearTimeout(timerId);
        }
    
        return () => {
          clearTimeout(timerId);
        };
      }, [startHoldPress]);
    
      return {
        onMouseDown: () => setStartHoldPress(true),
        onMouseUp: () => setStartHoldPress(false),
        onMouseLeave: () => setStartHoldPress(false),
        onTouchStart: () => setStartHoldPress(true),
        onTouchEnd: () => setStartHoldPress(false)
      };
    }
    
    0 讨论(0)
  • 2020-12-13 02:39

    With hooks in react 16.8 you could rewrite class with functions and hooks.

    import { useState, useEffect } from 'react';
    
    export default function useLongPress(callback = () => {}, ms = 300) {
      const [startLongPress, setStartLongPress] = useState(false);
    
      useEffect(() => {
        let timerId;
        if (startLongPress) {
          timerId = setTimeout(callback, ms);
        } else {
          clearTimeout(timerId);
        }
    
        return () => {
          clearTimeout(timerId);
        };
      }, [callback, ms, startLongPress]);
    
      return {
        onMouseDown: () => setStartLongPress(true),
        onMouseUp: () => setStartLongPress(false),
        onMouseLeave: () => setStartLongPress(false),
        onTouchStart: () => setStartLongPress(true),
        onTouchEnd: () => setStartLongPress(false),
      };
    }
    
    import useLongPress from './useLongPress';
    
    function MyComponent (props) {
      const backspaceLongPress = useLongPress(props.longPressBackspaceCallback, 500);
    
      return (
        <Page>
          <Button {...backspaceLongPress}>
            Click me
          </Button>
        </Page>
      );
    };
    
    
    0 讨论(0)
  • 2020-12-13 02:46

    Nice hook! But I would like make a small improvement. Using useCallback to wrap event handlers. This ensures these will not changed on every render.

    import { useState, useEffect, useCallback } from 'react';
    
    export default function useLongPress(callback = () => {}, ms = 300) {
      const [startLongPress, setStartLongPress] = useState(false);
    
      useEffect(() => {
        let timerId;
        if (startLongPress) {
          timerId = setTimeout(callback, ms);
        } else {
          clearTimeout(timerId);
        }
    
        return () => {
          clearTimeout(timerId);
        };
      }, [callback, ms, startLongPress]);
    
      const start = useCallback(() => {
        setStartLongPress(true);
      }, []);
      const stop = useCallback(() => {
        setStartLongPress(false);
      }, []);
    
      return {
        onMouseDown: start,
        onMouseUp: stop,
        onMouseLeave: stop,
        onTouchStart: start,
        onTouchEnd: stop,
      };
    }
    
    0 讨论(0)
  • 2020-12-13 02:46

    Ionic React LongPress Example I use it with Ionic React, it works well.

    import React, {useState}  from 'react';
    import { Route, Redirect } from 'react-router';
    
    interface MainTabsProps { }
    const MainTabs: React.FC<MainTabsProps> = () => {
    
    // timeout id  
    var initial: any;
    
    // setstate
    const [start, setStart] = useState(false);
    
    const handleButtonPress = () => {
      initial = setTimeout(() => {
        setStart(true); // start long button          
        console.log('long press button');
        }, 1500);
    }
    
    const handleButtonRelease = () => {
      setStart(false); // stop long press   
      clearTimeout(initial); // clear timeout  
      if(start===false) { // is click
        console.log('click button');
      }  
    }
    
      return (
        <IonPage>
          <IonHeader>
            <IonTitle>Ionic React LongPress</IonTitle>
          </IonHeader>    
          <IonContent className="ion-padding">
            <IonButton expand="block"  
              onMouseDown={handleButtonPress} 
              onMouseUp={handleButtonRelease} >LongPress</IonButton>    
          </IonContent>
        </IonPage>
      );
    };
    
    export default MainTabs;
    
    0 讨论(0)
提交回复
热议问题