【问题标题】:How to implement canvas panning with Fabric.js如何使用 Fabric.js 实现画布平移
【发布时间】:2016-03-29 04:31:40
【问题描述】:

我有一个 Fabric.js 画布,我想实现软件包通常使用“手动”工具进行的全画布平移。当您按下其中一个鼠标按钮,然后在按住鼠标按钮的同时在画布上移动时,画布的可见部分会相应地发生变化。

你可以看到in this video我想要达到的目标。

为了实现这个功能,我写了以下代码:

$(canvas.wrapperEl).on('mousemove', function(evt) {
    if (evt.button == 2) { // 2 is the right mouse button
        canvas.absolutePan({
            x: evt.clientX,
            y: evt.clientY
        });
    }
});

但它不起作用。你可以看到in this video会发生什么。

如何按顺序修改我的代码:

  1. 要像第一个视频那样进行平移?

  2. 让事件处理程序消费事件?它应该防止在用户按下或释放鼠标右键时出现上下文菜单。

【问题讨论】:

标签: javascript canvas fabricjs panning


【解决方案1】:

平移 Fabric 画布以响应鼠标移动的一种简单方法是计算鼠标事件之间的光标位移并将其传递给 relativePan

观察我们如何使用前一个鼠标事件的screenXscreenY属性来计算当前鼠标事件的相对位置:

function startPan(event) {
  if (event.button != 2) {
    return;
  }
  var x0 = event.screenX,
      y0 = event.screenY;
  function continuePan(event) {
    var x = event.screenX,
        y = event.screenY;
    fc.relativePan({ x: x - x0, y: y - y0 });
    x0 = x;
    y0 = y;
  }
  function stopPan(event) {
    $(window).off('mousemove', continuePan);
    $(window).off('mouseup', stopPan);
  };
  $(window).mousemove(continuePan);
  $(window).mouseup(stopPan);
  $(window).contextmenu(cancelMenu);
};
function cancelMenu() {
  $(window).off('contextmenu', cancelMenu);
  return false;
}
$(canvasWrapper).mousedown(startPan);

我们开始平移 mousedown 并继续平移 mousemove。在mouseup,我们取消平移;我们还取消了mouseup-cancelling 函数本身。

右键菜单也称为上下文菜单,通过返回false 取消。菜单取消功能也会自行取消。因此,如果您随后在画布包装之外单击,上下文菜单将起作用。

这是一个展示这种方法的页面:

http://michaellaszlo.com/so/fabric-pan/

您将在 Fabric 画布上看到三张图片(加载图片可能需要一两分钟)。您将能够使用标准的 Fabric 功能。您可以左键单击图像以移动、拉伸和旋转它们。但是当您在画布容器中单击鼠标右键时,您会用鼠标平移整个 Fabric 画布。

【讨论】:

  • 谢谢。我已经尝试过您的方法,但有一个小问题 - 请参阅我的更新 1。
  • 我没有看到 IE 以不同的方式处理 contextmenu 事件。我已经更新了我的 IE 代码。您可能需要重新加载演示页面两次才能从浏览器缓存中清除旧的 JavaScript 文件。
【解决方案2】:

我在 Github 上有一个使用 fabric.js 画布平移的示例:https://sabatinomasala.github.io/fabric-clipping-demo/

负责平移行为的代码如下:https://github.com/SabatinoMasala/fabric-clipping-demo/blob/master/src/classes/Panning.js

这是fabric.Canvas.prototype 上的一个简单扩展,可让您在画布上切换“拖动模式”,如下所示:

canvas.toggleDragMode(true); // Start panning
canvas.toggleDragMode(false); // Stop panning

看看下面的 sn-p,整个代码都有文档。

