Skip to content

Commit

Permalink
feat: base world with physics on each partner items (using matter js)
Browse files Browse the repository at this point in the history
  • Loading branch information
dan1M committed Oct 23, 2024
1 parent 7c763be commit 0a8abb5
Showing 1 changed file with 139 additions and 14 deletions.
153 changes: 139 additions & 14 deletions webapp/src/components/landing/PartnerSectionWithPhysics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ import {
useBreakpointValue,
} from "@chakra-ui/react";
import { useInView } from "framer-motion";
import { Engine, Render } from "matter-js";
import {
Bodies,
Engine,
Events,
IChamferableBodyDefinition,
Mouse,
MouseConstraint,
Render,
Runner,
World,
} from "matter-js";
import NextLink from "next/link";
import React, { useEffect, useRef, useState } from "react";

Expand All @@ -31,47 +41,156 @@ const partnersList = [
promo_label: "Gratuit",
},
];

const partnerItemClassName = "partner-item";

const PartnerSectionWithPhysics = ({}: PartnerSectionProps) => {
const isDesktop = useBreakpointValue({ base: false, lg: true });

const [physicBodies, setPhysicBodies] = useState([]);
const canvasRef = useRef<HTMLCanvasElement>(null);

const parentPartnersRef = useRef<HTMLDivElement>(null);

const arePhysicsTriggered = useInView(parentPartnersRef, {
const arePhysicsTriggered = useInView(canvasRef, {
once: true,
amount: 1,
});

useEffect(() => {
if (!arePhysicsTriggered || !parentPartnersRef.current) return;
if (!arePhysicsTriggered || !canvasRef.current) return;

const parent = parentPartnersRef.current;
const canvas = canvasRef.current;
const engine = Engine.create();
const world = engine.world;
const runner = Runner.create();

const render = Render.create({
element: parent,
canvas: canvas,
engine: engine,
options: {
width: parent.clientWidth,
height: parent.clientHeight,
wireframes: false,
width: canvas.clientWidth,
height: canvas.clientHeight,
wireframeBackground: "transparent",
background: "transparent",
},
});

Render.run(render);
Runner.run(runner, engine);

const partnerElements =
document.getElementsByClassName(partnerItemClassName);
// Creates Matter.js bodies dynamically for each partner element
const partnerBodies = Array.from(partnerElements).map((element, index) => {
const html_element = element as HTMLElement;
const width = html_element.offsetWidth;
const height = html_element.offsetHeight;
const x = html_element.offsetLeft + width / 2;
const y = html_element.offsetTop + height / 2;

const body = Bodies.rectangle(x, y, width, height, {
restitution: 0.5, // Bounciness
friction: 0.3,
slop: 0.01,
density: 0.01,
render: {
// remove border
visible: false,
},
});
World.add(engine.world, body);
return { body, element: html_element }; // Returns the body and the DOM element
});

const innerOffset = 45;
const wallsOptions: IChamferableBodyDefinition = {
isStatic: true,
friction: 1,
render: { visible: false },
};

// Create walls around the parent element
const bottom = Bodies.rectangle(
canvas.clientWidth / 2,
canvas.clientHeight + innerOffset,
canvas.clientWidth + 200,
100,
wallsOptions
);
const top = Bodies.rectangle(
canvas.clientWidth / 2,
-innerOffset,
canvas.clientWidth + 200,
100,
wallsOptions
);
const left = Bodies.rectangle(
-innerOffset,
canvas.clientHeight / 2,
100,
canvas.clientHeight + 200,
wallsOptions
);
const right = Bodies.rectangle(
canvas.clientWidth + innerOffset,
canvas.clientHeight / 2,
100,
canvas.clientHeight + 200,
wallsOptions
);
World.add(engine.world, [bottom, top, left, right]);

// Mouse control
const mouse = Mouse.create(canvas);
const mouseConstraint = MouseConstraint.create(engine, {
mouse,

constraint: {
damping: 0.2,
stiffness: 1,
render: { visible: false },
},
});
World.add(engine.world, mouseConstraint);

// Update function for DOM position synced with matter-js bodies
const update = () => {
partnerBodies.forEach(({ body, element }) => {
const { x, y } = body.position;

element.style.left = `${x - element.offsetWidth / 2}px`;
element.style.top = `${y - element.offsetHeight / 2}px`;
element.style.rotate = `${body.angle}rad`;

element.style.position = "absolute";
});
requestAnimationFrame(update);
};

update();

return () => {
Runner.stop(runner);
Render.stop(render);
World.clear(engine.world, false);
Engine.clear(engine);
};
}, [arePhysicsTriggered]);

// TODO: Make rounded border rectangles
// TODO: Handle scroll when in canvas
// TODO: Handle link click when in canvas
// TODO: Fix image hitboxes

return (
<Flex
flexDir={{ base: "column", lg: "row" }}
pos={"relative"}
bg={"primary"}
w={{ base: "95%", lg: "full" }}
mx={"auto"}
rounded="5xl"
color={"white"}
mt={isDesktop ? 0 : 20}
ref={parentPartnersRef}
>
<Box as="canvas" ref={canvasRef} pos={"absolute"} w={"full"} h={"full"} />
<Flex
flex={1}
flexDir="column"
Expand Down Expand Up @@ -107,7 +226,6 @@ const PartnerSectionWithPhysics = ({}: PartnerSectionProps) => {
{partnersList.map((partner, index) => (
<Flex
key={`partner-${index}`}
className="partner-item"
flexDir={index % 2 === 0 ? "row" : "row-reverse"}
justifyContent={{ base: "start", lg: "center" }}
mb={4}
Expand All @@ -119,7 +237,11 @@ const PartnerSectionWithPhysics = ({}: PartnerSectionProps) => {
justifyContent="center"
bg="white"
rounded="full"
h={14}
w={"fit-content"}
p={4}
className={partnerItemClassName}
pointerEvents={"none"}
>
<Image src={partner.img} alt={`Logo de ${partner.name}`} />
</Flex>
Expand All @@ -130,7 +252,10 @@ const PartnerSectionWithPhysics = ({}: PartnerSectionProps) => {
fontWeight="extrabold"
rounded="full"
fontSize="xl"
h={14}
p={4}
pointerEvents={"none"}
className={partnerItemClassName}
>
{partner.promo_label}
</Flex>
Expand Down

0 comments on commit 0a8abb5

Please sign in to comment.