点击查看html编辑器说明文档

果冻edit icon

|
|
Fork(复制)
|
|
作者:
天生闹腾

👉 新版编辑器已上线,点击进行体验吧!

BUG反馈
嵌入
设置
下载
HTML
格式化
支持Emmet,输入 p 后按 Tab键试试吧!
<head> ...
展开
</head>
<body>
            
            <div id="info">
    <div>
        <div>使用鼠标拖拽</div>
        <div>按<code>1</code>键切换慢动作模式</div>
        <div>按<code>2</code>键切换零重力模式</div>
        <div>按<code>3</code>键切换反向重力模式</div>
    </div>
</div>
<canvas></canvas>
        
</body>
CSS
格式化
            
            * {
  margin: 0;
}
 
body {
  font-family: Helvetica, sans-serif;
  background: #f8f8f8;
}

#info {
  font-size: 22px;
  color: #777;
  position: absolute;
  z-index: -1;
  display: flex;
  flex-direction: column;
  width: 100vw;
  height: 100vh;
  justify-content: center;
  align-items: center;
  animation: fadeOut 1s forwards 3s;
}

#info > div {
  margin-top: -3em;
}

#info > div > div {
  margin-bottom: 0.4em;
}

code {
  color: #555;
  background: rgba(0, 0, 0, 0.07);
  padding: 0em 0.2em;
}


@keyframes fadeOut {
  from {
    opacity: 1
  }
  to {
    opacity: 0.4
  }
}
        
JS
格式化
            
            const SPACING = 14;
const ITERATIONS = 14;
const MOUSE = SPACING * 5;
let GRAVITY = 0.05;
let SPEED = 1;

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const mouse = {
  x: 0,
  y: 0,
  px: 0,
  py: 0,
  points: [],
};

const clamp = function (val, min, max) {
  return Math.min(Math.max(val, min), max);
};

class Vector {
  constructor (x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }

  get length () {
    return Math.sqrt(this.x * this.x + this.y * this.y);
  }

  add (v) {
    const p = v instanceof Vector;
    this.x += p ? v.x : v;
    this.y += p ? v.y : v;
    return this;
  }

  sub (v) {
    const p = v instanceof Vector;
    this.x -= p ? v.x : v;
    this.y -= p ? v.y : v;
    return this;
  }

  mul (v) {
    const p = v instanceof Vector;
    this.x *= p ? v.x : v;
    this.y *= p ? v.y : v;
    return this;
  }

  scale (x) {
    this.x *= x;
    this.y *= x;
    return this;
  }

  normalize () {
    const len = this.length;
    if (len > 0) {
      this.x /= len;
      this.y /= len;
    }

    return this;
  }

  distance (v) {
    const x = this.x - v.x;
    const y = this.y - v.y;
    return Math.sqrt(x * x + y * y);
  }

  static add (v1, v2) {
    const v = v2 instanceof Vector;
    return new Vector(
      v1.x + (v ? v2.x : v2),
      v1.y + (v ? v2.y : v2)
    );
  }

  static sub (v1, v2) {
    const v = v2 instanceof Vector;
    return new Vector(
      v1.x - (v ? v2.x : v2),
      v1.y - (v ? v2.y : v2)
    );
  }

  static mul (v1, v2) {
    const v = v2 instanceof Vector;
    return new Vector(
      v1.x * (v ? v2.x : v2),
      v1.y * (v ? v2.y : v2)
    );
  }

  static dot (v1, v2) {
    return v1.x * v2.x + v1.y * v2.y;
  }
}

