You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Canvas下方元素可点击且不影响光标轨迹功能?

解决方案:让Canvas光标轨迹与下方元素交互共存

核心问题是pointer-events: none会让Canvas失去所有鼠标事件响应,导致轨迹绘制失效。解决思路是将鼠标事件绑定到整个文档,而非Canvas本身,同时保留Canvas的穿透属性。

步骤1:修改Canvas的CSS

添加pointer-events: none确保事件穿透,同时用fixed定位实现全屏覆盖:

#trail{
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none; /* 关键:让Canvas不拦截鼠标事件 */
  border: 0;
}

a:hover{
  color: black;
}

步骤2:调整事件绑定目标

把鼠标/触摸事件从Canvas移到document上,这样无论鼠标在哪都能捕获到位置,同时不影响下方元素交互:

// 替换原enableListeners函数
function enableListeners() {

  // 鼠标移动事件绑定到document
  $(document).on('mousemove', function(e) {
    if (frame === drawEveryFrame) {
      // 全屏Canvas下,pageX/Y直接对应Canvas坐标,无需计算偏移
      addPoint(e.pageX, e.pageY);
      frame = 0;
    }
    frame++;
  });

  // 触摸移动事件同理绑定到document
  $(document).on('touchmove', function(e) {
    e.preventDefault(); // 避免页面滚动干扰
    var touch = e.touches[0];
    addPoint(touch.pageX, touch.pageY);
  });
}

完整修改后的代码

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="trail"></canvas>
<a href="example.com"><h1>hello world</h1></a>

CSS

#trail{
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  border: 0;
}

a:hover{
  color: black;
}

JavaScript

const LINE_DURATION = 2;
const LINE_WIDTH_START = 10;

$(document).ready(function() {
  enableDrawingCanvas();
  resizeCanvas(window.innerWidth, window.innerHeight);
  // 监听窗口 resize,保持Canvas全屏
  $(window).on('resize', function() {
    resizeCanvas(window.innerWidth, window.innerHeight);
  });
});

//////////////////////////
// Variable definitions //
//////////////////////////
var active = true;

var canvas;
var context;

var newWidth = 1000;
var newHeight = 800;

var mode = 2;
var pathMode = 1;
var spread = 2;

var lineColor = 'rgb(255, 88, 0)';
var lineDuration = LINE_DURATION;
var lineFadeLinger = 1;
var lineWidthStart = LINE_WIDTH_START;
var fadeDuration = 50;
var drawEveryFrame = 1; // Only adds a Point after these many 'mousemove' events

var clickCount = 0;
var frame = 0;

var flipNext = true;

var points = new Array();

///////////////////////
// Program functions //
///////////////////////

// Find canvas reference & enable listeners
function enableDrawingCanvas() {
  if (canvas === undefined) {
    canvas = document.getElementById('trail');
    context = canvas.getContext('2d');
    enableListeners();
    init();
  }
}

// Initialize animation start
function init() {
  draw();
}

// Draw current state
function draw() {
  if (active) {
    animatePoints();
    window.requestAnimFrame(draw);
  }
}

// Update mouse positions
function animatePoints() {
  context.clearRect(0, 0, context.canvas.width, context.canvas.height);

  var duration = lineDuration * 1000 / 60;
  var point, lastPoint;

  if (pathMode === 2) {
    context.beginPath();
  }

  for (var i = 0; i < points.length; i++) {
    point = points[i];

    if (points[i - 1] !== undefined) {
      lastPoint = points[i - 1];
    } else {
      lastPoint = points[i];
    }

    point.lifetime += 1;

    if (point.lifetime > duration) {
      points.splice(i, 1);
      continue;
    }

    // Begin drawing stuff!
    var inc = (point.lifetime / duration); // 0 to 1 over lineDuration
    var dec = 1 - inc;

    var spreadRate;
    if (spread === 1) {
      spreadRate = lineWidthStart / (point.lifetime * 2);
    } // Lerp Decrease
    if (spread === 2) {
      spreadRate = lineWidthStart * (1 - inc);
    } // Linear Decrease

    var fadeRate = dec;

    //context.strokeStyle = lineColor;
    context.lineJoin = "round";
    context.lineWidth = spreadRate;
    context.strokeStyle = 'rgb(' + Math.floor(255) + ',' +
      Math.floor(88 - (0 * dec)) + ',' +
      Math.floor(0 - (0 * dec)) + ')';

    var distance = Point.distance(lastPoint, point);
    var midpoint = Point.midPoint(lastPoint, point);
    var angle = Point.angle(lastPoint, point);

    if (pathMode === 1) {
      context.beginPath();
    }

    if (mode === 1) {
      context.arc(midpoint.x, midpoint.y, distance / 2, angle, (angle + Math.PI), point.flip);
    }

    if (mode === 2) {
      context.moveTo(lastPoint.x, lastPoint.y);
      context.lineTo(point.x, point.y);
    }

    if (pathMode === 1) {
      context.stroke();
      context.closePath();
    }
  }

  if (pathMode === 2) {
    context.stroke();
    context.closePath();
  }

  //if (points.length > 0) { console.log(spreadRate + "|" + points.length + " points alive."); }
}

function addPoint(x, y) {
  flipNext = !flipNext;
  var point = new Point(x, y, 0, flipNext);
  points.push(point);
}

//////////////////////////////
// Less Important functions //
//////////////////////////////

// RequestAnimFrame definition
window.requestAnimFrame = (function(callback) {
  return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
    function(callback) {
      window.setTimeout(callback, 1000 / 60);
    };
})();

// Update canvas dimensions based on input
function resizeCanvas(w, h) {
  if (context !== undefined) {
    context.canvas.width = w;
    context.canvas.height = h;

    newWidth = w;
    newHeight = h;
  }
}

// Listeners for mouse and touch events
function enableListeners() {

  //********* Mouse Listeners *********//
  $(document).on('mousemove', function(e) {
    if (frame === drawEveryFrame) {
      addPoint(e.pageX, e.pageY);
      frame = 0;
    }
    frame++;
  });

  //********* Touch Listeners *********//
  $(document).on('touchmove', function(e) {
    e.preventDefault();
    var touch = e.touches[0];
    addPoint(touch.pageX, touch.pageY);
  });
}


// POINT CLASS
// Cartersian location of where mouse location
// was previously at. 
// Used to draw arcs between Points.
var Point = class Point {

  // Define class constructor
  constructor(x, y, lifetime, flip) {
    this.x = x;
    this.y = y;
    this.lifetime = lifetime;
    this.flip = flip;
  }

  // Get the distance between a & b
  static distance(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.sqrt(dx * dx + dy * dy);
  }

  // Get the mid point between a & b
  static midPoint(a, b) {
    const mx = a.x + (b.x - a.x) * 0.5;
    const my = a.y + (b.y - a.y) * 0.5;

    return new Point(mx, my);
  }

  // Get the angle between a & b
  static angle(a, b) {
    const dx = a.x - b.x;
    const dy = a.y - b.y;

    return Math.atan2(dy, dx);
  }

  // Simple getter for printing
  get pos() {
    return this.x + "," + this.y;
  }
}

原理说明

  • pointer-events: none让Canvas完全不响应鼠标事件,所有点击、hover操作直接穿透到下方元素,保证站点原有功能正常。
  • 将事件绑定到document后,无论鼠标在页面哪个位置,都能捕获到移动/触摸事件,从而获取坐标绘制轨迹,实现视觉效果与交互功能的共存。

内容的提问来源于stack exchange,提问作者Z2r

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 21:45:38