react.js 的拖放支持触摸功能

问题描述 投票:0回答:4

如何为Facebook的react.js实现支持触摸事件的拖放?

有几个关于react.js 拖放的questions、articleslibraries,但它们似乎都没有提到触摸事件,而且演示也没有在我的手机上运行。

总的来说,我想知道什么是最简单的:尝试使用现有的 d&d 库来实现这一点,这些库已经支持触摸,但可能需要一些工作才能与 React 正确配合。或者尝试使用任何 React d&d 示例,并使它们与触摸一起工作(看到这个问题,这可能不是微不足道的?)

drag-and-drop touch reactjs
4个回答
8
投票

反应运动(带有触摸事件)

我们尝试了“react-motion”来拖动列表中的项目。如果项目超过 15-20 个,它就会变得非常滞后。 (但是对于小列表,效果很好,就像这个demo)。请注意,移动设备比台式机慢得多。

关于react-motion的重要说明:在测试动画性能时不要忘记使用生产模式!

react-dnd(带触摸事件)

第二个选项是“react-dnd”。这是一个很棒的图书馆。它的级别较低,但是很容易理解如何使用它。但起初,“react-dnd”不是我们的选择,因为不支持触摸事件。

后来,当雅虎发布了react-dnd-touch-backend时,我们决定将我们的应用程序从“react-motion”切换到“react-dnd”。这解决了我们所有的性能问题。我们列出了 50-70 项,它按预期工作。

雅虎做了非常好的工作,该解决方案适用于我们的生产应用程序。


6
投票

你已经提到了react-dnd,我制作了PR,为触摸设备启用了dnd,所以你可以尝试一下


2
投票

我还没有找到任何答案。接受的答案并不是真正的答案,但它指向一个 github 库。我将尝试仅使用 React 在这里包含一个完整的答案。

这样,代码应该是不言自明的,但是提前说几句话。我们需要使用大量状态变量来保持渲染之间的状态,否则任何变量都会被重置。为了使过渡平滑,我使用 useEffect 钩子在渲染完成后更新位置。我在codesandbox中对此进行了测试,我在here中添加了链接,供任何人编辑代码并使用它,只需分叉即可。它适用于 MS Surface Book2 Pro 和安卓。 iPhone IOS 存在格式化问题。适用于 Safari 和 Chrome。如果有人修复它那就太好了。现在我已经拥有了我需要的东西并声称成功了。

以下是codesandbox.io中src下的文件:

App.js

import "./styles/index.pcss";

import "./styles/tailwind-pre-build.css";
import Photos from "./Photos.js";

export default function App() {
  return (
    <>
      <div className="flow-root bg-green-200">
        <div className="my-4 bg-blue-100 mb-20">
          Drag and Drop with touch screens
        </div>
      </div>
      <div className="flow-root bg-red-200">
        <div className="bg-blue-100">
          <Photos />
        </div>
      </div>
    </>
  );
}

照片.js:

import React, { useState } from "react";

import "./styles/index.pcss";
import Image from "./image";

export default function Photos() {
  const [styleForNumber, setStyleForNumber] = useState({
    position: "relative",
    width: "58px",
    height: "58px"
  });

  const photosArray = [
    "https://spinelli.io/noderestshop/uploads/G.1natalie.1642116451444",
    "https://spinelli.io/noderestshop/uploads/G.2natalie.1642116452437",
    "https://spinelli.io/noderestshop/uploads/G.3natalie.1642116453418",
    "https://spinelli.io/noderestshop/uploads/G.4natalie.1642116454396",
    "https://spinelli.io/noderestshop/uploads/G.5natalie.1642116455384",
    "https://spinelli.io/noderestshop/uploads/G.6natalie.1642116456410",
    "https://spinelli.io/noderestshop/uploads/G.7natalie.1642116457466",
    "https://spinelli.io/noderestshop/uploads/G.8natalie.1642116458535",
    "https://spinelli.io/noderestshop/uploads/G.0natalie.1642116228246"
  ];

  return (
    <>
      <div
        className="w-1/2 bg-green-200"
        style={{
          display: "grid",
          gridTemplateColumns: "[first] 60px [second] 60px [third] 60px",
          gridTemplateRows: "60px 60px 60px",
          rowGap: "10px",
          columnGap: "20px",
          position: "relative",
          justifyContent: "center",
          placeItems: "center"
        }}
      >
        {photosArray.map((photo, i) => (
          <div
            className="relative z-1 h-full w-full flex flex-wrap content-center touch-none"
            key={i}
          >
            <div className="contents">
              <Image photo={photo} i={i} />
            </div>
          </div>
        ))}
      </div>
    </>
  );
}

图像.js:

import React, { useRef, useState, useEffect } from "react";

import "./styles/index.pcss";

