首页 > 解决方案 > 具有可滚动内容时无法在触摸设备上滚动(react-use-gesture)

问题描述

我正在使用 react-use-gesture 的 useDrag 钩子来实现以下动画(在章节之间切换),但它不适用于没有touch-action: none;. 我有可滚动的内容,如果我设置touch-action:none滚动在触摸设备上不起作用,并且如果我设置touch-action: unset滚动有效但动画不起作用。

关于如何在触摸设备上获得以下动画的任何想法?目前,动画适用于非触摸设备。

import clamp from "lodash-es/clamp";
import React, { useRef, useState } from "react";
import { animated, useSprings } from "react-spring";
import { useDrag } from "react-use-gesture";
import chapters from "./chapters";
import "./styles.css";

export default function App() {
  const index = useRef(0);
  const [chapterScrollState, setChapterScrollState] = useState(
    chapters.map(() => -1)
  ); // -1 => top, 0 => between, 1 => bottom
  const [props, set] = useSprings(chapters.length, (i) => ({
    y: i * window.innerHeight,
    scale: 1,
    display: "block"
  }));
  const bind = useDrag(
    ({
      active,
      movement: [mx, my],
      direction: [xDir, yDir],
      distance,
      cancel
    }) => {
      if (chapterScrollState[index.current] === yDir) {
        cancel();
      }

      if (active && distance > window.innerHeight / 2)
        cancel(
          (index.current = clamp(
            index.current + (yDir > 0 ? -1 : 1),
            0,
            chapters.length - 1
          ))
        );
      set((i) => {
        if (i < index.current - 1 || i > index.current + 1)
          return { display: "none" };
        const y = (i - index.current) * window.innerHeight + (active ? my : 0);
        const scale = active ? 1 - distance / window.innerHeight / 2 : 1;
        return { y, scale, display: "block" };
      });
    }
  );

  const onScroll = (e, i) => {
    let scrollState = 0;
    if (
      Math.round(e.target.scrollTop) ===
      e.target.scrollHeight - e.target.offsetHeight
    ) {
      // bottom
      scrollState = 1;
    } else if (e.target.scrollTop === 0) {
      // top
      scrollState = -1;
    }
    setChapterScrollState((prev) => {
      let newArr = [...prev];
      newArr[i] = scrollState;
      return newArr;
    });
  };

  return props.map(({ y, display, scale }, i) => {
    return (
      <animated.div {...bind()} key={i} style={{ display, y }}>
        <animated.div
          className="animatedChapter"
          onScroll={(e) => onScroll(e, i)}
          style={{
            scale
          }}
        >
          <div className="chapter">{chapters[i].content}</div>
        </animated.div>
      </animated.div>
    );
  });
}

* {
  box-sizing: border-box;
}

html,
body {
  overscroll-behavior-y: contain;
  margin: 0;
  padding: 0;
  height: 100%;
  width: 100%;
  user-select: none;
  font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir,
    helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif;
  position: fixed;
  overflow: hidden;
}

#root {
  position: fixed;
  overflow: hidden;
  width: 100%;
  height: 100%;
}

#root > div {
  position: absolute;
  width: 100vw;
  height: 100vh;
  will-change: transform;
}

#root > div > div {
  overflow-y: auto;
  touch-action: none;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
  width: 100%;
  height: 100%;
  will-change: transform;
  box-shadow: 0 62.5px 125px -25px rgba(50, 50, 73, 0.5),
    0 37.5px 75px -37.5px rgba(0, 0, 0, 0.6);
}

.chapter {
  padding: 30px;
}

代码沙盒链接

动画 GIF

标签: javascriptcssreactjsreact-spring

解决方案


推荐阅读