文字随意拖动效果

一月 09, 2024 / Mr.x / 2阅读 / 0评论/ 分类: 创意
文字拖动效果 — 荣小站

文字随意拖动效果

使用 Matter.js 物理引擎实现的文字拖动效果,每个文字都可以自由拖动并带有物理碰撞。

▼ 效果展示 ▼ 拖动文字试试 ▼
丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。明月几时有,把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。

这是一个有趣的物理文字效果。使用 Matter.js 2D 物理引擎,将每个文字变成可拖动的物理实体,具有重力、碰撞和边界约束。你可以随意拖动文字,松开后会自然下落并与其他文字碰撞。

核心特性

  • 物理引擎:使用 Matter.js 实现真实物理效果
  • 自由拖动:鼠标拖动文字,松开后自然下落
  • 碰撞效果:文字之间会产生物理碰撞
  • 边界约束:文字不会飞出视窗

完整代码

以下是完整的单文件代码,你可以直接保存为 .html 文件运行:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>文字拖动效果</title>
  <link href="https://fonts.googleapis.com/css2?family=ZCOOL+XiaoWei&display=swap" rel="stylesheet">
  <style>
    body {
      margin: 0;
      padding: 0;
      background: #1a1a2e;
      color: transparent;
      font-family: 'ZCOOL XiaoWei', serif;
      width: 100vw;
      height: 100vh;
      overflow: hidden;
    }
    * {
      user-select: none;
    }
    .word {
      position: absolute;
      cursor: grab;
      font-size: 30px;
      color: #ffeb3b;
    }
    .word:active {
      cursor: grabbing;
    }
  </style>
</head>
<body>
  <main>
    <div class="text">丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。明月几时有,把酒问青天。...</div>
  </main>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script>
  <script>
    // 拆分文字
    const splitWords = () => {
      const textNode = document.querySelector(".text");
      const text = textNode.textContent;
      const newDomElements = text.split("").map((char) => {
        return `<span class="word">${char === ' ' ? '&nbsp;' : char}</span>`;
      });
      textNode.innerHTML = newDomElements.join("");
    };

    // 渲染画布
    const renderCanvas = () => {
      const Engine = Matter.Engine;
      const Render = Matter.Render;
      const World = Matter.World;
      const Bodies = Matter.Bodies;
      const Runner = Matter.Runner;

      const canvasSize = {
        width: window.innerWidth,
        height: window.innerHeight
      };

      const engine = Engine.create({});

      const render = Render.create({
        element: document.body,
        engine: engine,
        options: {
          ...canvasSize,
          background: "#1a1a2e",
          wireframes: false
        }
      });

      // 边界
      const wallOptions = {
        isStatic: true,
        render: { fillStyle: "transparent" }
      };
      const floor = Bodies.rectangle(
        canvasSize.width / 2,
        canvasSize.height,
        canvasSize.width,
        50,
        wallOptions
      );
      const wall1 = Bodies.rectangle(0, canvasSize.height / 2, 50, canvasSize.height, wallOptions);
      const wall2 = Bodies.rectangle(canvasSize.width, canvasSize.height / 2, 50, canvasSize.height, wallOptions);
      const top = Bodies.rectangle(canvasSize.width / 2, 0, canvasSize.width, 50, wallOptions);

      // 创建文字物理实体
      const wordElements = document.querySelectorAll(".word");
      const wordBodies = [...wordElements].map((elemRef) => {
        const width = elemRef.offsetWidth || 30;
        const height = elemRef.offsetHeight || 30;

        return {
          body: Matter.Bodies.rectangle(
            Math.random() * (canvasSize.width - 100) + 50,
            Math.random() * 200,
            width,
            height,
            {
              render: { fillStyle: "transparent" }
            }
          ),
          elem: elemRef,
          render() {
            const { x, y } = this.body.position;
            this.elem.style.top = `${y - height / 2}px`;
            this.elem.style.left = `${x - width / 2}px`;
            this.elem.style.transform = `rotate(${this.body.angle}rad)`;
          }
        };
      });

      // 鼠标控制
      const mouse = Matter.Mouse.create(document.body);
      const mouseConstraint = Matter.MouseConstraint.create(engine, {
        mouse,
        constraint: {
          stiffness: 0.2,
          render: { visible: false }
        }
      });

      // 移除鼠标滚轮事件
      mouse.element.removeEventListener("mousewheel", mouse.mousewheel);
      mouse.element.removeEventListener("DOMMouseScroll", mouse.mousewheel);

      World.add(engine.world, [
        floor, wall1, wall2, top,
        ...wordBodies.map((box) => box.body),
        mouseConstraint
      ]);

      render.mouse = mouse;
      Runner.run(engine);
      Render.run(render);

      // 循环更新
      (function rerender() {
        wordBodies.forEach((element) => {
          element.render();
        });
        Matter.Engine.update(engine);
        requestAnimationFrame(rerender);
      })();
    };

    window.addEventListener("DOMContentLoaded", () => {
      splitWords();
      renderCanvas();
    });
  </script>
</body>
</html>

技术要点

技术点说明
Matter.js2D 物理引擎库
物理实体每个文字创建对应的物理刚体
MouseConstraint实现鼠标拖动交互
边界约束创建四面墙壁防止飞出

使用说明

  1. 将完整代码保存为 .html 文件
  2. 用浏览器打开
  3. 鼠标拖动任意文字
  4. 松开后文字会自然下落并碰撞

#文字随意拖动效果(1)

评论