如何让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
相关产品推荐
相关产品推荐

