在任意网页中加载鼠标波浪特效的油猴脚本

网上冲浪🏄🏻看到 狂奔滴小马的博客里的一个特效,他博客源码里是一个TS写的插件,我通过AI把它转成了js的版本如下:

class Wave {
  constructor({ phase = 0, offset = 0, frequency = 0.001, amplitude = 1 } = {}) {
    this.phase = phase;
    this.offset = offset;
    this.frequency = frequency;
    this.amplitude = amplitude;
  }

  update() {
    this.phase += this.frequency;
    return this.offset + Math.sin(this.phase) * this.amplitude;
  }
}

class Node {
  constructor() {
    this.x = 0;
    this.y = 0;
    this.vy = 0;
    this.vx = 0;
  }
}

class Line {
  constructor({ spring }, pos) {
    this.spring = spring + 0.1 * Math.random() - 0.05;
    this.friction = E.friction + 0.01 * Math.random() - 0.005;
    this.nodes = [];
    this.pos = pos;
    for (let i = 0; i < E.size; i++) {
      const node = new Node();
      node.x = this.pos.x;
      node.y = this.pos.y;
      this.nodes.push(node);
    }
  }

  update() {
    let spring = this.spring;
    let node = this.nodes[0];

    node.vx += (this.pos.x - node.x) * spring;
    node.vy += (this.pos.y - node.y) * spring;

    for (let i = 0; i < this.nodes.length; i++) {
      node = this.nodes[i];
      if (i > 0) {
        const prevNode = this.nodes[i - 1];
        node.vx += (prevNode.x - node.x) * spring;
        node.vy += (prevNode.y - node.y) * spring;
        node.vx += prevNode.vx * E.dampening;
        node.vy += prevNode.vy * E.dampening;
      }
      node.vx *= this.friction;
      node.vy *= this.friction;
      node.x += node.vx;
      node.y += node.vy;
      spring *= E.tension;
    }
  }

  draw(ctx) {
    let currNode, nextNode;
    let x = this.nodes[0].x;
    let y = this.nodes[0].y;

    ctx.beginPath();
    ctx.moveTo(x, y);

    for (let i = 1; i < this.nodes.length - 2; i++) {
      currNode = this.nodes[i];
      nextNode = this.nodes[i + 1];
      x = 0.5 * (currNode.x + nextNode.x);
      y = 0.5 * (currNode.y + nextNode.y);
      ctx.quadraticCurveTo(currNode.x, currNode.y, x, y);
    }
    currNode = this.nodes[this.nodes.length - 2];
    nextNode = this.nodes[this.nodes.length - 1];
    ctx.quadraticCurveTo(currNode.x, currNode.y, nextNode.x, nextNode.y);

    ctx.stroke();
    ctx.closePath();
  }
}

const E = {
  friction: 0.5,
  trails: 20,
  size: 50,
  dampening: 0.25,
  tension: 0.98,
};

function renderCanvas() {
  const canvas = document.getElementById("canvas");
  const ctx = canvas.getContext("2d");
  let lines = [];
  const pos = { x: 0, y: 0 };
  const wave = new Wave({
    phase: Math.random() * 2 * Math.PI,
    amplitude: 85,
    frequency: 0.0015,
    offset: 285,
  });
  let running = true;
  let frame = 1;

  function resizeCanvas() {
    ctx.canvas.width = window.innerWidth;
    ctx.canvas.height = window.innerHeight;
  }

  resizeCanvas();

  function animate() {
    if (running) {
      ctx.globalCompositeOperation = "source-over";
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
      ctx.globalCompositeOperation = "lighter";
      ctx.strokeStyle = `hsla(${Math.round(wave.update())},90%,50%,0.25)`;
      ctx.lineWidth = 1;

      for (let i = 0; i < E.trails; i++) {
        const line = lines[i];
        line.update();
        line.draw(ctx);
      }

      frame++;
      window.requestAnimationFrame(animate);
    }
  }

  function bindMouseMove(event) {
    function drawLine() {
      lines = [];
      for (let i = 0; i < E.trails; i++) {
        lines.push(new Line({ spring: 0.45 + (i / E.trails) * 0.025 }, pos));
      }
    }

    function move(e) {
      if (e.touches) {
        pos.x = e.touches[0].pageX;
        pos.y = e.touches[0].pageY;
      } else {
        pos.x = e.clientX;
        pos.y = e.clientY;
      }
      e.preventDefault();
    }

    function start(e) {
      if (e.touches.length === 1) {
        pos.x = e.touches[0].pageX;
        pos.y = e.touches[0].pageY;
      }
    }

    document.removeEventListener("mousemove", bindMouseMove);
    document.removeEventListener("touchstart", bindMouseMove);
    document.addEventListener("mousemove", move);
    document.addEventListener("touchmove", move);
    document.addEventListener("touchstart", start);

    move(event);
    drawLine();
    animate();
  }

  document.addEventListener("mousemove", bindMouseMove);
  document.addEventListener("touchstart", bindMouseMove);
  window.addEventListener("resize", resizeCanvas);
}

// Call this function in your HTML file
// Example usage:
// <canvas id="canvas"></canvas>
// <script src="wave-effect.js"></script>
// <script>renderCanvas();</script>

