Canvas提升性能十点注意事项

本文介绍了提升Canvas性能的十个关键点,包括预渲染、减少API调用、避免频繁改变Canvas状态、限制重绘区域、使用多层画布、避免阴影、正确清除画布、进行整数像素操作、利用requestAnimationFrame做动画以及利用Web Worker和引擎优化计算。

你还在抱怨自己写的canvas demo徘徊在10帧以下吗?你还在烦恼打开自己写的应用就听见CUP风扇转吗?你正在写一个javascript Canvas库吗?那么下面九点就是你必须知道的!

一.预渲染

错误代码:

      var canvas = document.getElementById("myCanvas");
      var context = this.canvas.getContext('2d');
      var drawAsync = eval(Jscex.compile("async", function () {
          while (true) {
              drawMario(context);
              $await(Jscex.Async.sleep(1000));
          }
      }))
      drawAsync().start();
正确代码:

      var canvas = document.getElementById("myCanvas");
      var context = this.canvas.getContext('2d');
      var m_canvas = document.createElement('canvas');
      m_canvas.width = 64;
      m_canvas.height = 64;
      var m_context = m_canvas.getContext('2d');
      drawMario(m_context);
      var drawAsync = eval(Jscex.compile("async", function () {
          while (true) {
              context.drawImage(m_canvas, 0, 0);
              $await(Jscex.Async.sleep(1000));
          }
      }))
      drawAsync().start();
这里m_canvas的宽度和高度控制得越小越好。

二.尽量少调用canvasAPI

错误代码:

   
    for (var i = 0; i < points.length - 1; i++) {
          var p1 = points[i];
          var p2 = points[i + 1];
          context.beginPath();
          context.moveTo(p1.x, p1.y);
          context.lineTo(p2.x, p2.y);
          context.stroke();
      } 
正确代码:

      context.beginPath();
      for (var i = 0; i < points.length - 1; i++) {
          var p1 = points[i];
          var p2 = points[i + 1];
          context.moveTo(p1.x, p1.y);
          context.lineTo(p2.x, p2.y);
      }
      context.stroke();
三.尽量少改变CANVAS状态

错误代码:

      for (var i = 0; i < STRIPES; i++) {
          context.fillStyle = (i % 2 ? COLOR1 : COLOR2);
          context.fillRect(i * GAP, 0, GAP, 480);
      } 
正确代码:

      context.fillStyle = COLOR1;
      for (var i = 0; i < STRIPES / 2; i++) {
          context.fillRect((i * 2) * GAP, 0, GAP, 480);
      }
      context.fillStyle = COLOR2;
      for (var i = 0; i < STRIPES / 2; i++) {
          context.fillRect((i * 2 + 1) * GAP, 0, GAP, 480);
      }
四.重新渲染的范围尽量小

错误代码:

  context.fillRect(0, 0, canvas.width, canvas.height); 
正确代码:

      context.fillRect(20, 20, 100, 100);
五.复杂场景使用多层画布

 <canvas  width="600" height="400" style="position: absolute; z-index: 0">
</canvas>
<canvas  width="600" height="400" style="position: absolute; z-index: 1">
</canvas>
六.不要使用阴影

      context.shadowOffsetX = 5;
      context.shadowOffsetY = 5;
      context.shadowBlur = 4;
      context.shadowColor = 'rgba(255, 0, 0, 0.5)';
      context.fillRect(20, 20, 150, 100);
七.清除画布

详细性能差别: 
http://simonsarris.com/blog/346-how-you-clear-your-canvas-matters 
一般情况下:clearRect的性能优于fillRect优于canvas.width = canvas.width;

八.像素级别操作尽量用整数

几种取整数的方法:

      rounded = (0.5 + somenum) | 0;
      rounded = ~ ~(0.5 + somenum);
      rounded = (0.5 + somenum) << 0;
