matter-js
matter-js copied to clipboard
How to drag multiple items simultaneously?
How to add multi drag support such that two or more items can be drag simultaneously?
It is going to be a multi-touch/drag application allowing users to drag multiple items at the same time.
Example: I have boxA and boxB which are connected to HTML div elements. I want to add multi-drag support such that boxA and boxB can be drag simultaneously.
I saw two other posts which are dead end.
Any lead would be very helpful. Thank you.
` import Head from 'next/head'; import { Box, Button, Flex, Heading, Image, Text } from '@chakra-ui/react'; import React, { Fragment, useEffect, useRef, useState } from 'react'; import Matter from 'matter-js'; import MatterAttractors from 'matter-attractors';
import { Colors } from '@lib/helpers';
import Draggable from 'react-draggable';
export default function InteractiveDisplay() { // Attractor const executiveAttractRef = useRef([]); const judicativeAttractRef = useRef([]);
// DOM Refs
const engineRef = useRef(); const worldRef = useRef(); const canvasRef = useRef(); const requestRef = useRef(); const judicativeBoxRef = useRef(); const executiveBoxRef = useRef();
const resetBodiesRef = useRef();
let domRenders = [];
const animate = () => { Matter.use(MatterAttractors); // Matter.use(MatterDomPlugin);
// Canvas setup
const canvas = document.getElementById('world');
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
window.addEventListener('resize', function () {
canvas.width = document.documentElement.clientWidth;
canvas.height = document.documentElement.clientHeight;
location.reload();
});
function percentX(percent) {
return Math.round((percent / 100) * canvas.width);
}
function percentY(percent) {
return Math.round((percent / 100) * canvas.height);
}
const Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Events = Matter.Events,
MouseConstraint = Matter.MouseConstraint,
Mouse = Matter.Mouse,
Composite = Matter.Composite,
Constraint = Matter.Constraint,
Composites = Matter.Composites,
Bodies = Matter.Bodies,
Body = Matter.Body,
World = Matter.World;
// create engine
engineRef.current = Engine.create();
const engine = engineRef.current,
// const engine = Engine.create(),
world = engine.world;
engine.gravity.y = 0;
// create renderer
worldRef.current = world;
const render = Render.create({
element: canvasRef.current,
engine: engine,
options: {
width: percentX(100),
height: percentY(100),
wireframes: false,
background: Colors.transparent,
},
});
const runner = Runner.create();
Runner.run(runner, engine);
Render.run(render);
const bodiesSize = 600;
const colliderState = {
Size: bodiesSize,
X: canvas.width / 2 - bodiesSize / 2,
Y: canvas.height / 2 - bodiesSize / 2,
};
const defaultState = {
mainBodies: {
radius: 115, //150 default
judicative: {
x: colliderState.X + colliderState.Size / 2,
y: colliderState.Y,
},
executive: {
x: colliderState.X + colliderState.Size,
y: colliderState.Y + colliderState.Size / 2,
},
},
colliderState,
};
const boxA = {
body: Bodies.circle(
defaultState.mainBodies.judicative.x,
defaultState.mainBodies.judicative.y + 300,
defaultState.mainBodies.radius / 2,
{
isSensor: false,
label: `judicative`,
isStatic: false,
render: {
strokeStyle: Colors.transparent,
fillStyle: 'green',
lineWidth: 1,
},
plugin: {
attractors: [
function (bodyA, bodyB) {
const label = bodyB.label.split(' ');
if (judicativeAttractRef?.current && label[1] === 'judicative') {
const element = judicativeAttractRef?.current.filter(
(el) => el.label === label[2]
);
if (element.length > 0 && label[2] === element[0].label) {
return {
x: (bodyA.position.x - bodyB.position.x) * 0.0005,
y: (bodyA.position.y - bodyB.position.y) * 0.0005,
};
}
}
},
],
},
}
),
elem: judicativeBoxRef.current,
render() {
const { x, y } = this.body.position;
this.elem.style.top = `${y - 10}px`;
this.elem.style.left = `${x + 60}px`;
},
};
resetBodiesRef.current = {
...resetBodiesRef.current,
boxA,
};
domRenders.push(boxA);
const boxB = {
body: Matter.Bodies.circle(
defaultState.mainBodies.executive.x,
defaultState.mainBodies.executive.y,
defaultState.mainBodies.radius / 2,
{
isSensor: false,
label: `executive`,
isStatic: false,
render: {
strokeStyle: Colors.transparent,
fillStyle: 'blue',
lineWidth: 1,
},
plugin: {
attractors: [
function (bodyA, bodyB) {
const label = bodyB.label.split(' ');
if (executiveAttractRef?.current && label[1] === 'executive') {
const element = executiveAttractRef?.current.filter(
(el) => el.label === label[2]
);
if (element.length > 0 && label[2] === element[0].label) {
return {
x: (bodyA.position.x - bodyB.position.x) * 0.0005,
y: (bodyA.position.y - bodyB.position.y) * 0.0005,
};
}
}
},
],
},
}
),
elem: executiveBoxRef.current,
render() {
const { x, y } = this.body.position;
this.elem.style.top = `${y - 10}px`;
this.elem.style.left = `${x + 60}px`;
},
};
resetBodiesRef.current = {
...resetBodiesRef.current,
boxB,
};
domRenders.push(boxB);
const mainParts = [boxA.body, boxB.body];
World.add(world, mainParts);
const mouse = Mouse.create(render.canvas),
mouseConstraint = MouseConstraint.create(engine, {
// mouse: mouse,
element: canvasRef.current,
constraint: {
stiffness: 0.2,
render: {
visible: false,
},
},
});
Composite.add(world, mouseConstraint);
render.mouse = mouse;
Render.lookAt(render, {
min: { x: 0, y: 0 },
max: { x: canvas.width, y: canvas.height },
});
(function rerender() {
domRenders.length > 0 && domRenders.map((box) => box.render());
Matter.Engine.update(engine);
requestRef.current = requestAnimationFrame(rerender);
})();
};
useEffect(() => { animate(); return () => { cancelAnimationFrame(requestRef.current); Matter.Engine.clear(engineRef.current); }; }, []);
return ( <Flex w={'100vw'} h={'100vh'} ref={canvasRef} id={'world'} pos={'relative'}> <Box ref={judicativeBoxRef} height={{ md: '15rem', '2xl': '30rem' }} width={{ md: '15rem', '2xl': '30rem' }} position={'absolute'} display={'flex'} justifyContent={'center'} alignItems={'center'} className={'juris'} zIndex={100} borderRadius={'50rem'} transformOrigin={'center center'} willChange={'left,top'} > <Image src={'/images/center/juris-de.svg'} pos={'absolute'} userSelect={'none'} pointerEvents={'none'} height={{ md: '60%', '2xl': '85%' }} width={{ md: '60%', '2xl': '85%' }} /> </Box>
<Box
ref={executiveBoxRef}
height={{ md: '15rem', '2xl': '30rem' }}
width={{ md: '15rem', '2xl': '30rem' }}
position={'absolute'}
display={'flex'}
justifyContent={'center'}
alignItems={'center'}
className={'exec'}
zIndex={100}
borderRadius={'50rem'}
transformOrigin={'center center'}
// bg={'rgba(35, 62, 214, 0.2)'}
>
<Image
src={'/images/center/executive-en.svg'}
pos={'absolute'}
userSelect={'none'}
pointerEvents={'none'}
height={{ md: '60%', '2xl': '85%' }}
width={{ md: '60%', '2xl': '85%' }}
/>
</Box>
</Flex>
); } `
Hi @liabru, Thanks for your amazing work. Sorry for directly tagging you here. I came across this requirement after completely finishing the project. i have been trying to fix this for over a week now. Do you know any leads on the issue? Thank you for your time.
touch-events Maybe it can help you