油猴脚本如下:
需要动态的创建一个canvas的dom节点浮在最上层,但不能影响交互

// ==UserScript==
// @name         Wave Effect
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  在任意网页上添加鼠标波浪特效
// @author       me
// @match        *://*/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  // 创建 Canvas 元素并添加到页面
  const canvas = document.createElement("canvas");
  canvas.id = "wave-effect-canvas";
  canvas.style.position = "fixed";
  canvas.style.top = "0";
  canvas.style.left = "0";
  canvas.style.width = "100%";
  canvas.style.height = "100%";
  canvas.style.pointerEvents = "none"; // 确保不会影响页面交互
  canvas.style.zIndex = "9999"; // 覆盖其他内容
  document.body.appendChild(canvas);

  const ctx = canvas.getContext("2d");

  // 调整 Canvas 尺寸
  function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }

  resizeCanvas();
  window.addEventListener("resize", resizeCanvas);

  // 波浪特效逻辑
  class Wave {
    constructor({ phase = 0, offset = 0, frequency = 0.001, amplitude = 1 } = {}) {
      this.phase = phase;
      this.offset = offset;
      this.frequency = frequency;
      this.amplitude = amplitude;
    }

    update() {
      this.phase += this.frequency;
      return this.offset + Math.sin(this.phase) * this.amplitude;
    }
  }

  class Node {
    constructor() {
      this.x = 0;
      this.y = 0;
      this.vy = 0;
      this.vx = 0;
    }
  }

  class Line {
    constructor({ spring }, pos) {
      this.spring = spring + 0.1 * Math.random() - 0.05;
      this.friction = E.friction + 0.01 * Math.random() - 0.005;
      this.nodes = [];
      this.pos = pos;
      for (let i = 0; i < E.size; i++) {
        const node = new Node();
        node.x = this.pos.x;
        node.y = this.pos.y;
        this.nodes.push(node);
      }
    }

    update() {
      let spring = this.spring;
      let node = this.nodes[0];

      node.vx += (this.pos.x - node.x) * spring;
      node.vy += (this.pos.y - node.y) * spring;

      for (let i = 0; i < this.nodes.length; i++) {
        node = this.nodes[i];
        if (i > 0) {
          const prevNode = this.nodes[i - 1];
          node.vx += (prevNode.x - node.x) * spring;
          node.vy += (prevNode.y - node.y) * spring;
          node.vx += prevNode.vx * E.dampening;
          node.vy += prevNode.vy * E.dampening;
        }
        node.vx *= this.friction;
        node.vy *= this.friction;
        node.x += node.vx;
        node.y += node.vy;
        spring *= E.tension;
      }
    }

    draw(ctx) {
      let currNode, nextNode;
      let x = this.nodes[0].x;
      let y = this.nodes[0].y;

      ctx.beginPath();
      ctx.moveTo(x, y);

      for (let i = 1; i < this.nodes.length - 2; i++) {
        currNode = this.nodes[i];
        nextNode = this.nodes[i + 1];
        x = 0.5 * (currNode.x + nextNode.x);
        y = 0.5 * (currNode.y + nextNode.y);
        ctx.quadraticCurveTo(currNode.x, currNode.y, x, y);
      }
      currNode = this.nodes[this.nodes.length - 2];
      nextNode = this.nodes[this.nodes.length - 1];
      ctx.quadraticCurveTo(currNode.x, currNode.y, nextNode.x, nextNode.y);

      ctx.stroke();
      ctx.closePath();
    }
  }

  const E = {
    friction: 0.5,
    trails: 20,
    size: 50,
    dampening: 0.25,
    tension: 0.98,
  };

  let lines = [];
  const pos = { x: 0, y: 0 };
  const wave = new Wave({
    phase: Math.random() * 2 * Math.PI,
    amplitude: 85,
    frequency: 0.0015,
    offset: 285,
  });
  let running = true;

  function animate() {
    if (running) {
      ctx.globalCompositeOperation = "source-over";
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.globalCompositeOperation = "lighter";
      ctx.strokeStyle = `hsla(${Math.round(wave.update())},90%,50%,0.25)`;
      ctx.lineWidth = 1;

      for (let i = 0; i < E.trails; i++) {
        const line = lines[i];
        line.update();
        line.draw(ctx);
      }

      requestAnimationFrame(animate);
    }
  }

  function initializeLines() {
    lines = [];
    for (let i = 0; i < E.trails; i++) {
      lines.push(new Line({ spring: 0.45 + (i / E.trails) * 0.025 }, pos));
    }
  }

  document.addEventListener("mousemove", (e) => {
    pos.x = e.clientX;
    pos.y = e.clientY;
    if (lines.length === 0) {
      initializeLines();
      animate();
    }
  });

  document.addEventListener("touchmove", (e) => {
    if (e.touches && e.touches.length === 1) {
      pos.x = e.touches[0].pageX;
      pos.y = e.touches[0].pageY;
      if (lines.length === 0) {
        initializeLines();
        animate();
      }
    }
  });
})();

尽管花里胡哨就完事儿了~

posted @ 2025-05-01 18:41  CoderWGB  阅读(33)  评论(0)    收藏  举报