const reactor = function (a, b, p) {
  const refA = Vector.add(a.toWorld(p), a.pos);
  const refB = Vector.add(b.toWorld(Vector.mul(p, -1)), b.pos);

  const diff = Vector.sub(refB, refA);
  const mid = Vector.add(refA, Vector.mul(diff, 0.5));

  const t = clamp(b.p - a.p, -Math.PI, Math.PI);
  a.torque += t;
  b.torque -= t;

  const mfc = 0.04;
  const tfc = 0.02;
  const mf = Vector.mul(diff, mfc);
  const tf = Vector.mul(diff, tfc);
  const dm = Vector.sub(b.vat(mid), a.vat(mid));
  mf.add(Vector.mul(dm, mfc));
  tf.add(Vector.mul(dm, tfc));

  a.addForce(mf, mid);
  b.addForce(Vector.mul(mf, -1), mid);
  a.addTorque(tf, mid);
  b.addTorque(Vector.mul(tf, -1), mid);
};

const allContraints = [];

class Point {
  constructor (pos, square) {
    this.pos = pos;
    this.velocity = new Vector();
    this.force = new Vector();

    this.p = 0;
    this.w = 0;
    this.torque = 0;
    this.square = square;
  }

  update () {
    this.velocity.add(Vector.mul(this.force, SPEED));

    this.force = new Vector(0, GRAVITY / ITERATIONS);

    this.pos.add(Vector.mul(this.velocity, SPEED));

    const qPI = Math.PI / 4;
    this.w += this.torque / ((SPACING / 2) ** 2 / 2);
    this.w = clamp(this.w * SPEED, -qPI, qPI);

    this.p += this.w;
    this.torque = 0;

    mouse.points.includes(this) &&
      this.moveTo(mouse, this.mouseDiff);
  }

  toWorld (input) {
    return new Vector(
      -input.y * Math.sin(this.p) + input.x * Math.cos(this.p),
      input.y * Math.cos(this.p) + input.x * Math.sin(this.p)
    );
  }

  vat (R) {
    const dr = Vector.sub(R, this.pos);
    const vdr = this.w * dr.length;

    dr.normalize();

    return Vector.add(
      this.velocity,
      new Vector(vdr * -dr.y, vdr * dr.x)
    );
  }

  addForce (F) {
    this.force.add(F);
  }

  addTorque (F, R) {
    const arm = Vector.sub(R, this.pos);
    const torque = F.y * arm.x - F.x * arm.y;
    this.torque += torque;
  }

  moveTo (v, offset) {
    const targetX = v.x + offset.x;
    const targetY = v.y + offset.y;
    const strength = 0.001;
    this.velocity.x += (targetX - this.pos.x) * strength * SPEED;
    this.velocity.y += (targetY - this.pos.y) * strength * SPEED;
    this.velocity.mul(0.99);
  }
}

class Square {
  constructor (width, height, spacing, hue) {
    this.width = width;
    this.height = height;
    this.spacing = spacing;
    this.hue = hue;

    const yOff = 200 + Math.random() * (canvas.height - 300 - height * SPACING);
    const xOff = 10 + Math.random() * (canvas.width - 10 - width * SPACING);

    const w = -0.5 + Math.random();

    this.points = Array(width * height).fill(0).map((_, i) => {
      const x = i % width;
      const y = ~~(i / width);

      const p = new Point(
        new Vector(
          xOff + x * spacing,
          canvas.height - yOff + y * spacing,
        ),
        this,
      );

      p.w = w;

      return p;
    });

    this.points.forEach((point, i) => {
      const x = i % width;
      const y = ~~(i / width);

      if (x > 0) {
        allContraints.push([
          this.points[i - 1],
          point,
          new Vector(SPACING / 2, 0)
        ]);
      }

      if (y > 0) {
        allContraints.push([
          this.points[i - width],
          point,
          new Vector(0, SPACING / 2)
        ]);
      }
    });

    this.drawPoints = [];

    for (let i = 0; i < width; i++) {
      this.drawPoints.push(this.points[i].pos);
    }

    for (let i = 0; i < height; i++) {
      this.drawPoints.push(this.points[(width - 1) + width * i].pos);
    }

    for (let i = width - 1; i > -1; i--) {
      this.drawPoints.push(this.points[(height - 1) * width + i].pos);
    }

    for (let i = height - 1; i > -1; i--) {
      this.drawPoints.push(this.points[(width ) * i].pos);
    }
  }

