SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>3D立体爱心</title>
<style>
  body { margin: 0; padding: 0; background: #000; overflow: hidden; }
  canvas { display: block; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let W = window.innerWidth;
let H = window.innerHeight;
canvas.width = W;
canvas.height = H;

const CANVAS_CENTER_X = W / 2;
const CANVAS_CENTER_Y = H / 2;
const IMAGE_ENLARGE = Math.min(W, H) / 45; // 自动适配手机屏幕大小
const HEART_COLOR = "#ff7171"; // 爱心颜色

// 核心公式:生成爱心形状的坐标
function heartFunction(t, shrinkRatio = IMAGE_ENLARGE) {
  let x = 16 * Math.pow(Math.sin(t), 3);
  let y = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
  x *= shrinkRatio;
  y *= shrinkRatio;
  x += CANVAS_CENTER_X;
  y += CANVAS_CENTER_Y;
  return { x: Math.floor(x), y: Math.floor(y) };
}

// 内部散落效果
function scatterInside(x, y, beta = 0.15) {
  let ratioX = -beta * Math.log(Math.random());
  let ratioY = -beta * Math.log(Math.random());
  let dx = ratioX * (x - CANVAS_CENTER_X);
  let dy = ratioY * (y - CANVAS_CENTER_Y);
  return { x: x - dx, y: y - dy };
}

// 缩小边缘扩散
function shrink(x, y, ratio) {
  let force = -1 / (Math.pow(Math.pow(x - CANVAS_CENTER_X, 2) + Math.pow(y - CANVAS_CENTER_Y, 2), 0.6));
  let dx = ratio * force * (x - CANVAS_CENTER_X);
  let dy = ratio * force * (y - CANVAS_CENTER_Y);
  return { x: x - dx, y: y - dy };
}

// 周期曲线,控制心跳节奏
function curve(p) {
  return 2 * (2 * Math.sin(4 * p)) / (2 * Math.PI);
}

let _points = [];
let _edge_diffusion_points = [];
let _center_diffusion_points = [];
let all_points = {}; // 存储每一帧的坐标
const GENERATE_FRAME = 20;

// 初始化构建点阵
function build(number) {
  for (let i = 0; i < number; i++) {
    let t = Math.random() * 2 * Math.PI;
    let p = heartFunction(t);
    _points.push(p);
  }

  // 边缘扩散
  for (let p of _points) {
    for (let i = 0; i < 3; i++) {
      let ep = scatterInside(p.x, p.y, 0.05);
      _edge_diffusion_points.push(ep);
    }
  }

  // 中心扩散
  for (let i = 0; i < 4000; i++) {
    let p = _points[Math.floor(Math.random() * _points.length)];
    let cp = scatterInside(p.x, p.y, 0.17);
    _center_diffusion_points.push(cp);
  }
}

function calcPosition(x, y, ratio) {
  let force = 1 / (Math.pow(Math.pow(x - CANVAS_CENTER_X, 2) + Math.pow(y - CANVAS_CENTER_Y, 2), 0.520));
  let dx = ratio * force * (x - CANVAS_CENTER_X) + Math.floor(Math.random() * 3) - 1;
  let dy = ratio * force * (y - CANVAS_CENTER_Y) + Math.floor(Math.random() * 3) - 1;
  return { x: x - dx, y: y - dy };
}

// 计算每一帧的坐标
function calc(generateFrame) {
  let ratio = 10 * curve(generateFrame / 10 * Math.PI);
  let haloRadius = Math.floor(4 + 6 * (1 + curve(generateFrame / 10 * Math.PI)));
  let haloNumber = Math.floor(3000 + 4000 * Math.abs(Math.pow(curve(generateFrame / 10 * Math.PI), 2)));
  let framePoints = [];
  
  // 光环
  let heartHaloPoint = new Set();
  for (let i = 0; i < haloNumber; i++) {
    let t = Math.random() * 2 * Math.PI;
    let p = heartFunction(t, IMAGE_ENLARGE * 1.05);
    let sp = shrink(p.x, p.y, haloRadius);
    let key = sp.x + "," + sp.y;
    if (!heartHaloPoint.has(key)) {
      heartHaloPoint.add(key);
      sp.x += Math.floor(Math.random() * 29) - 14;
      sp.y += Math.floor(Math.random() * 29) - 14;
      let size = Math.random() < 0.3 ? 1 : 2;
      framePoints.push({ x: sp.x, y: sp.y, size: size });
    }
  }

  // 轮廓
  for (let p of _points) {
    let cp = calcPosition(p.x, p.y, ratio);
    framePoints.push({ x: cp.x, y: cp.y, size: Math.floor(Math.random() * 3) + 1 });
  }

  // 内容(边缘扩散)
  for (let p of _edge_diffusion_points) {
    let cp = calcPosition(p.x, p.y, ratio);
    framePoints.push({ x: cp.x, y: cp.y, size: Math.floor(Math.random() * 2) + 1 });
  }

  // 内容(中心扩散)
  for (let p of _center_diffusion_points) {
    let cp = calcPosition(p.x, p.y, ratio);
    framePoints.push({ x: cp.x, y: cp.y, size: Math.floor(Math.random() * 2) + 1 });
  }

  all_points[generateFrame] = framePoints;
}

// 初始化运行
build(2000);
for (let frame = 0; frame < GENERATE_FRAME; frame++) {
  calc(frame);
}

let currentFrame = 0;

// 渲染函数
function render() {
  ctx.fillStyle = 'black';
  ctx.fillRect(0, 0, W, H);
  
  ctx.fillStyle = HEART_COLOR;
  
  let points = all_points[currentFrame % GENERATE_FRAME];
  for (let p of points) {
    ctx.fillRect(p.x, p.y, p.size, p.size);
  }
  
  currentFrame++;
  setTimeout(() => {
    requestAnimationFrame(render);
  }, 160); // 控制帧率,和原代码一致
}

render();
</script>
</body>
</html>