文字随意拖动效果
文字随意拖动效果
使用 Matter.js 物理引擎实现的文字拖动效果,每个文字都可以自由拖动并带有物理碰撞。
▼ 效果展示
▼ 拖动文字试试 ▼
丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。明月几时有,把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。转朱阁,低绮户,照无眠。不应有恨,何事长向别时圆?人有悲欢离合,月有阴晴圆缺,此事古难全。但愿人长久,千里共婵娟。
使用 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 === ' ' ? ' ' : 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.js | 2D 物理引擎库 |
| 物理实体 | 每个文字创建对应的物理刚体 |
| MouseConstraint | 实现鼠标拖动交互 |
| 边界约束 | 创建四面墙壁防止飞出 |
使用说明
- 将完整代码保存为
.html文件 - 用浏览器打开
- 鼠标拖动任意文字
- 松开后文字会自然下落并碰撞
评论