How to make items draggable and clickable?

时光总嘲笑我的痴心妄想 提交于 2021-02-10 15:44:16

问题


I'm new to Matter JS, so please bear with me. I have the following code I put together from demos and other sources to suit my needs:

function biscuits(width, height, items, gutter) {
  const {
    Engine,
    Render,
    Runner,
    Composites,
    MouseConstraint,
    Mouse,
    World,
    Bodies,
  } = Matter

  const engine = Engine.create()
  const world = engine.world

  const render = Render.create({
    element: document.getElementById('canvas'),
    engine,
    options: {
      width,
      height,
      showAngleIndicator: true,
    },
  })

  Render.run(render)

  const runner = Runner.create()
  Runner.run(runner, engine)

  const columns = media({ bp: 'xs' }) ? 3 : 1
  const stack = Composites.stack(
    getRandom(gutter, gutter * 2),
    gutter,
    columns,
    items.length,
    0,
    0,
    (x, y, a, b, c, i) => {
      const item = items[i]

      if (!item) {
        return null
      }

      const {
        width: itemWidth,
        height: itemHeight,
      } = item.getBoundingClientRect()

      const radiusAmount = media({ bp: 'sm' }) ? 100 : 70
      const radius = item.classList.contains('is-biscuit-4')
        ? radiusAmount
        : 0
      const shape = item.classList.contains('is-biscuit-2')
        ? Bodies.circle(x, y, itemWidth / 2)
        : Bodies.rectangle(x, y, itemWidth, itemHeight, {
            chamfer: { radius },
          })

      return shape
    }
  )

  World.add(world, stack)

  function positionDomElements() {
    Engine.update(engine, 20)

    stack.bodies.forEach((block, index) => {
      const item = items[index]
      const xTrans = block.position.x - item.offsetWidth / 2 - gutter / 2
      const yTrans = block.position.y - item.offsetHeight / 2 - gutter / 2

      item.style.transform = `translate3d(${xTrans}px, ${yTrans}px, 0) rotate(${block.angle}rad)`
    })

    window.requestAnimationFrame(positionDomElements)
  }

  positionDomElements()

  World.add(world, [
    Bodies.rectangle(width / 2, 0, width, gutter, { isStatic: true }),
    Bodies.rectangle(width / 2, height, width, gutter, { isStatic: true }),
    Bodies.rectangle(width, height / 2, gutter, height, { isStatic: true }),
    Bodies.rectangle(0, height / 2, gutter, height, { isStatic: true }),
  ])

  const mouse = Mouse.create(render.canvas)
  const mouseConstraint = MouseConstraint.create(engine, {
    mouse,
    constraint: {
      stiffness: 0.2,
      render: {
        visible: false,
      },
    },
  })

  World.add(world, mouseConstraint)

  render.mouse = mouse

  Render.lookAt(render, {
    min: { x: 0, y: 0 },
    max: { x: width, y: height },
  })
}

I have a HTML list of links that mimics the movements of the items in Matter JS (the positionDomElements function). I'm doing this for SEO purposes and also to make the navigation accessible and clickable.

However, because my canvas sits on top of my HTML (with opacity zero) I need to be able to make the items clickable as well as draggable, so that I can perform some other actions, like navigating to the links (and other events).

I'm not sure how to do this. I've searched around but I'm not having any luck.

Is it possible to have each item draggable (as it already is) AND perform a click event of some kind?

Any help or steer in the right direction would be greatly appreciated.


回答1:


It seems like your task here is to add physics to a set of DOM navigation list nodes. You may be under the impression that matter.js needs to be provided a canvas to function and that hiding the canvas or setting its opacity to 0 is necessary if you want to ignore it.

Actually, you can just run MJS headlessly using your own update loop without injecting an element into the engine. Effectively, anything related to Matter.Render or Matter.Runner will not be needed and you can use a call to Matter.Engine.update(engine); to step the engine forward one tick in the requestAnimationFrame loop. You can then position the DOM elements using values pulled from the MJS bodies. You're already doing both of these things, so it's mostly a matter of cutting out the canvas and rendering calls.

Here's a runnable example that you can reference and adapt to your use case.

Positioning is the hard part; it takes some fussing to ensure the MJS coordinates match your mouse and element coordinates. MJS treats x/y coordinates as center of the body, so I used body.vertices[0] for the top-left corner which matches the DOM better. I imagine a lot of these rendering decisions are applicaton-specific, so consider this a proof-of-concept.

const listEls = document.querySelectorAll("#mjs-wrapper li");
const engine = Matter.Engine.create();

const stack = Matter.Composites.stack(
  // xx, yy, columns, rows, columnGap, rowGap, cb
  0, 0, listEls.length, 1, 0, 0,
  (xx, yy, i) => {
    const {x, y, width, height} = listEls[i].getBoundingClientRect();
    return Matter.Bodies.rectangle(x, y, width, height, {
      isStatic: i === 0 || i + 1 === listEls.length
    });
  }
);
Matter.Composites.chain(stack, 0.5, 0, -0.5, 0, {
  stiffness: 0.5,
  length: 20
});
const mouseConstraint = Matter.MouseConstraint.create(
  engine, {element: document.querySelector("#mjs-wrapper")}
);
Matter.World.add(engine.world, [stack, mouseConstraint]);

listEls.forEach(e => {
  e.style.position = "absolute";
  e.addEventListener("click", e =>
    console.log(e.target.textContent)
  );
});

(function update() {
  requestAnimationFrame(update);
  stack.bodies.forEach((block, i) => {
    const li = listEls[i];
    const {x, y} = block.vertices[0];
    li.style.top = `${y}px`;
    li.style.left = `${x}px`;
    li.style.transform = `translate(-50%, -50%) 
                          rotate(${block.angle}rad) 
                          translate(50%, 50%)`;
  });
  Matter.Engine.update(engine);
})();
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  height: 100%;
}

body {
  min-width: 600px;
}

#mjs-wrapper {
  /* position this element */
  margin: 1em; 
  height: 100%;
}
#mjs-wrapper ul {
  font-size: 14pt;
  list-style: none;
  user-select: none;
  position: relative;
}
#mjs-wrapper li {
  background: #fff;
  border: 1px solid #555;
  display: inline-block;
  padding: 1em;
  cursor: move;
}
#mjs-wrapper li:hover {
  background: #f2f2f2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.14.2/matter.min.js" integrity="sha512-pi0tSRZdlNRZeANPwdAIHRAYg6gZZV6QlAiyHXn5TYqLzBKE9jlttO/QgYLMhISD6oNv2kPsVelx+n5nw0FqKA==" crossorigin="anonymous"></script>

<div id="mjs-wrapper">
  <ul>
    <li>Foo</li>
    <li>Bar</li>
    <li>Baz</li>
    <li>Quux</li>
    <li>Garply</li>
    <li>Corge</li>
  </ul>
</div>


来源:https://stackoverflow.com/questions/64432514/how-to-make-items-draggable-and-clickable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!