const STATE_IDLE = 'idle';
const STATE_PANNING = 'panning';
fabric.Canvas.prototype.toggleDragMode = function(dragMode) {
  // Remember the previous X and Y coordinates for delta calculations
  let lastClientX;
  let lastClientY;
  // Keep track of the state
  let state = STATE_IDLE;
  // We're entering dragmode
  if (dragMode) {
    // Discard any active object
    this.discardActiveObject();
    // Set the cursor to 'move'
    this.defaultCursor = 'move';
    // Loop over all objects and disable events / selectable. We remember its value in a temp variable stored on each object
    this.forEachObject(function(object) {
      object.prevEvented = object.evented;
      object.prevSelectable = object.selectable;
      object.evented = false;
      object.selectable = false;
    });
    // Remove selection ability on the canvas
    this.selection = false;
    // When MouseUp fires, we set the state to idle
    this.on('mouse:up', function(e) {
      state = STATE_IDLE;
    });
    // When MouseDown fires, we set the state to panning
    this.on('mouse:down', (e) => {
      state = STATE_PANNING;
      lastClientX = e.e.clientX;
      lastClientY = e.e.clientY;
    });
    // When the mouse moves, and we're panning (mouse down), we continue
    this.on('mouse:move', (e) => {
      if (state === STATE_PANNING && e && e.e) {
        // let delta = new fabric.Point(e.e.movementX, e.e.movementY); // No Safari support for movementX and movementY
        // For cross-browser compatibility, I had to manually keep track of the delta

        // Calculate deltas
        let deltaX = 0;
        let deltaY = 0;
        if (lastClientX) {
          deltaX = e.e.clientX - lastClientX;
        }
        if (lastClientY) {
          deltaY = e.e.clientY - lastClientY;
        }
        // Update the last X and Y values
        lastClientX = e.e.clientX;
        lastClientY = e.e.clientY;

        let delta = new fabric.Point(deltaX, deltaY);
        this.relativePan(delta);
        this.trigger('moved');
      }
    });
  } else {
    // When we exit dragmode, we restore the previous values on all objects
    this.forEachObject(function(object) {
      object.evented = (object.prevEvented !== undefined) ? object.prevEvented : object.evented;
      object.selectable = (object.prevSelectable !== undefined) ? object.prevSelectable : object.selectable;
    });
    // Reset the cursor
    this.defaultCursor = 'default';
    // Remove the event listeners
    this.off('mouse:up');
    this.off('mouse:down');
    this.off('mouse:move');
    // Restore selection ability on the canvas
    this.selection = true;
  }
};

// Create the canvas

let canvas = new fabric.Canvas('fabric')
canvas.backgroundColor = '#f1f1f1';

// Add a couple of rects

let rect = new fabric.Rect({
  width: 100,
  height: 100,
  fill: '#f00'
});
canvas.add(rect)

rect = new fabric.Rect({
  width: 200,
  height: 200,
  top: 200,
  left: 200,
  fill: '#f00'
});
canvas.add(rect)

// Handle dragmode change

let dragMode = false;
$('#dragmode').change(_ => {
  dragMode = !dragMode;
  canvas.toggleDragMode(dragMode);
});
<div>
  <label for="dragmode">
    Enable panning
    <input type="checkbox" id="dragmode" name="dragmode" />
  </label>
</div>
<canvas width="300" height="300" id="fabric"></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.15/fabric.min.js"></script>

