<body>
<main>
<div class="text">丙辰中秋, 欢饮达旦, 大醉, 作此篇, 兼怀子由。 明月几时有, 把酒问青天。 不知天上宫阙, 今夕是何年。 我欲乘风归去, 又恐琼楼玉宇, 高处不胜寒。 起舞弄清影, 何似在人间。转朱阁, 低绮户, 照无眠。 不应有恨, 何事长向别时圆? 人有悲欢离合, 月有阴晴圆缺, 此事古难全。 但愿人长久, 千里共婵娟。</div>
</main>
</body>
{/tabs-pane}
{tabs-pane label="css"}
@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@200;300;400;900&display=swap');
body {
margin: 0;
padding: 0;
background: #ffffff;
color: transparent;
font-family: 'Inconsolata', monospace;
width: 100vw;
height: 100vh;
overflow: hidden;
}
* {
user-select: none;
}
.word {
position: absolute;
cursor: grab;
font-size: 30px;
color: #ffeb3b;
}
.word.highlighted {
font-weight: bold;
color: black;
}
a {
text-decoration: none;
color: black;
display: block;
padding: 1rem;
}
{/tabs-pane}
{tabs-pane label="js"}
https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js
const splitWords = () => {
const textNode = document.querySelector(".text");
const text = textNode.textContent;
const newDomElements = text.split(" ").map((text) => {
const highlighted =
text.startsWith(`"30under30"`) ||
text.startsWith(`CTO`) ||
text.startsWith(`Mythrill`);
return `<span class="word ${
highlighted ? "highlighted" : null
}">${text}</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 params = {
isStatic: true,
render: {
fillStyle: "transparent"
}
};
const canvasSize = {
width: window.innerWidth,
height: window.innerHeight
};
const engine = Engine.create({});
const render = Render.create({
element: document.body,
engine: engine,
options: {
...canvasSize,
background: "transparent",
wireframes: false
}
});
const floor = Bodies.rectangle(
canvasSize.width / 2,
canvasSize.height,
canvasSize.width,
50,
params
);
const wall1 = Bodies.rectangle(
0,
canvasSize.height / 2,
50,
canvasSize.height,
params
);
const wall2 = Bodies.rectangle(
canvasSize.width,
canvasSize.height / 2,
50,
canvasSize.height,
params
);
const top = Bodies.rectangle(
canvasSize.width / 2,
0,
canvasSize.width,
50,
params
);
const wordElements = document.querySelectorAll(".word");
const wordBodies = [...wordElements].map((elemRef) => {
const width = elemRef.offsetWidth;
const height = elemRef.offsetHeight;
return {
body: Matter.Bodies.rectangle(canvasSize.width / 2, 0, width, height, {
render: {
fillStyle: "transparent"
}
}),
elem: elemRef,
render() {
const { x, y } = this.body.position;
this.elem.style.top = `${y - 20}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,
...wordBodies.map((box) => box.body),
wall1,
wall2,
top,
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", (event) => {
splitWords();
renderCanvas();
});
{/tabs-pane}
代码来自CodePenFalling Words
评语