【问题标题】:Image in canvas leaves a tiled trail when panned画布中的图像在平移时会留下平铺的轨迹
【发布时间】:2016-08-11 21:33:24
【问题描述】:

我正在尝试创建一个允许放大的可平移图像查看器。如果缩放系数或图像大小使得图像不再在整个画布上绘制,那么我希望画布区域不包含使用指定背景颜色绘制的图像。

我当前的实现允许缩放和平移,但在平移操作期间图像会在其后留下平铺轨迹(很像赢得游戏时 Windows Solitaire 中的牌)。如何清理我的画布,使图像不会留下痕迹,并且我的背景矩形正确呈现在我的画布中?

要重新创建不需要的效果,请将放大倍率设置为您看到深灰色背景显示的某个级别,然后用鼠标平移图像(鼠标向下并拖动)。

下面添加了代码 sn-p 和 Plnkr 链接,供那些希望在那里捣乱的人使用。 http://plnkr.co/edit/Cl4T4d13AgPpaDFzhsq1

<!DOCTYPE html>
<html>

  <head>
    <style>
      canvas{
        border:solid 5px #333;
      }
    </style>
    
  </head>

  <body>

	  <button onclick="changeScale(0.10)">+</button>
	  <button onclick="changeScale(-0.10)">-</button>
	  
    <div id="container">
      <canvas width="700" height="500" id ="canvas1"></canvas>
    </div>
    
    <script>
    
      var canvas = document.getElementById('canvas1');
      var context = canvas.getContext("2d");
      var imageDimensions ={width:0,height:0};
      var photo = new Image();
      var isDown = false;
      var startCoords = [];
      var last = [0, 0];
      var windowWidth = canvas.width;
      var windowHeight = canvas.height;

      var scale=1;
      
      photo.addEventListener('load', eventPhotoLoaded , false);
      photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
      
      function eventPhotoLoaded(e) {
        imageDimensions.width = photo.width;
        imageDimensions.height = photo.height;
        drawScreen();
      }
      
      function changeScale(delta){
        scale += delta;
        drawScreen();
      }
      
      function drawScreen(){
        context.fillRect(0,0, windowWidth, windowHeight);
        context.fillStyle="#333333";
        context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
      }

      canvas.onmousedown = function(e) {
          isDown = true;
      
          startCoords = [
              e.offsetX - last[0],
              e.offsetY - last[1]
         ];
      };
      
      canvas.onmouseup   = function(e) {
          isDown = false;
          
          last = [
              e.offsetX - startCoords[0], // set last coordinates
              e.offsetY - startCoords[1]
          ];
      };
      
      canvas.onmousemove = function(e)
      {
          if(!isDown) return;
          
          var x = e.offsetX;
          var y = e.offsetY;
          
          context.setTransform(1, 0, 0, 1,
                           x - startCoords[0], y - startCoords[1]);
          drawScreen();
      }
      

    </script>
  </body>

</html>

【问题讨论】:

  • 可能类似于 context.save(); context.fillRect(0,0, windowWidth, windowHeight); context.restore(); 因为 setTransform 也会更改您填充的矩形。
  • 你是对的。如果您想将该评论作为答案,我将很高兴接受它。

标签: javascript canvas


【解决方案1】:

您需要重置转换。

在您清除画布之前添加context.setTransform(1,0,0,1,0,0);,这将解决您的问题。它将当前转换设置为默认值。然后在绘制图像之前设置图像的变换。

更新: 当与用户输入(例如鼠标或触摸事件)交互时,应该独立于渲染进行处理。渲染将每帧仅触发一次,并对在前一个刷新间隔期间发生的任何鼠标更改进行视觉更改。如果不需要,则不进行渲染。

如果不需要,不要使用保存和恢复。

      var canvas = document.getElementById('canvas1');
      var ctx = canvas.getContext("2d");
      var photo = new Image();
      var mouse = {}
      mouse.lastY = mouse.lastX = mouse.y = mouse.x = 0;
      mouse.down = false;
      var changed = true;
      var scale = 1;
      var imageX = 0;
      var imageY = 0;
      photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
      function changeScale(delta){
        scale += delta;
        changed = true;
      }
      // Turns mouse button of when moving out to prevent mouse button locking if you have other mouse event handlers.
      function mouseEvents(event){  // do it all in one function
          if(event.type === "mouseup" || event.type === "mouseout"){
            mouse.down = false;
            changed = true;
          }else
          if(event.type === "mousedown"){
            mouse.down = true;            
          }
          mouse.x = event.offsetX;
          mouse.y = event.offsetY;
          if(mouse.down) {
            changed = true;
          }
      }
      canvas.addEventListener("mousemove",mouseEvents);
      canvas.addEventListener("mouseup",mouseEvents);
      canvas.addEventListener("mouseout",mouseEvents);
      canvas.addEventListener("mousedown",mouseEvents);

      function update(){
        requestAnimationFrame(update);
        if(photo.complete && changed){
          ctx.setTransform(1,0,0,1,0,0);
          ctx.fillStyle="#333";
          ctx.fillRect(0,0, canvas.width, canvas.height);
          if(mouse.down){
            imageX += mouse.x - mouse.lastX;
            imageY += mouse.y - mouse.lastY;
          }
          ctx.setTransform(scale, 0, 0, scale, imageX,imageY);
          ctx.drawImage(photo,0,0);
          changed = false;
        }
        mouse.lastX = mouse.x
        mouse.lastY = mouse.y                  
      }
      requestAnimationFrame(update);
