Canvas如何判断点在形状内及内置API性能详解

这篇文章主要为大家介绍了Canvas如何判断点在形状内及内置API性能详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

背景

起因是有一个项目,需要在同一个canvas中渲染一批几何图形,当鼠标移动到其中某一个图形中,对这个形状高亮处理。基本实现方式是监听mousemove事件,回调中传入当前鼠标的位置,同时遍历所有图形,判断点是否在这个形状中,找到当前选中的元素并重新渲染canvas。

const canvas = document.getElementById('my-canvas'); const ctx = canvas.getContext('2d'); canvas.addEventListener('mousemove', function(event) { const x = event.clientX - canvas.offsetLeft; const y = event.clientY - canvas.offsetTop; // Check each polygon to see if the mouse is inside for (let i = 0; i 

当图形的量级持续上升,意味着JS逻辑执行时间同步增加,鼠标移动过快必然出现卡顿(低FPS)。

这个问题有很多优化的角度:

  • 降低鼠标事件执行的频率,即节流;
  • 分区判断,减少需要遍历的多边形数量;
  • 优化判断点是否在形状中的逻辑 isPointInside()

我初步实现的isPointInside()主要依赖几何坐标的计算,这里主要针对矩形、圆形、多边形实现:

/** * 判断点是否在形状内 * @param shape * @param point * @param type * @returns */ export const isPointInside = ( shape: IRect | ICircle | IPolygon, point: IPoint, type: EElementType, ): boolean => { if (!shape || !point) return false; switch (type) { case EElementType.Rect: { const rect = shape as IRect; return ( rect.x <= point.x && rect.x + rect.width >= point.x && rect.y <= point.y && rect.y + rect.height >= point.y ); } case EElementType.Circle: { const circle = shape as ICircle; return ( Math.sqrt( Math.pow(point.x - circle.x, 2) + Math.pow(point.y - circle.y, 2), ) <= circle.radius ); } case EElementType.Polygon: { const polygon = shape as IPolygon; // 将多边形划分为若干个三角形 for (let i = 1; i  0 && cross1 * cross3 > 0) { return true; } } return false; } default: return false; } }; 

但当我把这个问题抛给ChatGpt时发现了另一种可能性:

const canvas = document.getElementById('my-canvas'); const ctx = canvas.getContext('2d'); canvas.addEventListener('mousemove', function(event) { const x = event.clientX - canvas.offsetLeft; const y = event.clientY - canvas.offsetTop; // Check each polygon to see if the mouse is inside for (let i = 0; i 

他推荐了Canvas内置的API Context.isPointInPath(path,x,y)判断一个点是否被一个路径包围。

MDN这样介绍:developer.mozilla.org/zh-CN/docs/…

这个API此前我并未留意,当我把自己的方法同时转达时,ChatGpt这样评价和对比两种方法:

The internal APIisPointInPath() is likely to be faster than the custom implementation of isPointInside()that you have provided, as it is a built-in method of the canvas API optimized for detecting whether a point is inside a path.

isPointInPath() is optimized for performance and is likely to be faster than your implementation, which calculates the shape of the annotation and checks if the point is inside. However, the exact performance difference depends on the complexity of the shapes and the number of annotations you have on the canvas.

Therefore, it's recommended to use isPointInPath() for performance reasons, especially if you have a large number of annotations. You can create a path for each annotation, and then check if the mouse pointer is inside any of the paths using isPointInPath()

出于性能考虑内置方法更好?为什么好?好到什么程度?

于是就有个接下来的实验。

测试案例

const canvas = document.createElement('canvas'); document.body.appendChild(canvas); const count = 1000; const width = 1500; const height = 1500; canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); // Create random shapes const shapes = []; const createPathFromPoints = (points) => { const path = new Path3D(); path.moveTo(points[0].x, points[0].y); for (let i = 1; i  { const path = new Path3D(); path.arc(center.x, center.y, radius, 0, 2 * Math.PI); path.closePath(); return path; }; for (let i = 0; i  { ctx.fillStyle = randomColor(); switch (type) { case 'rect': ctx.fillRect(shape.x, shape.y, shape.width, shape.height); break; case 'circle': ctx.beginPath(); ctx.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); ctx.fill(); break; case 'polygon': ctx.beginPath(); ctx.moveTo(shape[0].x, shape[0].y); for (let i = 1; i  { const point = { x: e.clientX - canvas.offsetLeft, y: e.clientY - canvas.offsetTop }; // Method 1 const start1 = performance.now(); const result1 = shapes.findIndex(({ shape, type }) => { return isPointInside(shape, point, type); }); const end1 = performance.now(); // Method 2 const start2 = performance.now(); const result2 = shapes.findIndex(({ path }) => { return ctx.isPointInPath(path, point.x, point.y); }) const end2 = performance.now(); if ((end1 - start1) <(end2 - start2)) { customWin++; } else if ((end1 - start1) > (end2 - start2)) { builtinWin++; } renderAllShapes(shapes); console.log(result1, result2); console.log(end1 - start1, end2 - start2); console.log(customWin, builtinWin); }); 

上述代码canvas中随机创建了count个形状,分别使用两种方法判断鼠标hover形状,采用performance.now()毫秒级的记录执行时间。

同时执行两种方法,当count=1000时,FPS > 55正常使用,但是当count=10000时,FPS <20,说明批量判断存在性能瓶颈。

Count自定义内置
10000.0300.150
20000.0380.243
30000.0600.310

根据控制台打印,两种方法当前hover元素的判断一致,但执行时间上,90%的情况下,自定义实现的isPointInside()优于内置APIisPointInPath()

所以,ChatGpt可以不负责任的讲结论,内置API也不一定是最优解,实践是唯一标准。

以上就是Canvas如何判断点在形状内及内置API性能详解的详细内容,更多关于Canvas内置API性能的资料请关注0133技术站其它相关文章!

以上就是Canvas如何判断点在形状内及内置API性能详解的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » JavaScript 教程