九.使用requestAnimationFrame制作游戏或动画


        (function () {
            var lastTime = 0;
            var vendors = ['ms', 'moz', 'webkit', 'o'];
            for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
                window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
                window.cancelAnimationFrame =
          window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
            }

            if (!window.requestAnimationFrame)
                window.requestAnimationFrame = function (callback, element) {
                    var currTime = new Date().getTime();
                    var timeToCall = Math.max(0, 16 - (currTime - lastTime));
                    var id = window.setTimeout(function () { callback(currTime + timeToCall); },
              timeToCall);
                    lastTime = currTime + timeToCall;
                    return id;
                };

            if (!window.cancelAnimationFrame)
                window.cancelAnimationFrame = function (id) {
                    clearTimeout(id);
                };
        } ());

十.其他

与渲染无关的计算交给worker

复杂的计算交给引擎(自己写,或者用开源的),比如3D、物理

缓存load好的图片,canvas上画canvas,而不是画image

<think>我们正在讨论Mapbox地图的交互功能,特别是关于触摸交互、鼠标悬停行为以及样式变化。根据引用[1],我们了解到MapboxGLJS是一个强大的JavaScript库,它使用WebGL来渲染交互式地图,支持矢量瓦片和栅格瓦片,并且能够处理大量的数据,同时提供流畅的交互和动画效果。用户的问题涉及以下几个关键词:-mousemove:鼠标移动事件-queryRenderedFeatures:Mapbox的方法,用于获取当前渲染在地图上的要素-unclustered-points:非聚簇的点,通常是指未聚合的单个点要素-pointer样式:鼠标指针的样式用户想要实现的功能可能包括:1.当鼠标悬停在地图上的某些要素(比如未聚簇的点)时,改变鼠标指针的样式(例如变成手型)或者改变要素的样式(如高亮显示)。2.通过触摸交互(在移动设备上)实现类似的效果。我们可以使用Mapbox的事件监听和样式设置来实现这些交互。步骤:1.监听地图的mousemove事件(在移动设备上可能是touchmove,但要注意Mapbox已经将触摸事件转换为鼠标事件,所以mousemove在移动设备上也能工作)。2.在事件处理函数中,使用queryRenderedFeatures方法获取当前鼠标位置下渲染的要素,我们可以指定一个图层(例如'unclustered-points')来查询。3.判断获取到的要素,如果有要素,则改变鼠标指针的样式(比如设置地图容器的cursor样式为'pointer')并可以改变该要素的样式(通过更新图层样式或使用feature-state高亮该要素)。4.当鼠标移出时,恢复默认样式。另外,注意引用[1]中提到的MapboxGLJS能够实时渲染,所以我们可以利用它动态改变样式。下面,我将提供一个实现鼠标悬停改变指针样式和要素样式的示例代码,并解释每一步。代码实现思路:-添加地图(略,假设已经有一个地图对象map)-添加数据源和图层(这里使用'unclustered-points'作为图层名)-为地图添加'mousemove'事件监听,并指定图层'unclustered-points'-在事件处理函数中,用queryRenderedFeatures查询当前鼠标位置下的要素,如果存在要素,则改变鼠标指针为'pointer',同时可以高亮该要素(例如改变颜色)-添加'mouseleave'事件,当鼠标离开图层时恢复指针样式和要素样式。注意:在移动设备上,触摸事件也会触发这些事件,但是要注意移动设备上的表现可能需要额外的处理(例如防止页面滚动等)。示例代码:```javascript//假设已经初始化了map对象//为mousemove事件添加监听,指定图层'unclustered-points'map.on('mousemove','unclustered-points',(e)=>{//改变鼠标指针样式map.getCanvas().style.cursor='pointer';//我们可以改变要素的样式,这里假设我们要改变要素的颜色,我们使用feature-state//首先,我们要把当前悬停的要素的id保存下来,以便在鼠标移出时恢复//由于可能有多个要素,我们只取第一个if(e.features.length>0){//假设我们有一个当前悬停的要素idconsthoveredId=e.features[0].id;//设置feature-state,我们之前需要在地图样式中的paint配置中使用到feature-state//例如,在图层中配置了'circle-color'为一个基于feature-state的条件表达式map.setFeatureState({source:'your-source-id',id:hoveredId},{hover:true});}});//当鼠标移出图层时,恢复默认指针和样式map.on('mouseleave','unclustered-points',()=>{map.getCanvas().style.cursor='';//我们需要清除所有设置过hover状态的要素//但这里我们不知道之前哪些要素被设置过,所以我们需要记录上一个悬停的要素//或者我们可以在整个图层上遍历所有设置过hover状态的要素,这样开销较大//另一种做法是:在每次悬停时,先清除上一个悬停要素的状态,然后再设置新的//所以我们需要一个变量来记录上一次悬停的要素//注意:在多个要素的情况下,我们可能只记录一个,或者用数组,这里简单处理记录一个if(lastHoveredId){map.setFeatureState({source:'your-source-id',id:lastHoveredId},{hover:false});lastHoveredId=null;}});```在上面的代码中,我们在图层样式定义中需要配置'circle-color'来根据feature-state的hover属性变化:```javascript"paint":{"circle-color":["case",["boolean",["feature-state","hover"],false],"#ff0000",//悬停时的颜色"#00ff00"//默认颜色]}```但是,上面的代码中,我们在mouseleave事件中处理了样式恢复,但注意我们每次进入mousemove时,应该清除上一次设置的状态(以避免多个高亮点),所以需要记录上一次的id。改进后的mousemove事件处理:```javascriptletlastHoveredId=null;map.on('mousemove','unclustered-points',(e)=>{map.getCanvas().style.cursor='pointer';if(e.features.length>0){//清除上一个悬停要素的状态if(lastHoveredId!==null){map.setFeatureState({source:'your-source-id',id:lastHoveredId},{hover:false});}//设置当前悬停要素的状态constcurrentId=e.features[0].id;lastHoveredId=currentId;map.setFeatureState({source:'your-source-id',id:currentId},{hover:true});}});```这样,在鼠标移动时,每次只高亮一个点(第一个),并且当鼠标离开图层时,我们会清除最后一个高亮点。注意:这个示例假设了每个要素都有一个唯一的id,并且数据源中已经设置了id(或者使用GeoJSON的id属性)。另外,对于触摸交互,在移动设备上,我们可能需要处理touchstart和touchend事件,但Mapbox会将触摸事件转换为鼠标事件,所以上述代码在移动设备上也能工作(比如当用户用手指在屏幕上移动时,会触发mousemove事件)。但是,在移动设备上,我们可能还需要考虑以下事项:-防止地图拖动和悬停事件冲突。Mapbox默认在移动设备上拖动地图,所以当用户拖动时,mousemove事件会被频繁触发,可能会导致性能问题。我们可以通过判断是否在拖动中来解决,但是Mapbox的事件处理已经做了优化,所以一般情况下不会很卡顿。-如果移动设备上需要点击弹出弹窗等,也要注意事件冲突。总结:通过Mapbox的mousemove事件和queryRenderedFeatures(在事件对象e中已经包含了当前点下的要素,所以我们不需要手动调用queryRenderedFeatures)可以获取到要素,然后利用setFeatureState和图层样式中的条件表达式来改变要素样式,同时可以设置鼠标指针样式。相关问题:1.MapboxGLJS中如何使用鼠标事件处理地图上的要素交互?2.Mapbox如何通过setFeatureState来动态改变要素的样式?3.在移动设备上,Mapbox的触摸事件是如何转换成鼠标事件的?会有哪些注意事项?4.Mapbox的queryRenderedFeatures方法的使用场景有哪些?5.Mapbox中处理大量点要素时,如何通过聚簇(clustering)来优化性能,以及如何处理聚簇点与非聚簇点(unclustered-points)?我们可以在后续回答中详细解答这些问题。</think> 基于你的问题,我将详细解释Mapbox地图触摸交互的实现方式,重点围绕`mousemove`、`queryRenderedFeatures`、`unclustered-points`图层和指针样式操作: ### 1. **触摸交互核心实现逻辑** Mapbox通过事件监听 + 要素查询 + 样式更新实现交互: ```javascript // 初始化地图 const map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/streets-v11' }); // 关键:监听指针移动事件 map.on('mousemove', 'unclustered-points', (e) => { // 查询渲染要素 const features = map.queryRenderedFeatures(e.point, { layers: ['unclustered-points'] }); // 更新指针样式 map.getCanvas().style.cursor = features.length ? 'pointer' : 'auto'; // 高亮显示要素(可选) if (features.length) { map.setFeatureState({ source: 'your-source', id: features[0].id }, { hover: true }); } }); // 移出时重置样式 map.on('mouseleave', 'unclustered-points', () => { map.getCanvas().style.cursor = 'auto'; }); ``` ### 2. **关键技术解析** 1. **`queryRenderedFeatures`** - 作用:获取屏幕上渲染的GeoJSON要素 - 关键参数: ```javascript map.queryRenderedFeatures([x, y], { layers: ['unclustered-points'], // 限定图层 filter: ['==', 'type', 'marker'] // 过滤条件 }) ``` 2. **`unclustered-points`图层** 常用于非聚簇点数据,配置示例: ```javascript map.addSource('points', { type: 'geojson', data: geojsonData, cluster: true // 启用聚类 }); map.addLayer({ id: 'unclustered-points', type: 'circle', source: 'points', filter: ['!', ['has', 'point_count']] // 筛选未聚簇点 }); ``` 3. **指针样式控制** - `map.getCanvas().style.cursor`: 动态修改鼠标样式 - 常用值:`'pointer'`(手型)、`'grab'`(抓取)、`'crosshair'`(十字) 4. **状态驱动样式** 通过`setFeatureState`实现动态样式: ```javascript // 图层样式定义 "circle-color": [ 'case', ['boolean', ['feature-state', 'hover'], false], '#ff0000', // 悬停时红色 '#00ff00' // 默认绿色 ] ``` ### 3. **移动端适配要点** 1. 统一使用`mousemove`事件(Mapbox自动处理触摸转换) 2. 添加防抖提升性能: ```javascript let timeout; map.on('mousemove', () => { clearTimeout(timeout); timeout = setTimeout(updateStyle, 50); }); ``` 3. 触屏交互增强: ```javascript map.on('touchstart', 'unclustered-points', showPopup); // 点击显示弹窗 ``` ### 4. **完整交互流程** $$ \begin{array}{c} \text{用户交互} \\ \downarrow \\ \text{地图事件(mousemove/touch)} \\ \downarrow \\ \text{queryRenderedFeatures查询要素} \\ \downarrow \\ \begin{cases} \text{存在要素} & \rightarrow \text{指针变手型} \\ \text{无要素} & \rightarrow \text{重置指针} \end{cases} \\ \downarrow \\ \text{setFeatureState更新要素状态} \\ \downarrow \\ \text{动态样式生效} \end{array} $$ ### 5. **性能优化建议** 1. 限制查询范围:使用`radius: 5`缩小查询半径 2. 图层分离:交互图层独立于背景图层 3. 使用`feature-state`替代重绘: ```javascript // 错误:全量更新 map.setPaintProperty('layer', 'circle-color', newColor); // 正确:增量更新 map.setFeatureState({id}, {hover: true}); ``` > **引用说明**:Mapbox GL JS 通过实时渲染引擎实现高性能交互,其矢量切片技术可支撑大规模地理数据的高效渲染[^1]。 --- **相关问题**: 1. 如何用`queryRenderedFeatures`实现多图层联合查询? 2. 聚簇点(clustered-points)与未聚簇点交互有何区别? 3. Mapbox 触摸事件在移动端与桌面端有哪些兼容性问题? 4. 如何通过 `feature-state` 实现点击/悬停的双重样式反馈? 5. GeoJSON 要素属性如何参与交互筛选(如类型过滤)?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值