最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Scroll events unintentionally changing Material UI slider values - Stack Overflow

programmeradmin1浏览0评论

I have a React application that is utilizing Material UI. The application has slider ponents implemented in a lot of places (). When using a touch screen device, I am unintentionally impacting slider ponents (the value is getting changed) while trying to scroll up or down the page. This does not happen with other page ponents, I suspect this is because sliders do (and should) respond to swipe events.

Material UI has documentation that implies a way to discern between a "scroll" and a "swipe" (.html#types-of-gestures). Is there a way for me to indicate to my slider ponents that a "scroll" should be ignored. Or, can I discern between a vertical or horizontal swipe, telling the slider to ignore vertical swipes?

I have a React application that is utilizing Material UI. The application has slider ponents implemented in a lot of places (https://material.io/ponents/sliders). When using a touch screen device, I am unintentionally impacting slider ponents (the value is getting changed) while trying to scroll up or down the page. This does not happen with other page ponents, I suspect this is because sliders do (and should) respond to swipe events.

Material UI has documentation that implies a way to discern between a "scroll" and a "swipe" (https://material.io/design/interaction/gestures.html#types-of-gestures). Is there a way for me to indicate to my slider ponents that a "scroll" should be ignored. Or, can I discern between a vertical or horizontal swipe, telling the slider to ignore vertical swipes?

Share Improve this question asked Nov 10, 2020 at 20:48 Jason GrossJason Gross 3101 silver badge11 bronze badges 3
  • I have the same question! – mvaldetaro Commented Nov 23, 2020 at 20:07
  • 2 Supply code example of your current usage for more help. – morganney Commented Sep 17, 2021 at 13:31
  • Does the Material UI page you linked to on sliders have the same undesired effect of changing the slider values as you scroll up and down on your mobile device? If not, then it is something with your implementation which you haven't shown. – morganney Commented Sep 17, 2021 at 17:08
Add a ment  | 

2 Answers 2

Reset to default 3

I have e up with a fairly elegant solution, I believe, which allows the user to scroll if their scroll position begins on the track but not on the thumbs. This replicates the native HTML range input so I feel that this is the best solution.

There's two parts to this

Step 1, allow touch-action on the slider root element as it is disabled by default and prevents the user from starting a scroll on the slider

const useStyles = makeStyles({
  sliderRoot: {
    touchAction: "auto"
  }
});

return (
  <Slider
    classes={{
      root: classes.sliderRoot
    }}
    ...
  />

Step 2, stop propagation on the root element with a ref

const ref = useRef(null);

useEffect(() => {
  if (ref.current) {
    ref.current.addEventListener(
      "touchstart",
      (e) => {
        const isThumb = e.target?.dataset.index;

        if (!isThumb) {
          e.stopPropagation();
        }
      },
      { capture: true }
    );
  }
});

return (
  <Slider
    ref={ref}
    ...
  />

And here is a fully working demo on Codesandbox.

There is not going to be a pletely clean solution to this other than telling your users to watch their finger placement when scrolling on mobile devices.

Using a controlled Slider, one approach would be to listen to the touchstart event and record the current pageY on the first changedTouches object. Then pare that coordinate to the pageY on the onChangeCommited event handler for the corresponding touchmove event. If the difference between the two coordinates is larger than some predefined range, then do not update the Slider value.

Inside your ponent using the Slider:

  const delta = 50
  const sliderRef = useRef(null)
  const [value, setValue] = useState(0) // Or from some prop
  const [touchStart, setTouchStart] = useState(0)
  const debouncedHandler = useMemo(() => {
    // Using lodash.debounce
    return debounce((evt, value) => {
      // If it is a mouse event then just update value as usual
      if (evt instanceof MouseEvent) {
        setValue(value)
      }
    }, 25)
  }, [])

  useLayoutEffect(() => {
    if (sliderRef.current) {
      sliderRef.current.addEventListener('touchstart', evt => {
        setTouchStart(evt.changedTouches[0].pageY)
      })
    }
  }, [])

  return (
    <Slider
      value={value}
      ref={sliderRef}
      onChange={debouncedHandler}
      onChangeCommitted={(evt, value) => {
        if (evt instanceof TouchEvent) {
          if (Math.abs(touchStart - evt.changedTouches[0].pageY) < delta) {
            setValue(value)
          }
        } else {
          setValue(value)
        }
      }}
    />
  )

This will prevent the Slider from changing value on TouchEvent when the difference between the starting y-coordinate and the ending y-coordinate is larger than delta. Adjust delta to whatever value you like. The tradeoff is that you will not get as smooth of a transition when adjusting the Slider with a normal MouseEvent (or TouchEvent within the predefined range).

See the jsFiddle.

Or npm i mui-scrollable-slider-hook and use it like

  import Slider from '@mui/material/Slider'
  import { useMuiScrollableSlider } from 'mui-scrollable-slider-hook'

  const { ref, value, onChange, onChangeCommitted } = useMuiScrollableSlider()

  return <Slider ref={ref} value={value} onChange={onChange} onChangeCommitted={onChangeCommitted} />

An example of using mui-scrollable-slider-hook on codesandbox.

发布评论

评论列表(0)

  1. 暂无评论