如何用p5js实现椭圆沿多点线段移动及鼠标悬停精准显示?
单椭圆移动实现方案
你现在出现多个椭圆的核心原因有两个:
- p5.js默认画布是叠加绘制模式,之前绘制的椭圆不会自动消失
- 你的绘图逻辑放在了点位遍历的循环内,每个点位都会触发一次椭圆绘制
修正方法如下:
- 调整
draw()函数的执行顺序:每帧开头先调用background(255)清空整个画布,之后按顺序绘制坐标轴、所有线段,最后仅绘制1个当前位置的椭圆,保证每帧只保留最新的椭圆 - 提前预处理CSV数据,把所有日期对应的画布坐标整合成一个全局的路径数组,不要在遍历点位的循环里做绘图操作
- 全局单独维护动画进度变量
pct,每帧仅计算一次当前进度对应的坐标,再绘制单个椭圆
鼠标悬停坐标映射方案
map()方法的核心逻辑是把一个数值从原始区间线性映射到目标区间,你可以按以下步骤实现悬停定位:
- 先确定图表的X轴边界:比如图表左侧起始X坐标为
chartXMin,右侧结束X坐标为chartXMax,对应日期范围是当月第一天dayMin到当月最后一天dayMax - 把鼠标X坐标映射为对应日期:
const hoverDay = constrain(map(mouseX, chartXMin, chartXMax, dayMin, dayMax), dayMin, dayMax),这里用constrain限制日期不会超出当月范围 - 遍历路径点找到该日期对应的线段区间,计算区间内的百分比:
const segmentPct = map(hoverDay, 区间左端点日期, 区间右端点日期, 0, 1) - 用百分比计算对应Y坐标,直接在该位置绘制椭圆即可
核心优化代码示例
// 全局变量定义 let path = []; // 预处理后的路径点数组,每个元素结构:{x:画布X坐标, y:画布Y坐标, day:对应日期} let animPct = 0; // 动画进度变量 const chartXMin = 50; // 替换为你的图表左侧X坐标 const chartXMax = 750; // 替换为你的图表右侧X坐标 const dayMin = 1; // 替换为你的数据起始日期 const dayMax = 31; // 替换为你的数据结束日期 function preload() { // 在这里加载CSV数据,提前计算所有点的画布坐标存入path数组,避免每帧重复计算 } function draw() { // 每帧先清屏 background(255); // 绘制所有底图元素:坐标轴、全部线段 drawAllChartLines(); // ===== 自动动画模式(要使用悬停模式就注释这段)===== animPct += 0.005; // 数值越大动画速度越快 if (animPct > 1) animPct = 0; // 播放完从头循环 const animPos = getPosByPct(animPct); drawEllipse(animPos.x, animPos.y, 10); // ===== 鼠标悬停模式(要使用动画模式就注释这段)===== // if (mouseX >= chartXMin && mouseX <= chartXMax) { // const hoverDay = constrain(map(mouseX, chartXMin, chartXMax, dayMin, dayMax), dayMin, dayMax); // const hoverPos = getPosByDay(hoverDay); // drawEllipse(hoverPos.x, hoverPos.y, 10); // } } // 根据全局动画进度获取当前坐标 function getPosByPct(pct) { const totalSeg = path.length - 1; const segIndex = floor(pct * totalSeg); const segPct = pct * totalSeg - segIndex; return { x: lerp(path[segIndex].x, path[segIndex+1].x, segPct), y: lerp(path[segIndex].y, path[segIndex+1].y, segPct) } } // 根据输入日期获取对应路径上的坐标 function getPosByDay(day) { for (let i = 0; i < path.length - 1; i++) { if (path[i].day <= day && path[i+1].day >= day) { const segPct = map(day, path[i].day, path[i+1].day, 0, 1); return { x: lerp(path[i].x, path[i+1].x, segPct), y: lerp(path[i].y, path[i+1].y, segPct) } } } return day < path[0].day ? path[0] : path[path.length - 1]; } // 你的原有绘图方法保留即可 this.drawEllipse = function(ellipseX, ellipseY, size) { fill('black'); ellipse(ellipseX, ellipseY, size); };
原有代码问题修正
- 你重复声明了
beginY变量,会导致变量值异常,删掉多余的声明即可 - 不要在点位遍历的循环内执行绘图操作,所有绘图逻辑统一放在
draw()函数中按顺序执行
内容的提问来源于stack exchange,提问作者soyNstacks
相关产品推荐
相关产品推荐

