<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./style.css">
<script src="./index.js"></script>
</head>
<body>
<canvas id="art" style="display:block;margin:0 auto;max-width:100%;height:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);"></canvas>
</body>
</html>
index.html
style.css
index.js
现在支持上传本地图片了!
body {
background-color: #fefefe;
}
.title {
margin-top: 95px;
text-align: center;
}
.desc {
color: #777;
text-align: center;
}
.icon {
display: block;
width: 48px;
height: 48px;
margin: 30px auto;
}
const canvas = document.getElementById("art");
const ctx = canvas.getContext("2d");
const text = "笔.COOL";
const fontSize = 300;
const fontFamily = "Arial Black, Arial, system-ui, sans-serif";
const margin = 200;
const spacingX = 6;
const outsideColor = "rgba(0, 255, 255, 0.3)";
const insideColor = "rgba(255, 0, 255, 1)";
document.body.style.background = "linear-gradient(135deg, #000000 0%, #0d1117 25%, #1a0d2e 50%, #2d1b69 75%, #000000 100%)";
document.body.style.margin = "0";
document.body.style.padding = "0";
document.body.style.height = "100vh";
document.body.style.overflow = "hidden";
if (document.fonts && document.fonts.ready) {
await document.fonts.ready;
}
ctx.font = `bold ${fontSize}px ${fontFamily}`;
const m = ctx.measureText(text);
const textW = Math.ceil(m.width);
const textH = Math.ceil(
(m.actualBoundingBoxAscent || fontSize * 0.8) +
(m.actualBoundingBoxDescent || fontSize * 0.2)
);
canvas.width = textW + margin * 2;
canvas.height = textH + margin * 2;
const w = canvas.width, h = canvas.height;
const maskCanvas = document.createElement("canvas");
maskCanvas.width = w;
maskCanvas.height = h;
const maskCtx = maskCanvas.getContext("2d");
maskCtx.fillStyle = "#fff";
maskCtx.font = `bold ${fontSize}px ${fontFamily}`;
maskCtx.textAlign = "center";
maskCtx.textBaseline = "middle";
maskCtx.fillText(text, w / 2, h / 2);
const maskData = maskCtx.getImageData(0, 0, w, h).data;
function wave(x, y, t) {
const centerX = w / 2;
const centerY = h / 2;
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
const maxDistance = Math.sqrt(centerX ** 2 + centerY ** 2);
const waveSpeed = 0.003;
const waveDelay = distance * 0.002;
const wavePhase = (t - waveDelay) * waveSpeed;
const amplitude = 30 * (1 - distance / maxDistance) * (y / h);
const frequency = 0.03;
return Math.sin(wavePhase + distance * frequency) * amplitude;
}
function draw(t) {
ctx.clearRect(0, 0, w, h);
for (let x = 0; x < w; x += spacingX) {
let points = [];
for (let y = 0; y < h; y++) {
const i = ((y * w) + x) * 4;
const inside = maskData[i + 3] > 128;
const offset = inside ? wave(x, y, t) : 0;
points.push({ x: x + offset, y, inside });
}
let currentInside = points[0].inside;
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.strokeStyle = currentInside ? insideColor : outsideColor;
ctx.lineWidth = currentInside ? 4 : 1.5;
for (let i = 1; i < points.length; i++) {
const p = points[i];
if (p.inside !== currentInside) {
ctx.lineTo(p.x, p.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(p.x, p.y);
currentInside = p.inside;
ctx.strokeStyle = currentInside ? insideColor : outsideColor;
ctx.lineWidth = currentInside ? 4 : 1.5;
} else {
ctx.lineTo(p.x, p.y);
}
}
ctx.stroke();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
预览