如何使项目可拖动和可点击?

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

我是 Matter JS 的新手,所以请耐心等待。我从演示和其他来源整理了以下代码以满足我的需求:

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 },
  })
}

我有一个 HTML 链接列表,它模仿 Matter JS 中项目的移动(positionDomElements 函数)。我这样做是为了 SEO 目的,也是为了使导航易于访问和点击。

但是,因为我的画布位于 HTML 之上(不透明度为零),所以我需要能够使项目可单击和可拖动,以便我可以执行一些其他操作,例如导航到链接(和其他事件) ).

我不知道该怎么做。我四处寻找,但没有任何运气。

是否可以让每个项目可拖动(因为它已经是)并执行某种点击事件?

任何帮助或引导正确的方向将不胜感激。

javascript matter.js
1个回答
1
投票

您的任务似乎是将物理添加到一组 DOM 导航列表节点。您可能会认为,matter.js 需要提供画布才能运行,如果您想忽略它,则需要隐藏画布或将其不透明度设置为 0。

实际上,您可以使用自己的更新循环无头运行 MJS,而无需将元素注入到引擎中。实际上,不需要与

Matter.Render
Matter.Runner
相关的任何内容,您可以使用对
Matter.Engine.update(engine);
的调用使引擎在
requestAnimationFrame
循环中向前步进一个刻度。然后,您可以使用从 MJS 主体中提取的值来定位 DOM 元素。您已经在做这两件事,所以主要是剪切画布和渲染调用的问题。

这是一个可运行的示例,您可以参考并适应您的用例。

定位是难点;确保 MJS 坐标与您的鼠标和元素坐标相匹配需要一些麻烦。 MJS 将 x/y 坐标视为主体的中心,因此我使用

body.vertices[0]
表示左上角,这样可以更好地匹配 DOM。我想这些渲染决策中有很多都是特定于应用程序的,因此请将此视为概念验证。

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.Composite.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.20.0/matter.min.js"></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>

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