export default function Image({ photo, i }) {
  const imgRef = useRef();
  const [top, setTop] = useState(0);
  const [left, setLeft] = useState(0);
  const [drag, setDrag] = useState(false);
  const [styleForImg, setStyleForImg] = useState({
    position: "absolute",
    width: "58px",
    height: "58px"
  });
  const [offsetTop, setOffsetTop] = useState(-40);
  const [offsetLeft, setOffsetLeft] = useState(0);
  const [xAtTouchPointStart, setXAtTouchPointStart] = useState(0);
  const [yAtTouchPointStart, setYAtTouchPointStart] = useState(0);

  useEffect(() => {
    if (drag) {
      setStyleForImg({
        position: "relative",
        width: "58px",
        height: "58px",
        top: top,
        left: left
      });
    } else {
      setStyleForImg({
        position: "relative",
        width: "58px",
        height: "58px"
      });
    }
    console.log("style: ", styleForImg);
  }, [drag, top, left]);

  const handleTouchStart = (e, i) => {
    e.preventDefault();
    let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
    let touch = evt.touches[0] || evt.changedTouches[0];
    const x = +touch.pageX;
    const y = +touch.pageY;
    console.log(
      "onTouchStart coordinates of icon @ start: X: " + x + " | Y: " + y
    );
    console.log("dragged from position n = ", i + 1);
    // get the mouse cursor position at startup:
    setXAtTouchPointStart(x);
    setYAtTouchPointStart(y);
    setDrag(true);
  };

  const handleTouchEnd = (e) => {
    // if (process.env.NODE_ENV === 'debug5' || process.env.NODE_ENV === 'development') {
    e.preventDefault();
    setDrag(false);
    console.log(
      new Date(),
      "onTouchEnd event, coordinates of icon @ end: X: " +
        e.changedTouches[0]?.clientX +
        " | Y: " +
        e.changedTouches[0]?.clientY +
        " | top: " +
        top +
        " | left: " +
        left
    );
  };

  const handleElementDrag = (e) => {
    e = e || window.event;
    e.preventDefault();
    let x = 0;
    let y = 0;

    //Get touch or click position
    //https://stackoverflow.com/a/41993300/5078983
    if (
      e.type === "touchstart" ||
      e.type === "touchmove" ||
      e.type === "touchend" ||
      e.type === "touchcancel"
    ) {
      let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
      let touch = evt.touches[0] || evt.changedTouches[0];
      x = +touch.pageX; // X Coordinate relative to the viewport of the touch point
      y = +touch.pageY; // same for Y
    } else if (
      e.type === "mousedown" ||
      e.type === "mouseup" ||
      e.type === "mousemove" ||
      e.type === "mouseover" ||
      e.type === "mouseout" ||
      e.type === "mouseenter" ||
      e.type === "mouseleave"
    ) {
      x = +e.clientX;
      y = +e.clientY;
    }
    console.log("x: ", x, "y: ", y);
    // calculate the new cursor position:
    const xRelativeToStart = x - xAtTouchPointStart;
    console.log(
      "xRel = ",
      x,
      " - ",
      xAtTouchPointStart,
      " = ",
      xRelativeToStart
    );
    const yRelativeToStart = y - yAtTouchPointStart;
    console.log(
      "yRel = ",
      y,
      " - ",
      yAtTouchPointStart,
      " = ",
      yRelativeToStart
    );
    // setXAtTouchPointStart(x); // Reseting relative point to current touch point
    // setYAtTouchPointStart(y);
    // set the element's new position:
    setTop(yRelativeToStart + "px");
    setLeft(xRelativeToStart + "px");
    console.log("top: ", yRelativeToStart + "px");
    console.log("Left: ", xRelativeToStart + "px");
  };

  const handleDragEnd = (e) => {
    // if (process.env.NODE_ENV === 'debug5' || process.env.NODE_ENV === 'development') {
    console.log(
      new Date(),
      "Coordinates of icon @ end X: " + e.clientX + " | Y: " + e.clientY
    );
  };

  const handleDragStart = (e, i) => {
    // From https://stackoverflow.com/a/69109382/15355839
    e.stopPropagation(); // let child take the drag
    e.dataTransfer.dropEffect = "move";
    e.dataTransfer.effectAllowed = "move";
    console.log(
      "Coordinates of icon @ start: X: " + e.clientX + " | Y: " + e.clientY
    );
    // console.log ('event: ', e)
    console.log("dragged from position n = ", i + 1);
  };
  return (
    <img
      ref={imgRef}
      className="hover:border-none border-4 border-solid border-green-600 mb-4"
      src={photo}
      alt="placeholder"
      style={styleForImg}
      onDragStart={(e) => handleDragStart(e, i)}
      onDragEnd={handleDragEnd}
      onTouchStart={(e) => handleTouchStart(e, i)}
      onTouchEnd={handleTouchEnd}
      onTouchMove={handleElementDrag}
    ></img>
  );
}

index.js:

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import "./styles/index.pcss";

import App from "./App";

const root = document.getElementById("root");
ReactDOM.render(
  <StrictMode>
    <App />
  </StrictMode>,
  root
);

样式.css:

.Main {
  font-family: sans-serif;
  text-align: center;
}

/styles/index.pcss:

@tailwind base;

@tailwind components;

@tailwind utilities;

我无法让顺风网格工作,所以我使用了实际的 css 内联样式。不知道为什么他们没有在codesandbox中。


0
投票

Dnd 套件 有四个不同的传感器来检测用户输入:

  1. 指针
  2. 鼠标
  3. 触摸
  4. 键盘

您可以单独或组合使用它们。检查有关触摸传感器的文档,您应该可以开始了。

您还可以通过this示例来实验它的平滑程度。

© www.soinside.com 2019 - 2024. All rights reserved.