【讨论】:

    【解决方案3】:

    不确定 FabricJS,但可能是这样的:

    1. 让它像第一个视频中一样工作:

      通过使用 CSS cursor 属性,使用 javascript 在 mousedownmouseup 事件上切换它。

    2. 事件处理程序使用事件(防止上下文菜单出现,当用户释放鼠标右键时):

      使用 javascript 我们在 contextmenu 事件上返回 false

    代码: 有点问题 ( * )

    使用 jQuery JS Fiddle 1

    $('#test').on('mousedown', function(e){
        if (e.button == 2) {
            // if right-click, set cursor shape to grabbing
            $(this).css({'cursor':'grabbing'});
        }
    }).on('mouseup', function(){
        // set cursor shape to default
        $(this).css({'cursor':'default'});
    }).on('contextmenu', function(){ 
        //disable context menu on right click
        return false;
    });
    

    使用原始 javascript JS Fiddle 2

    var test = document.getElementById('test');
    
    test.addEventListener('mousedown', function(e){
        if (e.button == 2) {
            // if right-click, set cursor shape to grabbing
            this.style.cursor = 'grabbing';
        }
    });
    test.addEventListener('mouseup', function(){
        // set cursor shape to default
        this.style.cursor = 'default';
    });
    test.oncontextmenu = function(){ 
        //disable context menu on right click
        return false;
    }

    ( * ) 问题:

    上述 sn-ps 可以正常工作,但存在跨浏览器问题,如果您在 Firefox - 或 Opera 中检查上述小提琴 - 在签入时您会看到正确的行为Chrome 和 IE11 - 没有用 Edge 或 Safari 进行检查 - 我发现 Chrome 和 IE 不支持 grabbing 光标形状,所以在上面的代码 sn-ps 中,更改光标进入这个:

    jQuery:$(this).css({'cursor':'move'});JS Fiddle 3

    原始javascript:this.style.cursor = 'move';JS Fiddle 4

    现在我们有了一个工作代码,但没有手形光标。但有以下解决方案:-

    解决方案:

    1. Chrome 和 Safari 支持带有 -webkit- 前缀的 grabgrabbing,例如:

      $(this).css({'cursor': '-webkit-grabbing'});
      

      但是您需要先进行浏览器嗅探,如果是 Firefox,则使用默认和标准代码,如果是 Chrome 和 Safari,则使用 -webkit- 前缀,这仍然会使 IE 退出游戏。

    2. 看看this example,用 Chrome、Safari、Firefox、Opera 和 IE 对其进行测试,您会发现cursor: url(foo.bar)所有浏览器中都有效并受支持。 Chrome、Safari、Firefox 和 Opera 显示黄色微笑图像smiley.gif,但 IE 显示红色球光标url(myBall.cur)

      所以我认为你可以利用这个,以及像这样的抓手图像

      或者这个:

      您可以在除支持.cur 的IE 之外的所有浏览器中使用类似上述的pnggif 格式的图像,因此您需要找到一种方法将其转换为.cur。谷歌搜索显示convert image to cur的许多结果

    请注意,尽管 cursor:url(smiley.gif),url(myBall.cur),auto; - 带有逗号分隔的后备支持,在上面显示的 W3Schools 示例中运行良好,我无法让它在 javascript 中以相同的方式工作,我尝试了$(this).css({'cursor': 'grabbing, move'});,但它没有用。 我也尝试将其作为 CSS 类进行

    .myCursor{ cursor: grabbing, -webkit-grabbing, move; }
    

    然后使用 jQuery $(this).addClass('myCursor'); 但也无济于事。

    因此,无论您是采用第二种解决方案还是两种解决方案的混合修复,您仍然需要进行浏览器嗅探,this is my code 我已经使用过几次来检测浏览器,并且在本文发布时效果很好但您可能不需要“Mobile”和“Kindle”部分。

    // Detecting browsers
    $UA = navigator.userAgent;
    
    if ($UA.match(/firefox/i)) {
    	$browser = 'Firefox';
    } else if ($UA.indexOf('Trident') != -1 && $UA.indexOf('MSIE') == -1) {
    	$browser = 'MSIE';
    } else if ($UA.indexOf('MSIE') != -1) {
    	$browser = 'MSIE';
    } else if ($UA.indexOf('OPR/') != -1) {
    	$browser = 'Opera';
    } else if ($UA.indexOf("Chrome") != -1) {
    	$browser = 'Chrome';
    } else if ($UA.indexOf("Safari")!=-1) {
    	$browser = 'Safari';
    }
    if($UA.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Nokia|Mobile|Opera Mini/i)) {
    	$browser = 'Mobile';
    }else if($UA.match(/KFAPWI/i)){
    	$browser = 'Kindle';
    }
    
    console.log($browser);

    资源:

    【讨论】:

      【解决方案4】:

      我在 jsfiddle 上做了一个例子,我们实际上可以将整个画布及其所有对象拖到一个父 div 中,就像图片一样,我将尝试逐步解释它。

      1. 首先我下载了​​拖拽库jquery.dradscroll.js,你可以在网上找到。这是一个小js文件,稍加改动就可以帮助我们完成任务。 下载链接:http://www.java2s.com/Open-Source/Javascript_Free_Code/jQuery_Scroll/Download_jquery_dragscroll_Free_Java_Code.htm

      2. 创建容纳我们画布的容器。

          <div class="content">
            <canvas id="c" width="600" height="700" ></canvas>    
          </div>
        
      3. 小CSS

        .content{
           overflow:auto;
           width:400px;
           height:400px;
        }
        
      4. javascript:

        一个。创建画布。

        b.制作默认光标,当它在画布上时,张开手

        canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';

        c。重写 __onMouseDown 函数,以更改为 closehand 光标(在末尾)。

             fabric.Canvas.prototype.__onMouseDown = function(e){
          // accept only left clicks
          var isLeftClick  = 'which' in e ? e.which === 1 : e.button === 1;
          if (!isLeftClick && !fabric.isTouchSupported) {
            return;
          }
        
        
          if (this.isDrawingMode) {
            this._onMouseDownInDrawingMode(e);
            return;
          }
        
          // ignore if some object is being transformed at this moment
          if (this._currentTransform) {
            return;
          }
        
          var target = this.findTarget(e),
              pointer = this.getPointer(e, true);
        
          // save pointer for check in __onMouseUp event
          this._previousPointer = pointer;
        
          var shouldRender = this._shouldRender(target, pointer),
              shouldGroup = this._shouldGroup(e, target);
        
          if (this._shouldClearSelection(e, target)) {
            this._clearSelection(e, target, pointer);
          }
          else if (shouldGroup) {
            this._handleGrouping(e, target);
            target = this.getActiveGroup();
          }
        
          if (target && target.selectable && !shouldGroup) {
            this._beforeTransform(e, target);
            this._setupCurrentTransform(e, target);
          }
          // we must renderAll so that active image is placed on the top canvas
          shouldRender && this.renderAll();
        
          this.fire('mouse:down', { target: target, e: e });
          target && target.fire('mousedown', { e: e });
        
          if(!canvas.getActiveObject() || !canvas.getActiveGroup()){
            flag=true;
            //change cursor to closedhand.cur
            canvas.defaultCursor = 'url("http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur") 15 15, crosshair';     
        }//end if
        
      5. 覆盖 __onMouseUp 事件,将光标改回 openhand。

           fabric.Canvas.prototype.__onMouseUp = function(e){
               if(flag){
                  canvas.defaultCursor = 'url(" http://maps.gstatic.com/intl/en_us/mapfiles/openhand_8_8.cur") 15 15, crosshair';
                  flag=false;
               }
            };
        
      6. 您初始化 dragScroll() 以处理承载画布的内容:

            $('.content').dragScroll({});
        
      7. 对 jquery.dragScroll.js 文件进行一些小改动,以便了解何时拖动画布,何时不拖动。在 mousedown() 事件中,我们添加一个 if 语句来检查我们是否有一个活动对象或组。如果为 true ,则没有画布拖动。

           $($scrollArea).mousedown(function (e) {
            if (canvas.getActiveObject() || canvas.getActiveGroup()) {
                console.log('no drag');return;
            } else {                                             
               console.log($('body'));
                if (typeof options.limitTo == "object") {
                    for (var i = 0; i < options.limitTo.length; i++) {
                        if ($(e.target).hasClass(options.limitTo[i])) {
                            doMousedown(e);
                        }
                    }
                } else {
                    doMousedown(e);
                }
            }
        });
        
      8. mousedown 事件中,我们抓取 DOM 元素 (.content) 并获取 顶部和左侧位置

          function doMousedown(e) {
        
                e.preventDefault();
                down = true;
                x = e.pageX;
                y = e.pageY;        
                top = e.target.parentElement.parentElement.scrollTop; // .content
                left = e.target.parentElement.parentElement.scrollLeft;// .content
            }
        
      9. 如果我们不想让滚动条可见:

            .content{
           overflow:hidden;
           width:400px;
           height:400px;
        

        }

      10. 1234563 chrome 和 Mozilla)。

      jsfiddle 示例:https://jsfiddle.net/tornado1979/up48rxLs/

      你的浏览器可能比我运气好,但它绝对可以在你的实时应用上运行。

      无论如何, 希望对你有帮助,祝你好运。

      【讨论】:

        猜你喜欢
        • 2016-01-03
        • 2017-06-26
        • 2013-02-11
        • 1970-01-01
        • 2013-09-21
        • 2012-07-24
        • 1970-01-01
        • 2018-03-15
        • 2012-02-07
        相关资源
        最近更新 更多