canvas{
    border:solid 5px #333;
  }
<button onclick="changeScale(0.10)">+</button><button onclick="changeScale(-0.10)">-</button>
<canvas width="700" height="500" id ="canvas1"></canvas>

【讨论】:

  • 效果很好,解决了我在简单保存和恢复状态时使用缩放功能时遇到的问题。
【解决方案2】:

不错的代码;)

您在演示中看到了“平铺”效果,因为每次在拖动时调用drawScreen() 函数时,您都会将缩放后的图像绘制到画布上。您可以通过两个简单的步骤来解决此问题。

首先,您需要在调用drawScreen() 之间清除画布,其次,您需要在调用之间使用画布context.save()context.restore() 方法干净地重置画布变换矩阵drawScreen()

鉴于您的代码原样:

创建一个函数来清除画布。例如

  function clearCanvas() {
      context.clearRect(0, 0, canvas.width, canvas.height);
  }

canavs.onmousemove() 函数中,调用clearCanvas() 并调用context.save() 重新定义变换矩阵之前.​​..

  canvas.onmousemove = function(e) {
      if(!isDown) return;

      var x = e.offsetX;
      var y = e.offsetY;

      /* !!! */
      clearCanvas();
      context.save();

      context.setTransform(
         1, 0, 0, 1,
         x - startCoords[0], y - startCoords[1]
      );

      drawScreen();
  }

...然后在drawScreen()的末尾有条件地调用context.restore() ...

function drawScreen() {
    context.fillRect(0,0, windowWidth, windowHeight);
    context.fillStyle="#333333";
    context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
    /* !!! */
    if (isDown) context.restore();
}

此外,您可能希望在重新缩放图像之前调用 clearCanvas(),并且画布背景可以使用 CSS 而不是 .fillRect()(在 drawScreen() 中)设置样式 - 这可以在低规格设备上提高性能。

根据 Blindman67 下面的 cmets 进行编辑

另见

【讨论】:

  • 精彩的答案。我可以告诉你,你在这个问题上很深。
  • 当只需要将转换重置为默认值时,为什么还要使用昂贵的保存/恢复步骤。为什么if(context.save) context.restore() ??? save 是一个函数,将始终评估为 true。你只是增加了不必要的复杂性。此外,将 RAF 添加到鼠标事件也不是一个好主意,因为鼠标移动可以在一帧中触发多次。应该将渲染与鼠标事件分离,并且每次刷新只渲染一次,否则就是浪费处理,因为它永远不会被看到。
  • @Blindman67 "save 是一个函数,并且将始终评估为 true" - 是的,这很草率,但需要应用一些条件,因为 drawScreen() 也在 onmousemove 事件循环之外调用- 例如if (isDown) context.restore()?。我认为 RAF 是一种有用的方法来限制对绘图函数的调用 - 也许您可以在下面给出更详细的解释。 ;)
  • @BrianPeacock RAF 将在您每次调用 RAF 时调用回调。如果这是在一个屏幕刷新内,那么在将画布呈现给显示器之前,它会简单地进行多次渲染。在这种情况下,大约一半的渲染将永远不会被看到。保持渲染和 IO 分开。我已经更新了我的答案,尽管对于您的最佳方法是什么是主观的,有一些缺陷会给新手带来问题。如果您希望使用保存和恢复,请更新您的答案,我将删除我的答案。
  • @Blindman67 我已经从我的回答中删除了对requestAnimationFrame 的引用。保留“保存”和“恢复”作为使原始代码工作而无需进行结构检修的最小添加。今天我学到了一些重要的东西! :)
【解决方案3】:

在调用context.fillRect 之前调用context.save 以保存转换矩阵。 然后每当您需要绘制图像时,调用context.restore 来恢复矩阵。

例如:

function drawScreen(){
    context.save();
    context.fillStyle="#333333";
    context.fillRect(0,0, windowWidth, windowHeight);
    context.restore();
    context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
  }

另外,为了进一步优化,你只需要设置一次fillStyle,直到你改变画布的大小。

【讨论】:

    猜你喜欢
    • 2018-05-13
    • 2011-06-17
    • 2019-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多