├── .eslintignore ├── .prettierignore ├── src ├── react-app-env.d.ts ├── components │ ├── Chess │ │ ├── type.ts │ │ ├── index.scss │ │ ├── utils.ts │ │ ├── square.tsx │ │ ├── index.tsx │ │ ├── knight.tsx │ │ ├── board.tsx │ │ └── boardSquare.tsx │ ├── listSort │ │ ├── type.ts │ │ ├── dragSquare.tsx │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── box.tsx │ │ ├── dropSquare.tsx │ │ └── card.tsx │ ├── arbitrarilyDrag │ │ ├── type.ts │ │ ├── Classification.tsx │ │ ├── index.scss │ │ ├── word.tsx │ │ └── index.tsx │ ├── App.scss │ ├── cardAssemble │ │ ├── dragSquare.tsx │ │ ├── dropSquare.tsx │ │ ├── dropPlace.tsx │ │ ├── dragCard.tsx │ │ ├── index.scss │ │ └── index.tsx │ ├── base │ │ └── nav │ │ │ ├── index.scss │ │ │ └── index.tsx │ ├── multiDrag │ │ ├── Drop.tsx │ │ ├── index.tsx │ │ ├── index.scss │ │ └── Drag.tsx │ ├── App.tsx │ ├── dragPreviewImg │ │ ├── index.tsx │ │ ├── index.scss │ │ └── dragPreviewImg.tsx │ ├── dragPreviewDom │ │ ├── index.scss │ │ ├── index.tsx │ │ ├── dragPreviewDom.tsx │ │ └── customDragerLayer.tsx │ ├── cardSort │ │ ├── index.scss │ │ ├── card.tsx │ │ └── index.tsx │ └── routes.ts ├── assets │ ├── 任意拖拽.gif │ ├── 卡片拼图.gif │ ├── 批量拖拽.gif │ ├── apple.png │ ├── 拖拽卡片排序.gif │ └── 预置卡片排序.gif ├── setupTests.ts ├── reportWebVitals.ts └── index.tsx ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── .prettierrc.js ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json └── .eslintrc /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | scripts/ 4 | config/ 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | scripts/ 4 | config/ 5 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/components/Chess/type.ts: -------------------------------------------------------------------------------- 1 | export const ItemTypes = { 2 | KNIGHT: 'knight' 3 | } -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/assets/任意拖拽.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/任意拖拽.gif -------------------------------------------------------------------------------- /src/assets/卡片拼图.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/卡片拼图.gif -------------------------------------------------------------------------------- /src/assets/批量拖拽.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/批量拖拽.gif -------------------------------------------------------------------------------- /src/assets/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/apple.png -------------------------------------------------------------------------------- /src/assets/拖拽卡片排序.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/拖拽卡片排序.gif -------------------------------------------------------------------------------- /src/assets/预置卡片排序.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdolescentJou/react-dnd-demo/HEAD/src/assets/预置卡片排序.gif -------------------------------------------------------------------------------- /src/components/listSort/type.ts: -------------------------------------------------------------------------------- 1 | enum ItemTypes { 2 | Card = 'card' 3 | } 4 | 5 | export default ItemTypes; -------------------------------------------------------------------------------- /src/components/arbitrarilyDrag/type.ts: -------------------------------------------------------------------------------- 1 | export const WORD_TYPE = { 2 | adj: 'adj', 3 | verb: 'verb', 4 | }; 5 | -------------------------------------------------------------------------------- /src/components/App.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | .app { 7 | width: 100%; 8 | height: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Chess/index.scss: -------------------------------------------------------------------------------- 1 | .chess-container { 2 | width: 400px; 3 | height: 400px; 4 | margin: auto; 5 | border: 1px solid black; 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | //此处的规则供参考,其中多半其实都是默认值,可以根据个人习惯改写 2 | module.exports = { 3 | printWidth: 80, //单行长度 4 | tabWidth: 2, //缩进长度 5 | useTabs: false, //使用空格代替tab缩进 6 | semi: true, //句末使用分号 7 | singleQuote: true, //使用单引号 8 | }; 9 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/components/Chess/utils.ts: -------------------------------------------------------------------------------- 1 | export function canMoveKnight(toX: number, toY: number, knightPosition: [number, number]): boolean { 2 | const [x, y] = knightPosition; 3 | 4 | const dx = toX - x; 5 | const dy = toY - y; 6 | 7 | return (Math.abs(dx) === 2 && Math.abs(dy) === 1) || (Math.abs(dx) === 1 && Math.abs(dy) === 2); 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/components/cardAssemble/dragSquare.tsx: -------------------------------------------------------------------------------- 1 | import DragCard from './dragCard'; 2 | 3 | export default function DragSquare({ dragCardList, updateDragAndDrop }: any) { 4 | return ( 5 |
6 | {dragCardList.map((each: any, index: any) => ( 7 | 8 | ))} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/cardAssemble/dropSquare.tsx: -------------------------------------------------------------------------------- 1 | import DropPlace from './dropPlace'; 2 | export default function DropSquare({ dropCardList, updateDragAndDrop }: any) { 3 | 4 | return ( 5 |
6 | {dropCardList.map((each: any, index: number) => ( 7 | 8 | ))} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Chess/square.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Square({ black, children }: any) { 4 | const fill = black ? 'black' : 'white'; 5 | const stroke = black ? 'white' : 'black'; 6 | 7 | return ( 8 |
16 | {children} 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/components/base/nav/index.scss: -------------------------------------------------------------------------------- 1 | .nav-container { 2 | height: 80px; 3 | width: 100%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | margin: 30px 0; 8 | 9 | .link_btn { 10 | padding: 30px; 11 | border: 1px dashed black; 12 | font-size: 18px; 13 | color: #4f6f95; 14 | 15 | &--active { 16 | background-color: #0d1320; 17 | color: white; 18 | opacity: 0.3; 19 | } 20 | } 21 | 22 | .link_btn + .link_btn { 23 | margin-left: 25px; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/listSort/dragSquare.tsx: -------------------------------------------------------------------------------- 1 | import Box from './box'; 2 | 3 | export default function DragSquare({ dragCardList, dropCardList, updateDragAndDrop }: any) { 4 | return ( 5 |
6 | {dragCardList.map((each: any, index: any) => ( 7 | 14 | ))} 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './components/App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); 7 | root.render( ); 8 | // If you want to start measuring performance in your app, pass a function 9 | // to log results (for example: reportWebVitals(console.log)) 10 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 11 | reportWebVitals(); 12 | -------------------------------------------------------------------------------- /src/components/multiDrag/Drop.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useDrop } from 'react-dnd'; 3 | export default function DropSquare({ dropCardList, updateDragAndDrop }: any) { 4 | const [{ canDrop }, drop] = useDrop({ 5 | accept: 'DragDropBox', 6 | 7 | canDrop: (_item, monitor) => { 8 | return true; 9 | }, 10 | collect: (monitor) => ({ 11 | canDrop: !!monitor.canDrop(), 12 | }), 13 | }); 14 | 15 | 16 | return ( 17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.scss'; 3 | import { HashRouter as Router, Route, Routes } from 'react-router-dom'; 4 | import { routes } from './routes'; 5 | import Nav from './base/nav'; 6 | 7 | function App({ ...props }) { 8 | return ( 9 |
10 | 11 |
20 | ); 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/multiDrag/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import DragPreviewDom from './Drag'; 5 | import './index.scss'; 6 | 7 | class MultiDrag extends React.Component { 8 | constructor(props: any) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | 13 | render() { 14 | return ( 15 | 16 |
17 |

批量拖拽

18 | 19 |
20 |
21 | ); 22 | } 23 | } 24 | 25 | export default MultiDrag; 26 | -------------------------------------------------------------------------------- /src/components/dragPreviewImg/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import DragPreviewImg from './dragPreviewImg'; 5 | import './index.scss'; 6 | 7 | class DragPreview extends React.Component { 8 | constructor(props: any) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | 13 | render() { 14 | return ( 15 | 16 |
17 |

拖拽图片预览

18 | 19 |
20 |
21 | ); 22 | } 23 | } 24 | 25 | export default DragPreview; 26 | -------------------------------------------------------------------------------- /src/components/dragPreviewImg/index.scss: -------------------------------------------------------------------------------- 1 | .drag-preview { 2 | width: 100%; 3 | height: 700px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .card_drop { 10 | width: 320px; 11 | height: 320px; 12 | border: 1px solid black; 13 | margin-top: 20px; 14 | } 15 | 16 | .drag-group { 17 | margin-top: 20px; 18 | display: flex; 19 | } 20 | 21 | .card_drag_box { 22 | position: relative; 23 | width: auto; 24 | height: auto; 25 | } 26 | 27 | .card_drag { 28 | padding: 24px; 29 | font-size: 18px; 30 | border: 1px solid black; 31 | position: relative; 32 | margin-top: 20px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/dragPreviewDom/index.scss: -------------------------------------------------------------------------------- 1 | .drag-preview-dom { 2 | width: 100%; 3 | height: 700px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .card_drop { 10 | width: 400px; 11 | height: 400px; 12 | border: 1px solid black; 13 | margin-top: 20px; 14 | } 15 | 16 | .drag-group { 17 | margin-top: 20px; 18 | display: flex; 19 | } 20 | 21 | .card_drag_box { 22 | position: relative; 23 | width: auto; 24 | height: auto; 25 | } 26 | 27 | .card_drag { 28 | padding: 24px; 29 | font-size: 18px; 30 | border: 1px solid black; 31 | position: relative; 32 | margin-top:40px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 关于本项目 2 | 3 | 本项目使用 react-dnd 编写了一些 demo,涵盖了多种场景,尽量应用到其全面的 API 4 | 5 | ### 启动本项目 6 | 7 | clone 下来之后 8 | 9 | - npm install 10 | - npm start/npm run build 11 | 12 | ### 用例 13 | 14 | 在 components 目录下,每个文件夹代表一个用例,启动项目之后,通过路由进行访问 15 | 16 | - multiDrag: 批量拖拽,可以选择多个元素进行拖拽 17 | ![](./src/assets/批量拖拽.gif) 18 | 19 | - word: 任意拖拽,记录坐标进行拖拽 20 | 21 | ![](./src/assets/任意拖拽.gif) 22 | 23 | - cardSort: 预置卡片排序 24 | 25 | ![](./src/assets/%E9%A2%84%E7%BD%AE%E5%8D%A1%E7%89%87%E6%8E%92%E5%BA%8F.gif) 26 | 27 | - cardAssemble: 固定碎片的卡片拼图 28 | 29 | ![](./src/assets/%E5%8D%A1%E7%89%87%E6%8B%BC%E5%9B%BE.gif) 30 | 31 | - listSort: 有顺移的列表排序 32 | 33 | ![](./src/assets/拖拽卡片排序.gif) 34 | 35 | - dragPreviewImg: 拖拽图片预览 36 | - dragPreviewDom: 拖拽自定义预览 37 | - chess: 国际象棋,官方用例 -------------------------------------------------------------------------------- /src/components/Chess/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Board from './board'; 3 | import './index.scss'; 4 | 5 | class ChessDemo extends React.Component { 6 | constructor(props: any) { 7 | super(props); 8 | this.state = { 9 | knightPosition: [2, 3], 10 | }; 11 | this.moveKnight = this.moveKnight.bind(this); 12 | } 13 | 14 | moveKnight(toX: number, toY: number) { 15 | this.setState({ knightPosition: [toX, toY] }); 16 | } 17 | 18 | render() { 19 | const { knightPosition } = this.state; 20 | return ( 21 |
22 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | export default ChessDemo; 29 | -------------------------------------------------------------------------------- /src/components/dragPreviewDom/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import DragPreviewDom from './dragPreviewDom'; 5 | import { CustomDragLayer } from './customDragerLayer'; 6 | import './index.scss'; 7 | 8 | class DragPreview extends React.Component { 9 | constructor(props: any) { 10 | super(props); 11 | this.state = {}; 12 | } 13 | 14 | render() { 15 | return ( 16 | 17 |
18 |

拖拽Dom预览

19 | 20 | 21 |
22 |
23 | ); 24 | } 25 | } 26 | 27 | export default DragPreview; 28 | -------------------------------------------------------------------------------- /src/components/dragPreviewImg/dragPreviewImg.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC, useRef, useState } from 'react'; 2 | import { useDrag, DragPreviewImage } from 'react-dnd'; 3 | import apple from '../../assets/apple.png'; 4 | 5 | const DragPreviewImg = () => { 6 | const [{ isDragging }, drag, preview] = useDrag({ 7 | type: 'DragDropBox', 8 | collect: (monitor) => ({ 9 | isDragging: monitor.isDragging(), 10 | }), 11 | }); 12 | 13 | return ( 14 | <> 15 | 16 |
23 | drag item prview an apple 24 |
25 | 26 | ); 27 | }; 28 | export default DragPreviewImg; 29 | -------------------------------------------------------------------------------- /src/components/base/nav/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useLocation, useNavigate } from 'react-router-dom'; 3 | import { routes } from '../../routes'; 4 | import './index.scss'; 5 | 6 | const Nav = () => { 7 | let navigate = useNavigate(); 8 | let location = useLocation(); 9 | const handleChangeRoute = (url: string) => { 10 | navigate(url); 11 | }; 12 | 13 | return ( 14 |
15 | {routes.map((each, index) => ( 16 |
handleChangeRoute(each.url)} 19 | key={'link' + index} 20 | > 21 | {each.title} 22 |
23 | ))} 24 |
25 | ); 26 | }; 27 | 28 | export default Nav; 29 | -------------------------------------------------------------------------------- /src/components/cardAssemble/dropPlace.tsx: -------------------------------------------------------------------------------- 1 | import { useDrop } from 'react-dnd'; 2 | import DragCard from './dragCard'; 3 | 4 | export default function DropPlace({ innerDrag, updateCardList, index, id, updateDragAndDrop }: any) { 5 | const [{ isOver }, drop] = useDrop({ 6 | accept: 'DragDropBox', 7 | drop: (item: any, monitor) => { 8 | const dragIndex = item.id; 9 | const dropIndex = id; 10 | updateDragAndDrop(dragIndex, dropIndex); 11 | }, 12 | collect: (monitor) => ({ 13 | isOver: !!monitor.isOver(), 14 | }), 15 | }); 16 | 17 | return ( 18 |
19 | {innerDrag && } 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/cardAssemble/dragCard.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC, useRef, useState } from 'react'; 2 | import { useDrag, useDrop } from 'react-dnd'; 3 | 4 | const DragCard = ({ text, id, index, updateDragAndDrop, isHovering }: any) => { 5 | const [{ isDragging }, drag] = useDrag({ 6 | type: 'DragDropBox', 7 | item: { id, index, updateDragAndDrop }, 8 | end: (item, monitor) => { 9 | if (!monitor.didDrop()) { 10 | updateDragAndDrop(id); 11 | } 12 | }, 13 | collect: (monitor) => ({ 14 | isDragging: monitor.isDragging(), 15 | }), 16 | }); 17 | 18 | return ( 19 |
26 | {text} 27 |
28 | ); 29 | }; 30 | export default DragCard; 31 | -------------------------------------------------------------------------------- /src/components/Chess/knight.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, FC } from 'react'; 2 | import { useDrag } from 'react-dnd'; 3 | import { ItemTypes } from './type'; 4 | 5 | const Knight: FC = () => { 6 | const [{ isDragging }, drag]: any = useDrag(() => ({ 7 | type: ItemTypes.KNIGHT, 8 | collect: (monitor) => ({ 9 | isDragging: !!monitor.isDragging(), 10 | }), 11 | })); 12 | return ( 13 | 29 | ♘ 30 | 31 | ); 32 | }; 33 | export default Knight; 34 | -------------------------------------------------------------------------------- /src/components/cardSort/index.scss: -------------------------------------------------------------------------------- 1 | .card-container { 2 | width: 100%; 3 | height: 700px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .card_drop_group { 10 | margin-top: 30px; 11 | height: 240px; 12 | width: 600px; 13 | padding: 20px; 14 | border: 1px solid black; 15 | } 16 | 17 | .card_drag_group { 18 | height: 240px; 19 | padding: 20px; 20 | display: flex; 21 | margin-top: 50px; 22 | border:1px solid black; 23 | 24 | .card_drag { 25 | width: 120px; 26 | height: 240px; 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | font-size: 24px; 31 | font-weight: bold; 32 | border: 1px solid black; 33 | background-color:orange; 34 | } 35 | 36 | .card_drag + .card_drag { 37 | margin-left: 20px; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/dragPreviewDom/dragPreviewDom.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useDrag } from 'react-dnd'; 3 | import { getEmptyImage } from 'react-dnd-html5-backend'; 4 | 5 | const DragPreviewDom = () => { 6 | const [{ isDragging }, drag, preview] = useDrag({ 7 | type: 'DragDropBox', 8 | collect: (monitor) => { 9 | const isDragging = monitor.isDragging(); 10 | return { 11 | isDragging, 12 | }; 13 | }, 14 | }); 15 | 16 | useEffect(() => { 17 | preview(getEmptyImage(), { captureDraggingState: true }); // 隐藏拖拽dom 18 | }, []); 19 | 20 | return ( 21 |
22 |
31 | 拖拽Dom预览 32 |
33 |
34 | ); 35 | }; 36 | export default DragPreviewDom; 37 | -------------------------------------------------------------------------------- /src/components/arbitrarilyDrag/Classification.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDrop } from 'react-dnd'; 3 | 4 | function Classification({ type, title }: any) { 5 | const [{ isOver, canDrop }, drop] = useDrop( 6 | () => ({ 7 | accept: type, 8 | drop(_item: any, monitor: any) { 9 | const delta = monitor.getDifferenceFromInitialOffset(); 10 | const left = Math.round(delta.x); 11 | const top = Math.round(delta.y); 12 | return { top, left }; 13 | }, 14 | canDrop: (_item, monitor) => { 15 | const item = monitor.getItem() as any; 16 | return item.type === type; 17 | }, 18 | collect: (monitor) => ({ 19 | isOver: !!monitor.isOver(), 20 | canDrop: !!monitor.canDrop(), 21 | }), 22 | }), 23 | [], 24 | ); 25 | 26 | return ( 27 |
28 |
{title}
29 |
34 |
35 | ); 36 | } 37 | 38 | export default Classification; 39 | -------------------------------------------------------------------------------- /src/components/listSort/index.scss: -------------------------------------------------------------------------------- 1 | .scard-move-container { 2 | width: 100%; 3 | height: 700px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .card_drop_group { 10 | margin-top: 30px; 11 | height: 240px; 12 | width: 150px; 13 | padding: 20px; 14 | border: 1px solid black; 15 | display: flex; 16 | flex-direction: column; 17 | 18 | .card_drag { 19 | width: 150px; 20 | height: 40px; 21 | display: flex; 22 | align-items: center; 23 | justify-content: center; 24 | font-size: 18px; 25 | } 26 | 27 | .card_drop + .card_drop { 28 | margin-top: 20px; 29 | } 30 | } 31 | 32 | .card_drag_group { 33 | height: 240px; 34 | padding: 20px; 35 | display: flex; 36 | margin-top: 50px; 37 | 38 | .card_drag { 39 | width: 150px; 40 | height: 40px; 41 | display: flex; 42 | align-items: center; 43 | justify-content: center; 44 | font-size: 18px; 45 | // font-weight: bold; 46 | border: 1px solid black; 47 | } 48 | 49 | .card_drag + .card_drag { 50 | margin-left: 20px; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/routes.ts: -------------------------------------------------------------------------------- 1 | import Chess from './Chess'; 2 | import ArbitrarilyDrag from './arbitrarilyDrag'; 3 | import CardSort from './cardSort'; 4 | import CardAssemble from './cardAssemble'; 5 | import ListSort from './listSort'; 6 | import DragPreviewImg from './dragPreviewImg'; 7 | import DragPreviewDom from './dragPreviewDom'; 8 | import MultiDrag from './multiDrag'; 9 | export const routes = [ 10 | { 11 | url: '/chess', 12 | title: '官方用例', 13 | Component: Chess, 14 | }, 15 | { 16 | url: '/word', 17 | title: '任意拖拽', 18 | Component: ArbitrarilyDrag, 19 | }, 20 | { 21 | url: '/cardSort', 22 | title: '预置卡片排序', 23 | Component: CardSort, 24 | }, 25 | { 26 | url: '/cardAssemble', 27 | title: '卡片拼图', 28 | Component: CardAssemble, 29 | }, 30 | { 31 | url: '/listSort', 32 | title: '列表排序', 33 | Component: ListSort, 34 | }, 35 | { 36 | url: '/dragPreviewImg', 37 | title: '图片预览', 38 | Component: DragPreviewImg, 39 | }, 40 | { 41 | url: '/dragPreviewDom', 42 | title: '自定义预览', 43 | Component: DragPreviewDom, 44 | }, 45 | { 46 | url: '/multiDrg', 47 | title: '批量拖拽', 48 | Component: MultiDrag, 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /src/components/arbitrarilyDrag/index.scss: -------------------------------------------------------------------------------- 1 | .word-container { 2 | width: 100%; 3 | height: 500px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .word_drop_group { 10 | width: 700px; 11 | height: 340px; 12 | display: flex; 13 | justify-content: space-between; 14 | margin-top: 30px; 15 | 16 | .word_drop_container { 17 | width: 320px; 18 | height: 350px; 19 | display: flex; 20 | flex-direction: column; 21 | 22 | .word_drop_text { 23 | height: 30px; 24 | text-align: center; 25 | line-height: 30px; 26 | } 27 | 28 | .word_drop { 29 | width: 320px; 30 | height: 320px; 31 | border: 1px solid black; 32 | } 33 | } 34 | } 35 | 36 | .word_drag_group { 37 | margin-top: 100px; 38 | width: 700px; 39 | height: 300px; 40 | display: flex; 41 | flex-wrap: wrap; 42 | padding: 40px; 43 | border: 1px solid black; 44 | 45 | .word_drag { 46 | padding: 24px; 47 | font-size: 18px; 48 | border: 1px solid black; 49 | position: relative; 50 | } 51 | 52 | .word_drag+.word_drag{ 53 | margin-left: 20px; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/components/arbitrarilyDrag/word.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC, useRef, useState } from 'react'; 2 | import { useDrag } from 'react-dnd'; 3 | 4 | const Word: FC = ({ type, text, id, ...props }: any) => { 5 | const [offsetX, setOffsetX] = useState(0); 6 | const [offsetY, setOffsetY] = useState(0); 7 | const [{ isDragging }, drag]: any = useDrag(() => ({ 8 | type, 9 | item: { id, type }, 10 | end(item, monitor) { 11 | let top = 0, 12 | left = 0; 13 | if (monitor.didDrop()) { 14 | const dropRes = monitor.getDropResult() as any; //获取拖拽对象所处容器的数据 15 | if (dropRes) { 16 | top = dropRes.top; 17 | left = dropRes.left; 18 | } 19 | setOffsetX((offsetX) => offsetX + left); 20 | setOffsetY((offsetY) => offsetY + top); 21 | } else { 22 | setOffsetX(0); 23 | setOffsetY(0); 24 | } 25 | }, 26 | collect: (monitor) => ({ 27 | isDragging: !!monitor.isDragging(), 28 | }), 29 | })); 30 | 31 | return ( 32 |
42 | {text} 43 |
44 | ); 45 | }; 46 | export default Word; 47 | -------------------------------------------------------------------------------- /src/components/multiDrag/index.scss: -------------------------------------------------------------------------------- 1 | .multi-drag-container { 2 | width: 100%; 3 | height: 700px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .card_drop { 10 | width: 400px; 11 | height: 400px; 12 | border: 1px solid black; 13 | margin-top: 20px; 14 | } 15 | 16 | .drag-group { 17 | margin-top: 20px; 18 | display: flex; 19 | } 20 | 21 | .card_drag_box { 22 | position: relative; 23 | width: auto; 24 | margin-top: 30px; 25 | height: 180px; 26 | } 27 | 28 | .card_drag_list { 29 | display: flex; 30 | width: 100%; 31 | height: 100%; 32 | flex-direction: column; 33 | position: relative; 34 | } 35 | 36 | 37 | .card_drag_preview { 38 | background: none; 39 | position: absolute; 40 | width: 100%; 41 | height: 100%; 42 | top: 0; 43 | right: 0; 44 | 45 | .card_drag_preview_inner { 46 | position: relative; 47 | width: 100%; 48 | height: 100%; 49 | } 50 | } 51 | 52 | .card_drag { 53 | height: 48px; 54 | padding: 0 24px; 55 | width: 80px; 56 | font-size: 18px; 57 | border: 1px solid black; 58 | position: relative; 59 | margin-top: 10px; 60 | display: flex; 61 | align-items: center; 62 | justify-content: space-between; 63 | z-index: 11; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/cardSort/card.tsx: -------------------------------------------------------------------------------- 1 | import { CSSProperties, FC, useRef, useState } from 'react'; 2 | import { useDrag, useDrop } from 'react-dnd'; 3 | 4 | const Card = ({ text, id, index, changePosition }: any) => { 5 | const ref = useRef(null); 6 | // 因为没有定义收集函数,所以返回值数组第一项不要 7 | const [, drop] = useDrop({ 8 | accept: 'DragDropBox', 9 | hover: (item: any, monitor) => { 10 | if (!ref.current) return; 11 | let dragIndex = item.index; 12 | let hoverIndex = index; 13 | if (dragIndex === hoverIndex) return; // 如果回到自己的坑,那就什么都不做 14 | changePosition(dragIndex, hoverIndex); // 调用传入的方法完成交换 15 | item.index = hoverIndex; // 将当前当前移动到Box的index赋值给当前拖动的box,不然会出现两个盒子疯狂抖动! 16 | }, 17 | drop: (item, monitor) => {}, 18 | }); 19 | 20 | const [{ isDragging }, drag] = useDrag({ 21 | type: 'DragDropBox', 22 | item: { id, index }, 23 | end: () => {}, 24 | isDragging: (monitor) => { 25 | return index === monitor.getItem().index; 26 | }, 27 | collect: (monitor) => ({ 28 | isDragging: monitor.isDragging(), 29 | }), 30 | }); 31 | 32 | return ( 33 |
40 | {text} 41 |
42 | ); 43 | }; 44 | export default Card; 45 | -------------------------------------------------------------------------------- /src/components/Chess/board.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Knight from './knight'; 3 | import { DndProvider } from 'react-dnd'; 4 | import { HTML5Backend } from 'react-dnd-html5-backend'; 5 | import BoardSquare from './boardSquare'; 6 | 7 | function renderSquare(i: any, knightPosition: any, moveKnight: any) { 8 | const x = i % 8; 9 | const y = Math.floor(i / 8); 10 | return ( 11 |
12 | 13 | {renderPiece(x, y, knightPosition)} 14 | 15 |
16 | ); 17 | } 18 | 19 | function renderPiece(x: any, y: any, knightPosition = []) { 20 | const [knightX, knightY] = knightPosition; 21 | if (x === knightX && y === knightY) { 22 | return ; 23 | } 24 | } 25 | 26 | export default function Board({ knightPosition, moveKnight }: any) { 27 | const squares = []; 28 | 29 | for (let i = 0; i < 64; i++) { 30 | squares.push(renderSquare(i, knightPosition, moveKnight)); 31 | } 32 | 33 | return ( 34 | 35 |
44 | {squares} 45 |
46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/components/cardAssemble/index.scss: -------------------------------------------------------------------------------- 1 | .scard-container { 2 | width: 100%; 3 | height: 700px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | padding-top: 50px; 8 | 9 | .card_drop_group { 10 | margin-top: 30px; 11 | height: 240px; 12 | padding: 20px; 13 | border: 1px solid black; 14 | display: flex; 15 | 16 | .card_drop { 17 | width: 120px; 18 | height: 240px; 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | font-size: 24px; 23 | font-weight: bold; 24 | } 25 | 26 | .card_drag { 27 | width: 120px; 28 | height: 240px; 29 | display: flex; 30 | align-items: center; 31 | justify-content: center; 32 | font-size: 24px; 33 | font-weight: bold; 34 | border: 1px solid black; 35 | background-color: orange; 36 | } 37 | 38 | .card_drop + .card_drop { 39 | margin-left: 20px; 40 | } 41 | } 42 | 43 | .card_drag_group { 44 | height: 240px; 45 | padding: 20px; 46 | display: flex; 47 | margin-top: 50px; 48 | 49 | .card_drag { 50 | width: 120px; 51 | height: 240px; 52 | display: flex; 53 | align-items: center; 54 | justify-content: center; 55 | font-size: 24px; 56 | font-weight: bold; 57 | border: 1px solid black; 58 | background-color: orange; 59 | } 60 | 61 | .card_drag + .card_drag { 62 | margin-left: 20px; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.11.47", 11 | "@types/react": "^18.0.15", 12 | "@types/react-dom": "^18.0.6", 13 | "immutability-helper": "^3.1.1", 14 | "iscroll": "^5.2.0", 15 | "node-sass": "^7.0.3", 16 | "react": "^18.2.0", 17 | "react-dnd": "^16.0.1", 18 | "react-dnd-html5-backend": "^16.0.1", 19 | "react-dom": "^18.2.0", 20 | "react-router": "^6.4.2", 21 | "react-router-dom": "^6.4.2", 22 | "react-scripts": "5.0.1", 23 | "typescript": "^4.7.4", 24 | "web-vitals": "^2.1.4" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "eslint-config-prettier": "^8.5.0", 52 | "eslint-plugin-prettier": "^4.2.1", 53 | "prettier": "^2.7.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/listSort/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import DragSquare from './dragSquare'; 5 | import DropSquare from './dropSquare'; 6 | import './index.scss'; 7 | 8 | const CARD_INIT_ARR = [ 9 | { id: 1, text: 'Apple', bg: 'red' }, 10 | { id: 2, text: 'Banana', bg: 'yellow' }, 11 | { id: 3, text: 'Orange', bg: 'orange' }, 12 | { id: 4, text: 'Grape', bg: 'purple' }, 13 | { id: 5, text: 'Watermelon', bg: 'green' }, 14 | { id: 6, text: 'Peach', bg: 'pink' }, 15 | ]; 16 | 17 | class CardSort extends React.Component { 18 | constructor(props: any) { 19 | super(props); 20 | this.state = { 21 | dragCardList: CARD_INIT_ARR, 22 | dropCardList: [], 23 | }; 24 | this.updateDragAndDrop = this.updateDragAndDrop.bind(this); 25 | } 26 | 27 | updateDragAndDrop(newCardList: any) { 28 | this.setState({ dropCardList: newCardList }); 29 | } 30 | 31 | render() { 32 | const { dragCardList, dropCardList } = this.state; 33 | return ( 34 | 35 |
36 |

列表排序(数量无限,有顺移)

37 | 38 | 39 |
40 |
41 | ); 42 | } 43 | } 44 | 45 | export default CardSort; 46 | -------------------------------------------------------------------------------- /src/components/Chess/boardSquare.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Square from './square'; 3 | import { ItemTypes } from './type'; 4 | import { useDrop } from 'react-dnd'; 5 | import { canMoveKnight } from './utils'; 6 | 7 | function Overlay({ color }: any) { 8 | return ( 9 |
21 | ); 22 | } 23 | 24 | function BoardSquare({ x, y, knightPosition, children, moveKnight }: any) { 25 | const black = (x + y) % 2 === 1; 26 | const [{ isOver, canDrop }, drop] = useDrop( 27 | () => ({ 28 | accept: ItemTypes.KNIGHT, 29 | drop: (item, monitor) => { 30 | moveKnight(x, y); 31 | }, 32 | canDrop: () => canMoveKnight(x, y, knightPosition), 33 | collect: (monitor) => ({ 34 | isOver: !!monitor.isOver(), 35 | canDrop: !!monitor.canDrop(), 36 | }), 37 | }), 38 | [x, y], 39 | ); 40 | 41 | return ( 42 |
50 | {children} 51 | {isOver && !canDrop && } 52 | {!isOver && canDrop && } 53 | {isOver && canDrop && } 54 |
55 | ); 56 | } 57 | 58 | export default BoardSquare; 59 | -------------------------------------------------------------------------------- /src/components/listSort/box.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DragSourceMonitor, useDrag } from 'react-dnd'; 3 | import ItemType from './type'; 4 | 5 | let id = 1; 6 | 7 | const Box = ({ bg, text, dropCardList, updateDragAndDrop }: any) => { 8 | const style: React.CSSProperties = { 9 | background: bg, 10 | }; 11 | 12 | const box = { 13 | bg, 14 | text, 15 | }; 16 | 17 | const [{ isDragging }, drag] = useDrag({ 18 | type: ItemType.Card, 19 | item() { 20 | //在拖动操作开始时触发 21 | const useless = dropCardList.find((item: any) => item.id === -1); 22 | // 拖拽开始时,向 cardList 数据源中插入一个占位的元素,如果占位元素已经存在,不再重复插入 23 | if (!useless) { 24 | updateDragAndDrop([{ bg: 'aqua', text: '放这里', id: -1 }, ...dropCardList]); 25 | } 26 | 27 | return box; 28 | }, 29 | end(_: unknown, monitor: DragSourceMonitor) { 30 | const uselessIndex = dropCardList.findIndex((item: any) => item.id === -1); 31 | 32 | /** 33 | * 拖拽结束时,判断是否将拖拽元素放入了目标接收组件中 34 | * 1、如果是,则使用真正传入的 box 元素代替占位元素 35 | * 2、如果否,则将占位元素删除 36 | */ 37 | 38 | if (monitor.didDrop()) { 39 | dropCardList.splice(uselessIndex, 1, { ...monitor.getItem(), id: id++ }); 40 | } else { 41 | dropCardList.splice(uselessIndex, 1); 42 | } 43 | // 更新 cardList 数据源 44 | updateDragAndDrop(dropCardList); 45 | }, 46 | collect: (monitor) => ({ 47 | isDragging: monitor.isDragging(), 48 | }), 49 | }); 50 | return ( 51 |
52 | {text} 53 |
54 | ); 55 | }; 56 | 57 | export default Box; 58 | -------------------------------------------------------------------------------- /src/components/cardSort/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import Card from './card'; 5 | import './index.scss'; 6 | 7 | const CARD_INIT_ARR = [ 8 | { 9 | id:0, 10 | text: '000', 11 | }, 12 | { 13 | id:1, 14 | text: '111', 15 | }, 16 | { 17 | id:2, 18 | text: '222', 19 | }, 20 | { 21 | id:3, 22 | text: '333', 23 | }, 24 | { 25 | id:4, 26 | text: '444', 27 | }, 28 | ]; 29 | 30 | class CardSort extends React.Component { 31 | constructor(props: any) { 32 | super(props); 33 | this.state = { 34 | cardList: [], 35 | }; 36 | this.changePosition = this.changePosition.bind(this); 37 | } 38 | 39 | componentDidMount() { 40 | this.setState({ cardList: CARD_INIT_ARR }); 41 | } 42 | 43 | changePosition(dragIndex: string | number, hoverIndex: string | number) { 44 | let data = this.state.cardList.slice(); 45 | let temp = data[dragIndex]; 46 | // 交换位置 47 | data[dragIndex] = data[hoverIndex]; 48 | data[hoverIndex] = temp; 49 | this.setState({ cardList: data }); 50 | } 51 | 52 | render() { 53 | return ( 54 | 55 |
56 |

预置卡片排序

57 |
58 | {this.state.cardList.map((each: any, index: any) => ( 59 | 60 | ))} 61 |
62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | export default CardSort; 69 | -------------------------------------------------------------------------------- /src/components/arbitrarilyDrag/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import Classification from './Classification'; 5 | import './index.scss'; 6 | import { WORD_TYPE } from './type'; 7 | import Word from './word'; 8 | 9 | const WORDS = [ 10 | { 11 | text: 'interesting', 12 | type: WORD_TYPE.adj, 13 | }, 14 | { 15 | text: 'interest', 16 | type: WORD_TYPE.verb, 17 | }, 18 | { 19 | text: 'forget', 20 | type: WORD_TYPE.verb, 21 | }, 22 | { 23 | text: 'interested', 24 | type: WORD_TYPE.adj, 25 | }, 26 | ]; 27 | 28 | const Classifications = [ 29 | { 30 | title: '形容词', 31 | type: WORD_TYPE.adj, 32 | }, 33 | { 34 | title: '动词', 35 | type: WORD_TYPE.verb, 36 | }, 37 | ]; 38 | 39 | class WordClassification extends React.Component { 40 | constructor(props: any) { 41 | super(props); 42 | this.state = {}; 43 | } 44 | 45 | render() { 46 | return ( 47 | 48 |
49 |

任意拖拽

50 |
51 | {Classifications.map((each, index) => ( 52 | 53 | ))} 54 |
55 | 56 |
57 | {WORDS.map((each, index) => { 58 | let newEach = { ...each, id: 'drag' + index }; 59 | return ; 60 | })} 61 |
62 |
63 |
64 | ); 65 | } 66 | } 67 | 68 | export default WordClassification; 69 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": "./tsconfig.json" 9 | }, 10 | "plugins": ["jest", "@typescript-eslint"], 11 | "extends": ["react-app", "react-app/jest", "plugin:prettier/recommended"],// 关闭prettier和eslint有冲突的规则,没有定义的规则以prettiew为准 12 | "rules": { 13 | "linebreak-style": [0, "error", "windows"], 14 | "@typescript-eslint/no-explicit-any": [0], 15 | "@typescript-eslint/no-empty-function": [0], 16 | "@typescript-eslint/no-empty-interface": [0], 17 | "@typescript-eslint/no-unused-vars": [0], 18 | "@typescript-eslint/no-use-before-define": [0], 19 | "@typescript-eslint/explicit-member-accessibility": [0], 20 | "@typescript-eslint/no-var-requires": [0], 21 | "@typescript-eslint/no-non-null-assertion": [0], 22 | "@typescript-eslint/explicit-function-return-type": [0], 23 | "@typescript-eslint/ban-ts-ignore": "off", 24 | "@typescript-eslint/no-this-alias": "off", 25 | "newline-per-chained-call": ["error", { "ignoreChainWithDepth": 5 }], 26 | "camelcase": [0], 27 | "@typescript-eslint/camelcase": [0], 28 | "@typescript-eslint/ban-ts-comment": "off", 29 | "@typescript-eslint/function-paren-newline": "off", 30 | "nonblock-statement-body-position": "off", 31 | "dot-notation": [0, { "allowKeywords": true }], 32 | "arrow-parens": 0 33 | }, 34 | "overrides": [ 35 | { 36 | "files": ["*.ts", "*.tsx"], 37 | "rules": { 38 | "prefer-const": [0], 39 | "no-var": [2], 40 | "no-constant-condition": [0], 41 | "camelcase": [0], 42 | "@typescript-eslint/camelcase": [0], 43 | "function-paren-newline": "off" 44 | } 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/dragPreviewDom/customDragerLayer.tsx: -------------------------------------------------------------------------------- 1 | import type { CSSProperties, FC } from 'react'; 2 | import type { XYCoord } from 'react-dnd'; 3 | import { useDragLayer } from 'react-dnd'; 4 | 5 | function snapToGrid(x: number, y: number): [number, number] { 6 | const snappedX = Math.round(x / 32) * 32; 7 | const snappedY = Math.round(y / 32) * 32; 8 | return [snappedX, snappedY]; 9 | } 10 | 11 | const layerStyles: CSSProperties = { 12 | position: 'fixed', 13 | pointerEvents: 'none', 14 | zIndex: 100, 15 | left: 0, 16 | top: 0, 17 | // width: '100%', 18 | // height: '100%', 19 | }; 20 | 21 | function getItemStyles( 22 | initialOffset: XYCoord | null, 23 | currentOffset: XYCoord | null, 24 | isSnapToGrid: boolean 25 | ) { 26 | if (!initialOffset || !currentOffset) { 27 | return { 28 | display: 'none', 29 | }; 30 | } 31 | 32 | let { x, y } = currentOffset; 33 | 34 | if (isSnapToGrid) { 35 | x -= initialOffset.x; 36 | y -= initialOffset.y; 37 | [x, y] = snapToGrid(x, y); 38 | x += initialOffset.x; 39 | y += initialOffset.y; 40 | } 41 | 42 | const transform = `translate(${x}px, ${y}px)`; 43 | return { 44 | transform, 45 | WebkitTransform: transform, 46 | }; 47 | } 48 | 49 | export const CustomDragLayer = (props: any) => { 50 | const { itemType, isDragging, item, initialOffset, currentOffset } = 51 | useDragLayer((monitor) => ({ 52 | item: monitor.getItem(), 53 | itemType: monitor.getItemType(), 54 | initialOffset: monitor.getInitialSourceClientOffset(), 55 | currentOffset: monitor.getSourceClientOffset(), 56 | isDragging: monitor.isDragging(), 57 | })); 58 | 59 | function renderItem() { 60 | switch (itemType) { 61 | case 'DragDropBox': 62 | return
这里是预览样式
; 63 | default: 64 | return null; 65 | } 66 | } 67 | 68 | if (!isDragging) { 69 | return null; 70 | } 71 | 72 | return ( 73 |
74 |
77 | {renderItem()} 78 |
79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /src/components/listSort/dropSquare.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useDrop } from 'react-dnd'; 3 | import Card from './card'; 4 | import ItemTypes from './type'; 5 | import update from 'immutability-helper'; 6 | export default function DropSquare({ dropCardList, updateDragAndDrop }: any) { 7 | const [{ canDrop }, drop] = useDrop({ 8 | accept: ItemTypes.Card, 9 | 10 | canDrop: (_item, monitor) => { 11 | return true; 12 | }, 13 | collect: (monitor) => ({ 14 | canDrop: !!monitor.canDrop(), 15 | }), 16 | }); 17 | 18 | const moveCard = useCallback( 19 | (dragIndex: number, hoverIndex: number) => { 20 | console.log('dragIndex',dragIndex,'hoverIndex',hoverIndex); 21 | 22 | /** 23 | * 1、如果此时拖拽的组件是 Box 组件,则 dragIndex 为 undefined,则此时修改,则此时修改 cardList 中的占位元素的位置即可 24 | * 2、如果此时拖拽的组件是 Card 组件,则 dragIndex 不为 undefined,此时替换 dragIndex 和 hoverIndex 位置的元素即可 25 | */ 26 | if (dragIndex === undefined) { 27 | const lessIndex = dropCardList.findIndex((item: any) => item.id === -1); 28 | updateDragAndDrop( 29 | update(dropCardList, { 30 | $splice: [ 31 | [lessIndex, 1], 32 | [hoverIndex, 0, { bg: 'aqua', category: '放这里', id: -1 }], 33 | ], 34 | }), 35 | ); 36 | } else { 37 | const dragCard = dropCardList[dragIndex]; 38 | updateDragAndDrop( 39 | update(dropCardList, { 40 | $splice: [ 41 | [dragIndex, 1], 42 | [hoverIndex, 0, dragCard], 43 | ], 44 | }), 45 | ); 46 | } 47 | // eslint-disable-next-line 48 | }, 49 | [dropCardList, updateDragAndDrop], 50 | ); 51 | 52 | return ( 53 |
54 | {dropCardList.length > 0 && 55 | dropCardList.map((each: any, index: number) => ( 56 | 64 | ))} 65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/components/multiDrag/Drag.tsx: -------------------------------------------------------------------------------- 1 | import { relative } from 'path'; 2 | import { useEffect, useState } from 'react'; 3 | import { useDrag } from 'react-dnd'; 4 | import { getEmptyImage } from 'react-dnd-html5-backend'; 5 | 6 | const cardList = [ 7 | { 8 | id: 1, 9 | text: 'js', 10 | }, 11 | { 12 | id: 2, 13 | text: 'css', 14 | }, 15 | { 16 | id: 3, 17 | text: 'html', 18 | }, 19 | ]; 20 | 21 | const itemHeight = 48; 22 | 23 | const Drag = () => { 24 | let [selectList, setSelectList] = useState(cardList) as [any[], any]; 25 | let [isDragBegin, setisDragBegin] = useState(false); 26 | const [{ isDragging }, drag] = useDrag({ 27 | type: 'DragDropBox', 28 | item: () => { 29 | setisDragBegin(() => true); 30 | return {}; 31 | }, 32 | end: () => { 33 | if (isDragBegin) { 34 | setisDragBegin(() => false); 35 | } 36 | }, 37 | collect: (monitor) => { 38 | const isDragging = monitor.isDragging(); 39 | return { 40 | isDragging, 41 | }; 42 | }, 43 | }); 44 | 45 | const updateList = (each: any) => { 46 | let newSelectList: any[] = []; 47 | newSelectList = selectList.map((item) => { 48 | if (item.id === each.id) { 49 | if (selectList.filter((item) => item.id === each.id && item.selected).length > 0) { 50 | item.selected = false; 51 | } else { 52 | item.selected = true; 53 | } 54 | } 55 | return item; 56 | }); 57 | setSelectList(newSelectList); 58 | }; 59 | 60 | const isSelectItem = (id: any) => { 61 | return selectList 62 | .filter((item) => item.selected) 63 | .map((item) => item.id) 64 | .includes(id); 65 | }; 66 | 67 | return ( 68 |
69 |
70 | {cardList.map((each) => ( 71 |
72 | {each.text} 73 | updateList(each)} /> 74 |
75 | ))} 76 |
77 |
78 |
79 | {selectList.map((each) => ( 80 |
89 | {each.text} 90 | updateList(each)} /> 91 |
92 | ))} 93 |
94 |
95 |
96 | ); 97 | }; 98 | export default Drag; 99 | -------------------------------------------------------------------------------- /src/components/listSort/card.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * filename: Card 3 | * overview: 根据放入 Box 生成的 Card 组件 4 | */ 5 | 6 | import React, { useRef, useMemo } from 'react'; 7 | import { XYCoord } from 'dnd-core'; 8 | import { DragSourceMonitor, DropTargetMonitor, useDrag, useDrop } from 'react-dnd'; 9 | import ItemTypes from './type'; 10 | 11 | const Card = ({ bg, text, index, moveCard, id, updateDragAndDrop, dropCardList }: any) => { 12 | const ref = useRef(null); 13 | 14 | const [{ isDragging }, drag] = useDrag({ 15 | type: ItemTypes.Card, 16 | collect: (monitor: any) => ({ 17 | isDragging: monitor.getItem() ? index === monitor.getItem().index : false, // 直接用monitor.isDragging无法监控索引更改 18 | // isDragging: monitor.isDragging() 19 | }), 20 | // item 中包含 index 属性,则在 drop 组件 hover 和 drop 是可以根据第一个参数获取到 index 值 21 | item: { index, id }, 22 | 23 | end(item: any, monitor: DragSourceMonitor) { 24 | const idx = item.id; 25 | const uselessIndex = dropCardList.findIndex((item: any) => item.id === idx); 26 | 27 | /** 28 | * 监控是否将元素移出了列表 29 | */ 30 | if (!monitor.didDrop()) { 31 | dropCardList.splice(uselessIndex, 1); 32 | } 33 | // 更新 cardList 数据源 34 | updateDragAndDrop(dropCardList); 35 | }, 36 | }); 37 | 38 | const [, drop] = useDrop({ 39 | accept: ItemTypes.Card, 40 | hover(item: any, monitor: DropTargetMonitor) { 41 | if (!ref.current) { 42 | return; 43 | } 44 | const dragIndex = item.index; 45 | const hoverIndex = index; 46 | 47 | // 拖拽元素下标与鼠标悬浮元素下标一致时,不进行操作 48 | if (dragIndex === hoverIndex) { 49 | return; 50 | } 51 | 52 | // 确定屏幕上矩形范围 53 | const hoverBoundingRect = ref.current!.getBoundingClientRect(); 54 | 55 | // 获取中点垂直坐标 56 | const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; 57 | 58 | // 确定鼠标位置 59 | const clientOffset = monitor.getClientOffset(); 60 | 61 | // 获取距顶部距离 62 | const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; 63 | 64 | /** 65 | * 只在鼠标越过一半物品高度时执行移动。 66 | * 67 | * 当向下拖动时,仅当光标低于50%时才移动。 68 | * 当向上拖动时,仅当光标在50%以上时才移动。 69 | * 70 | * 可以防止鼠标位于元素一半高度时元素抖动的状况 71 | */ 72 | 73 | // 向下拖动 74 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { 75 | return; 76 | } 77 | 78 | // 向上拖动 79 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { 80 | return; 81 | } 82 | 83 | // 执行 move 回调函数 84 | moveCard(dragIndex, hoverIndex); 85 | 86 | /** 87 | * 如果拖拽的组件为 Box,则 dragIndex 为 undefined,此时不对 item 的 index 进行修改 88 | * 如果拖拽的组件为 Card,则将 hoverIndex 赋值给 item 的 index 属性 89 | */ 90 | if (item.index !== undefined) { 91 | item.index = hoverIndex; 92 | } 93 | }, 94 | }); 95 | 96 | const style: React.CSSProperties = useMemo( 97 | () => ({ 98 | // Card 为占位元素是,透明度 0.4,拖拽状态时透明度 0.2,正常情况透明度为 1 99 | opacity: id === -1 ? 0.4 : isDragging ? 1 : 1, 100 | backgroundColor: bg, 101 | }), 102 | [bg, id, isDragging], 103 | ); 104 | 105 | /** 106 | * 使用 drag 和 drop 对 ref 进行包裹,则组件既可以进行拖拽也可以接收拖拽组件 107 | */ 108 | return ( 109 |
110 | {text} 111 |
112 | ); 113 | }; 114 | 115 | export default Card; 116 | -------------------------------------------------------------------------------- /src/components/cardAssemble/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DndProvider } from 'react-dnd'; 3 | import { HTML5Backend } from 'react-dnd-html5-backend'; 4 | import DragSquare from './dragSquare'; 5 | import DropSquare from './dropSquare'; 6 | import './index.scss'; 7 | 8 | const CARD_INIT_ARR = [ 9 | { 10 | id: 1, 11 | text: '她', 12 | }, 13 | { 14 | id: 2, 15 | text: '我的', 16 | }, 17 | { 18 | id: 3, 19 | text: '唯一', 20 | }, 21 | { 22 | id: 4, 23 | text: '是', 24 | }, 25 | { 26 | id: 5, 27 | text: '现在', 28 | }, 29 | ]; 30 | 31 | const INIT_DROP = { 32 | innerDrag: null, 33 | } as any; 34 | 35 | const INIT_DROP_LIST = new Array(5).fill(0).map((_item, index) => { 36 | return { ...INIT_DROP, id: index + 1 }; 37 | }); 38 | 39 | class CardSort extends React.Component { 40 | constructor(props: any) { 41 | super(props); 42 | this.state = { 43 | dragCardList: CARD_INIT_ARR, 44 | dropCardList: INIT_DROP_LIST, 45 | }; 46 | this.updateDragAndDrop = this.updateDragAndDrop.bind(this); 47 | } 48 | 49 | updateDragAndDrop(dragId: any, dropId?: any) { 50 | let { dragCardList, dropCardList } = this.state; 51 | let moveDragItem: any = null; 52 | if (!dropId) { 53 | if (dragCardList.length === 5) return; 54 | this.dragItemBack(dragId); 55 | } else { 56 | let beginDragId = -1; 57 | [moveDragItem, dragCardList] = this.removeDragItemById(dragId, dragCardList); 58 | 59 | if (!moveDragItem) { 60 | [moveDragItem, dropCardList, beginDragId] = this.removeDropItemById(dragId, dropCardList); 61 | } 62 | 63 | dropCardList = dropCardList.map((item: { id: any; innerDrag: any }, _index: any, arr: any) => { 64 | if (item.id === dropId) { 65 | if (item.innerDrag) { 66 | arr[beginDragId].innerDrag = item.innerDrag; 67 | } 68 | item.innerDrag = moveDragItem; 69 | } 70 | return item; 71 | }); 72 | this.setState({ dragCardList, dropCardList }); 73 | } 74 | } 75 | 76 | // 从drop列表将drag拖拽回去 77 | dragItemBack(dragId: number) { 78 | let { dragCardList, dropCardList } = this.state; 79 | let moveDragItem = null; 80 | [moveDragItem, dropCardList] = this.removeDropItemById(dragId, dropCardList); 81 | 82 | dragCardList.push(moveDragItem); 83 | dragCardList = dragCardList.sort((pre: any, next: any) => pre.id - next.id); 84 | this.setState({ dragCardList, dropCardList }); 85 | } 86 | 87 | // 根据id从drop列表中删除drop 88 | removeDropItemById(itemId: any, dropCardList: any) { 89 | let moveDragItem: any = null; 90 | let dropId = -1; 91 | let newDropCardList = dropCardList.map((item: { id: any; innerDrag: any }, index: number) => { 92 | if (item.innerDrag && item.innerDrag.id === itemId) { 93 | moveDragItem = item.innerDrag; 94 | dropId = index; 95 | item.innerDrag = null; 96 | } 97 | return item; 98 | }); 99 | return [moveDragItem, newDropCardList, dropId]; 100 | } 101 | 102 | // 根据id从drag列表中删除drag 103 | removeDragItemById(itemId: any, dragCardList: any) { 104 | let moveDragItem: any = null; 105 | dragCardList = dragCardList.filter((item: any) => { 106 | if (item.id === itemId) { 107 | moveDragItem = item; 108 | return false; 109 | } 110 | return true; 111 | }); 112 | return [moveDragItem, dragCardList]; 113 | } 114 | 115 | render() { 116 | const { dragCardList, dropCardList } = this.state; 117 | return ( 118 | 119 |
120 |

卡片拼图

121 | 122 | 123 |
124 |
125 | ); 126 | } 127 | } 128 | 129 | export default CardSort; 130 | --------------------------------------------------------------------------------