如何使用 Fabric.js 实现 canvas 平移

How to implement canvas panning with Fabric.js

我有一个 Fabric.js canvas,我想实现软件包通常使用 "hand" 工具完成的完整 canvas 平移。当您按下鼠标按钮之一,然后在按住鼠标按钮的同时移动到 canvas 上时,canvas 的可见部分会相应地发生变化。

你可以看到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. 让事件处理器消费事件?它应该防止在用户按下或释放鼠标右键时出现上下文菜单。

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

  1. for it to work like in the first video:

    通过使用 CSS cursor 属性,使用 javascript.[=52 在 mousedownmouseup 事件上切换它=]

  2. the event handler consume the event (prevent the context menu from appearing, when the user releases the right mouse button):

    使用 javascript 我们 return falsecontextmenu 事件

CODE: 有点问题 ( * )

使用 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;
}


( * ) 问题:

上面的代码片段可以正常工作,但是有一个 cross-browser 问题,如果你在 Firefox - 或 Opera 中检查上面的小提琴 - 你会看到正确的行为,在 Chrome 和 IE11 中检查时 - 未使用 Edge 或 Safari 检查它 - 我发现 Chrome 和 IE 不支持 grabbing 光标形状,所以在上面的代码片段中,将光标行更改为:

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)

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

    或者这个:

    你可以使用像上面这样的图片,pnggif格式的所有浏览器,除了IE支持.cur,所以你需要找到一种方法来转换它变成 .cur。 Google 搜索显示 convert image to cur

  3. 的许多结果

请注意 ,尽管此 cursor:url(smiley.gif),url(myBall.cur),auto; - 具有以逗号分隔的后备支持 ,在上面显示的 W3Schools 示例中运行良好,但我无法在 javascript 中以相同的方式运行,我尝试了 $(this).css({'cursor': 'grabbing, move'});,但没有成功。 我也尝试这样做 CSS class

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

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

因此,无论您是采用第二种解决方案还是两种解决方案的混合修复,您仍然需要让浏览器进行嗅探,this is my code 我已经使用了几次来检测浏览器并且在这个 post 的时间,但你可能不需要 "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);


资源:

平移 Fabric canvas 以响应鼠标移动的一种简单方法是计算鼠标事件之间的光标位移并将其传递给 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-取消功能本身。

返回false取消了右键菜单,也就是上下文菜单。菜单取消功能也会自行取消。因此,如果您随后在 canvas 包装器外部单击,上下文菜单将起作用。

这是演示此方法的页面:

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

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

我在 jsfiddle 上做了一个例子,我们实际上可以将整个 canvas 及其所有对象拖到父 div 中,如图所示,我将尝试解释它循序渐进。

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

  2. 创建容器来容纳我们的canvas。

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

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

    一个。创建 canvas.

    b。制作默认光标,当它结束时canvas,打开手

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

    c。覆盖 __onMouseDown 函数,以更改为 closedhand 光标(在末尾)。

         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 事件,将光标改回开手状态。

       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() 以处理承载 canvas:

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

       $($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) 并获得 top & left position

      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. 虽然有一个小问题,jsfiddle 只接受 https 库,所以它会阻止 fabricjs,除非你从 'https://rawgit.com/kangax/fabric.js/master/dist/fabric.js' 添加它,但它仍然会阻止它一些次(至少在我的 chrome 和 mozilla 上是这样)。

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

你在浏览器上的运气可能比我好,但它肯定会在你的实时应用程序上运行。

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

我有一个关于 Github 使用 fabric.js Canvas 平移的例子:https://sabatinomasala.github.io/fabric-clipping-demo/

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

它是 fabric.Canvas.prototype 上的一个简单扩展,可让您在 canvas 上切换 'drag mode',如下所示:

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

看看下面的代码片段,整个代码都有文档。

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>

我知道这已经得到解答,但我使用新版本的 fabricjs (4.5.0)

重做了此 中创建的笔

笔数:https://codepen.io/201flaviosilva/pen/GROLbQa

在这种情况下,我使用鼠标中键进行平移:)

// Enable mouse middle button
canvas.fireMiddleClick = true;

// Mouse Up Event
if (e.button === 2) currentState = STATE_IDLE;

// Mouse Down Event
if (e.button === 2) currentState = STATE_PANNING;

:)