  draw (ctx) {
    const { drawPoints, hue } = this;

    ctx.lineWidth = 2;
    ctx.fillStyle = `hsla(${hue}, 90%, 80%, 0.8)`;
    ctx.strokeStyle = `hsla(${hue}, 90%, 70%, 0.8)`;

    ctx.beginPath();
    ctx.moveTo(drawPoints[0].x, drawPoints[0].y);

    drawPoints.forEach((point, i) =>{
      i && ctx.lineTo(point.x, point.y);
    });

    ctx.lineTo(drawPoints[0].x, drawPoints[0].y);
    ctx.stroke();
    ctx.fill();
  }
}

const hue = Math.random() * 360;
const squares = Array(4).fill(0).map((_, i) => {
  const size = 8 + i * 2;
  return new Square(
    size,
    size,
    SPACING,
    hue + i * 20,
  );
});

const allPoints = [].concat(...squares.map(({ points }) => points));

const update = function () {
  const { width, height } = canvas;

  ctx.clearRect(0, 0, width, height);

  let i = ITERATIONS;
  while (i--) {
    allContraints.forEach((con, i) => {
      reactor(...con, i);
    });

    allPoints.forEach((point, i) => {
      const { square } = point;

      const damping = 0.6;
      const spacing = (square ? square.spacing : SPACING) / 2;

      if (point.pos.x < spacing) {
        point.force.add(new Vector((spacing - point.pos.x) * 1, 0));
        point.velocity.y *= damping;
      } else if (point.pos.x > canvas.width - spacing) {
        point.force.add(new Vector((point.pos.x - canvas.width + spacing) * -1, 0));
        point.velocity.y *= damping;
      }

      if (point.pos.y < spacing) {
        point.force.add(new Vector(0, (spacing - point.pos.y) * 1));
        point.velocity.x *= damping;
      } else if (point.pos.y > canvas.height - spacing) {
        point.force.add(new Vector(0, (point.pos.y - canvas.height + spacing) * -1));
        point.velocity.x *= damping;
      }

      point.update();
    });
  }

  squares.forEach((s) => {
    s.draw(ctx);
  });

  if (mouse.down) {
    ctx.fillStyle = 'rgba(0, 0, 100, 0.03)';
    ctx.beginPath();
    ctx.arc(mouse.x, mouse.y, MOUSE, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(mouse.x, mouse.y, SPACING, 0, Math.PI * 2);
    ctx.fill();
  }

  mouse.px = mouse.x;
  mouse.py = mouse.y;

  window.requestAnimationFrame(update);
};

update();


const setMouse = (e) => {
  e = e.touches ? e.touches[0] : e;
  const rect = canvas.getBoundingClientRect();
  mouse.px = mouse.x;
  mouse.py = mouse.y;
  mouse.x = e.clientX - rect.left;
  mouse.y = e.clientY - rect.top;
};

canvas.onmousedown = canvas.ontouchstart = (e) => {
  setMouse(e);
  mouse.down = true;

  for (const point of allPoints) {
    if (point.pos.distance(mouse) < MOUSE && !mouse.points.includes(point)) {
      mouse.points.push(point);
      point.mouseDiff = Vector.sub(point.pos, new Vector(mouse.x, mouse.y));
      point.velocity.mul(0);
      point.force.mul(0);
    }
  }
};

canvas.onmouseup = canvas.ontouchend = () => {
  mouse.points = [];
  mouse.down = false;
};

canvas.onmousemove = canvas.ontouchmove = setMouse;

window.onkeydown = ({ keyCode }) => {
  if (keyCode === 49) {
    SPEED = 0.2;
  } else if (keyCode === 50) {
    GRAVITY = 0;
  } else if (keyCode === 51) {
    GRAVITY = -0.05;
  }
};
window.onkeyup = ({ keyCode }) => {
  if (keyCode === 49) {
    SPEED = 1;
  } else if (keyCode === 50) {
    GRAVITY = 0.05;
  } else if (keyCode === 51) {
    GRAVITY = 0.05;
  }
};

        
预览
控制台