黑洞

Published on
/
/趣玩前端
<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Black Hole Visualization</title>
    <style>
      body,
      html {
        height: 100%;
        margin: 0;
        padding: 0;
      }

      body {
        height: 100%;
        background-color: rgba(25, 25, 25, 1);
        overflow: hidden;
      }

      #blackhole {
        height: 100%;
        width: 100%;
        position: relative;
        display: flex;
      }

      .centerHover {
        width: 255px;
        height: 255px;
        background-color: transparent;
        border-radius: 50%;
        position: absolute;
        left: 50%;
        top: 50%;
        margin-top: -128px;
        margin-left: -128px;
        z-index: 2;
        cursor: pointer;
        line-height: 255px;
        text-align: center;
        transition: all 500ms;
      }

      .centerHover.open {
        opacity: 0;
        pointer-events: none;
      }

      .centerHover:hover span {
        color: #ddd;
      }

      .centerHover:hover span:before {
        background-color: #ddd;
      }

      .centerHover:hover span:after {
        background-color: #ddd;
      }

      .centerHover span {
        color: #666;
        font-family: serif;
        font-size: 18px;
        position: relative;
        transition: all 500ms;
      }

      .centerHover span:before {
        content: "";
        display: inline-block;
        height: 1px;
        width: 16px;
        margin-right: 12px;
        margin-bottom: 4px;
        background-color: #666;
        transition: all 500ms;
      }

      .centerHover span:after {
        content: "";
        display: inline-block;
        height: 1px;
        width: 16px;
        margin-left: 12px;
        margin-bottom: 4px;
        background-color: #666;
        transition: all 500ms;
      }

      canvas {
        position: relative;
        z-index: 1;
        width: 100%;
        height: 100%;
        margin: auto;
      }

      #blackhole {
        height: 100%;
        width: 100%;
        position: relative;
        display: flex;
      }

      .centerHover {
        width: 255px;
        height: 255px;
        background-color: transparent;
        border-radius: 50%;
        position: absolute;
        left: 50%;
        top: 50%;
        margin-top: -128px;
        margin-left: -128px;
        z-index: 2;
        cursor: pointer;
        line-height: 255px;
        text-align: center;
        transition: all 500ms;
      }

      .centerHover.open {
        opacity: 0;
        pointer-events: none;
      }

      .centerHover:hover span {
        color: #ddd;
      }

      .centerHover:hover span:before {
        background-color: #ddd;
      }

      .centerHover:hover span:after {
        background-color: #ddd;
      }

      .centerHover span {
        color: #666;
        font-family: serif;
        font-size: 18px;
        position: relative;
        transition: all 500ms;
      }

      .centerHover span:before {
        content: "";
        display: inline-block;
        height: 1px;
        width: 16px;
        margin-right: 12px;
        margin-bottom: 4px;
        background-color: #666;
        transition: all 500ms;
      }

      .centerHover span:after {
        content: "";
        display: inline-block;
        height: 1px;
        width: 16px;
        margin-left: 12px;
        margin-bottom: 4px;
        background-color: #666;
        transition: all 500ms;
      }

      canvas {
        position: relative;
        z-index: 1;
        width: 100%;
        height: 100%;
        margin: auto;
      }
    </style>
  </head>
  <body>
    <div id="blackhole">
      <div class="centerHover"><span>点击进入</span></div>
    </div>
    <script>
      function blackhole(element) {
        const container = document.querySelector(element);
        const h = container.offsetHeight;
        const w = container.offsetWidth;
        const cw = w;
        const ch = h;
        const maxorbit = 255;
        const centery = ch / 2;
        const centerx = cw / 2;

        const startTime = new Date().getTime();
        let currentTime = 0;

        const stars = [];
        let collapse = false;
        let expanse = false;
        let returning = false;

        const canvas = document.createElement("canvas");
        canvas.width = cw;
        canvas.height = ch;
        container.appendChild(canvas);
        const context = canvas.getContext("2d");

        context.globalCompositeOperation = "multiply";

        function setDPI(canvas, dpi) {
          if (!canvas.style.width) canvas.style.width = canvas.width + "px";
          if (!canvas.style.height) canvas.style.height = canvas.height + "px";

          const scaleFactor = dpi / 96;
          canvas.width = Math.ceil(canvas.width * scaleFactor);
          canvas.height = Math.ceil(canvas.height * scaleFactor);
          const ctx = canvas.getContext("2d");
          ctx.scale(scaleFactor, scaleFactor);
        }

        function rotate(cx, cy, x, y, angle) {
          const radians = angle;
          const cos = Math.cos(radians);
          const sin = Math.sin(radians);
          const nx = cos * (x - cx) + sin * (y - cy) + cx;
          const ny = cos * (y - cy) - sin * (x - cx) + cy;
          return [nx, ny];
        }

        setDPI(canvas, 192);

        class Star {
          constructor() {
            const rands = [];
            rands.push(Math.random() * (maxorbit / 2) + 1);
            rands.push(Math.random() * (maxorbit / 2) + maxorbit);

            this.orbital = rands.reduce((p, c) => p + c, 0) / rands.length;

            this.x = centerx;
            this.y = centery + this.orbital;

            this.yOrigin = centery + this.orbital;

            this.speed =
              ((Math.floor(Math.random() * 2.5) + 1.5) * Math.PI) / 180;
            this.rotation = 0;
            this.startRotation =
              ((Math.floor(Math.random() * 360) + 1) * Math.PI) / 180;

            this.id = stars.length;

            this.collapseBonus = this.orbital - maxorbit * 0.7;
            if (this.collapseBonus < 0) {
              this.collapseBonus = 0;
            }

            this.color = "rgba(255,255,255," + (1 - this.orbital / 255) + ")";

            this.hoverPos = centery + maxorbit / 2 + this.collapseBonus;
            this.expansePos =
              centery +
              (this.id % 100) * -10 +
              (Math.floor(Math.random() * 20) + 1);

            this.prevR = this.startRotation;
            this.prevX = this.x;
            this.prevY = this.y;

            this.originalY = this.yOrigin;

            stars.push(this);
          }

          draw() {
            if (!expanse && !returning) {
              this.rotation = this.startRotation + currentTime * this.speed;
              if (!collapse) {
                if (this.y > this.yOrigin) {
                  this.y -= 2.5;
                }
                if (this.y < this.yOrigin - 4) {
                  this.y += (this.yOrigin - this.y) / 10;
                }
              } else {
                this.trail = 1;
                if (this.y > this.hoverPos) {
                  this.y -= (this.hoverPos - this.y) / -5;
                }
                if (this.y < this.hoverPos - 4) {
                  this.y += 2.5;
                }
              }
            } else if (expanse && !returning) {
              this.rotation =
                this.startRotation + currentTime * (this.speed / 2);
              if (this.y > this.expansePos) {
                this.y -= Math.floor(this.expansePos - this.y) / -80;
              }
            } else if (returning) {
              this.rotation = this.startRotation + currentTime * this.speed;
              if (Math.abs(this.y - this.originalY) > 2) {
                this.y += (this.originalY - this.y) / 50;
              } else {
                this.y = this.originalY;
                this.yOrigin = this.originalY;
              }
            }

            context.save();
            context.fillStyle = this.color;
            context.strokeStyle = this.color;
            context.beginPath();
            const oldPos = rotate(
              centerx,
              centery,
              this.prevX,
              this.prevY,
              -this.prevR
            );
            context.moveTo(oldPos[0], oldPos[1]);
            context.translate(centerx, centery);
            context.rotate(this.rotation);
            context.translate(-centerx, -centery);
            context.lineTo(this.x, this.y);
            context.stroke();
            context.restore();

            this.prevR = this.rotation;
            this.prevX = this.x;
            this.prevY = this.y;
          }
        }

        const centerHover = document.querySelector(".centerHover");

        centerHover.addEventListener("click", function () {
          collapse = false;
          expanse = true;
          returning = false;
          this.classList.add("open");

          setTimeout(() => {
            expanse = false;
            returning = true;

            setTimeout(() => {
              returning = false;
              this.classList.remove("open");
            }, 2000);
          }, 5000);
        });

        centerHover.addEventListener("mouseover", function () {
          if (expanse === false) {
            collapse = true;
          }
        });

        centerHover.addEventListener("mouseout", function () {
          if (expanse === false) {
            collapse = false;
          }
        });

        function loop() {
          const now = new Date().getTime();
          currentTime = (now - startTime) / 50;

          context.fillStyle = "rgba(25,25,25,0.2)";
          context.fillRect(0, 0, cw, ch);

          for (let i = 0; i < stars.length; i++) {
            if (stars[i] !== undefined) {
              stars[i].draw();
            }
          }

          requestAnimationFrame(loop);
        }

        function init() {
          context.fillStyle = "rgba(25,25,25,1)";
          context.fillRect(0, 0, cw, ch);
          for (let i = 0; i < 2500; i++) {
            new Star();
          }
          loop();
        }

        init();
      }

      document.addEventListener("DOMContentLoaded", () => {
        blackhole("#blackhole");
      });
    </script>
  </body>
</html>