繁体   English   中英

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

[英]How to implement canvas panning with Fabric.js

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

您可以在此视频中看到我想要实现的目标。

为了实现此功能,我编写了以下代码:

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

但它不起作用。 你可以在这个视频中看到发生了什么。

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

  1. 像第一个视频那样平移工作?

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

平移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画布。

不确定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小提琴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; } 


(*) 问题:

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

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

原始javascript: this.style.cursor = 'move'; JS小提琴4

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

解决方案:

  1. Chrome和Safari支持使用-webkit-前缀grabgrabbing ,如:

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

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

  2. 看一下这个例子 ,用Chrome,Safari,Firefox,Opera和IE测试你可以看到cursor: url(foo.bar)所有浏览器中都可以工作和支持。 Chrome,Safari,Firefox和Opera显示黄色微笑图像smiley.gif ,但IE显示红色球光标url(myBall.cur)

    所以我认为你可以利用这个,并抓住像这样的抓手形象 在此输入图像描述

    或这个:

    在此输入图像描述

    您可以使用上面的图像, pnggif格式,除IE以外的所有浏览器都支持.cur ,因此您需要找到将其转换为.cur 谷歌搜索显示许多转换图像到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'); 但也无济于事。

所以你仍然需要让浏览器嗅探你是第二个解决方案还是两个解决方案的混合修复程序, 这是我用了几次检测浏览器的代码,它在这篇文章的时候效果很好但是你porbabely不需要“手机”和“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); 


资源:

我在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.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 = eeclientX; lastClientY = eeclientY; }); // When the mouse moves, and we're panning (mouse down), we continue this.on('mouse:move', (e) => { if (state === STATE_PANNING && e && ee) { // let delta = new fabric.Point(eemovementX, eemovementY); // 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 = eeclientX - lastClientX; } if (lastClientY) { deltaY = eeclientY - lastClientY; } // Update the last X and Y values lastClientX = eeclientX; lastClientY = eeclientY; 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> 

我在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的:

    一个。 创建画布。

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

    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事件,将光标更改为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语句来检查我们是否有一个活动对象或组。如果是,则不拖动画布。

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

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

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

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

我知道这已经得到回答,但我使用新版本的 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;

:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM