【问题标题】:Fabric JS: sync position of object after pan/zoom across two instancesFabric JS:在两个实例之间平移/缩放后同步对象的位置
【发布时间】:2019-04-14 19:43:43
【问题描述】:

Fabric JS 问题。

屏幕截图 this is what my Fabric JS app looks like

CODEPEN https://codepen.io/zaynbuksh/pen/VVKRxj?editors=0011alt-click-drag 进行平移,滚轮 进行缩放)

TLDR;如何让线条在平移和缩放几次后粘住?


我正在开发一个 Fabric JS 应用程序,用于标记标本图像。作为其中的一部分,人们希望能够放大每个标签所指向的内容。我被要求在放大标本图像时使标签保持可见。根据研究,人们建议将两张画布叠放在一起。

我创建了两个 Fabric JS 画布实例,它们相互叠加。底部的画布包含可以缩放和平移的背景图像,其上方的画布显示未缩放的指针线/标签(以始终保持标签可见)。

起初一切正常 - 仅当我平移和缩放 first 时间时,线条与图像保持同步。之后我在将线路同步到图像时遇到问题。

当我平移然后缩放 两次 或更多次时会出现问题。 每次我平移然后缩放时问题都会重复,即当我缩放时线条移动,但随后当我平移时保持同步,当我再次缩放时再次移动,正常平移等等......


(平移由 alt-click-drag 处理,Zoom 由滚轮处理)

/*

  "mouse:wheel" event is where zooms are handled
  "mouse:move" event is where panning is handled

*/

// create Fabric JS canvas'
var labelsCanvas = new fabric.Canvas("labelsCanvas");
var specimenCanvas = new fabric.Canvas("specimenCanvas");
//set defaults
var startingPositionForLine = 100;
const noZoom = 1;
var wasPanned = false;
var panY2 = startingPositionForLine;
var panX2 = startingPositionForLine;
var zoomY2 = startingPositionForLine;
var zoomX2 = startingPositionForLine;
// set starting zoom for specimen canvas
var specimenZoom = noZoom;

/* 

  Add pointer, label and background image into canvas

*/

// create a pointer line
var line = new fabric.Line([150, 35, panX2, panY2], {
  fill: "red",
  stroke: "red",
  strokeWidth: 3,
  strokeDashArray: [5, 2],
  // selectable: false,
  evented: false
});

// create text label
var text = new fabric.Text("Label 1", {
  left: 100,
  top: 0,
  // selectable: false,
  evented: false,
  backgroundColor: "red"
});

// add both into "Labels" canvas
labelsCanvas.add(text);
labelsCanvas.add(line);

// add a background image into Specimen canvas
fabric.Image.fromURL(
  "https://upload.wikimedia.org/wikipedia/commons/c/cb/Skull_brain_human_normal.svg",
  function(oImg) {
    oImg.left = 0;
    oImg.top = 0;
    oImg.scaleToWidth(300);
    oImg.scaleToHeight(300);
    specimenCanvas.add(oImg);
  }
);

/* 

  Handle mouse events

*/

// zoom the specimen image canvas via a mouse scroll-wheel event
labelsCanvas.on("mouse:wheel", function(opt) {
  // scroll value e.g. 5, 6 -1, -18
  var delta = opt.e.deltaY;
  // zoom level in specimen
  var zoom = specimenCanvas.getZoom();

  console.log("zoom ", zoom);

  // make zoom smaller
  zoom = zoom + delta / 200;
  // use sane defaults for zoom
  if (zoom > 20) zoom = 20;
  if (zoom < 0.01) zoom = 0.01;

  // create new zoom value
  zoomX2 = panX2 * zoom;
  zoomY2 = panY2 * zoom;
  // save the zoom
  specimenZoom = zoom;
  // set the specimen canvas zoom
  specimenCanvas.setZoom(zoom);

  // move line to sync it with the zoomed image
  line.set({
    x2: zoomX2,
    y2: zoomY2
  });

  console.log("zoomed line ", line.x2);

  // render the changes
  this.requestRenderAll();
  // block default mouse behaviour
  opt.e.preventDefault();
  opt.e.stopPropagation();

  console.log(labelsCanvas.viewportTransform[4]);

  // stuff I've tried to fix errors
  line.setCoords();
  specimenCanvas.calcOffset();

});

// pan the canvas
labelsCanvas.on("mouse:move", function(opt) {
  if (this.isDragging) {
    
    // pick up the click and drag event
    var e = opt.e;
    
    // sync the label position with the panning
    text.left = text.left + (e.clientX - this.lastPosX);

    var x2ToUse;
    var y2ToUse;

    // UNZOOMED canvas is being panned
    if (specimenZoom === noZoom) {
      x2ToUse = panX2;
      y2ToUse = panY2;
      
      // move the image using the difference between 
      // the current position and last known position 
      line.set({
        x1: line.x1 + (e.clientX - this.lastPosX),
        y1: line.y1,
        x2: x2ToUse + (e.clientX - this.lastPosX),
        y2: y2ToUse + (e.clientY - this.lastPosY)
      });
      
      // set the new panning value
      panX2 = line.x2;
      panY2 = line.y2;
      
      // stuff I've tried
      // zoomX2 = line.x2;
      // zoomY2 = line.y2;
    } 
    
    // ZOOMED canvas is being panned
    else 
    {
      x2ToUse = zoomX2;
      y2ToUse = zoomY2;
      
      // stuff I've tried
      // x2ToUse = panX2;
      // y2ToUse = panY2;

      // move the image using the difference between 
      // the current position and last known ZOOMED position 
      line.set({
        x1: line.x1 + (e.clientX - this.lastPosX),
        y1: line.y1,
        x2: x2ToUse + (e.clientX - this.lastPosX),
        y2: y2ToUse + (e.clientY - this.lastPosY)
      });
      
      zoomX2 = line.x2;
      zoomY2 = line.y2;
    }

    // hide label/pointer when it is out of view
    if (text.left < 0 || line.y2 < 35) {
      text.animate("opacity", "0", {
        duration: 15,
        onChange: labelsCanvas.renderAll.bind(labelsCanvas)
      });
      line.animate("opacity", "0", {
        duration: 15,
        onChange: labelsCanvas.renderAll.bind(labelsCanvas)
      });
    } 
    // show label/pointer when it is in view
    else 
    {
      text.animate("opacity", "1", {
        duration: 25,
        onChange: labelsCanvas.renderAll.bind(labelsCanvas)
      });
      line.animate("opacity", "1", {
        duration: 25,
        onChange: labelsCanvas.renderAll.bind(labelsCanvas)
      });
    }


    specimenCanvas.viewportTransform[4] += e.clientX - this.lastPosX;

    specimenCanvas.viewportTransform[5] += e.clientY - this.lastPosY;

    this.requestRenderAll();
    specimenCanvas.requestRenderAll();

    this.lastPosX = e.clientX;
    this.lastPosY = e.clientY;
  }

  console.log(line.x2);

  wasPanned = true;
});

labelsCanvas.on("mouse:down", function(opt) {
  var evt = opt.e;
  if (evt.altKey === true) {
    this.isDragging = true;
    this.selection = false;
    this.lastPosX = evt.clientX;
    this.lastPosY = evt.clientY;
  }
});

labelsCanvas.on("mouse:up", function(opt) {
  this.isDragging = false;
  this.selection = true;
});
.canvas-container {
    position: absolute!important;
    left: 0!important;
    top: 0!important;
}

.canvas {
    position: absolute;
    top: 0;
    right: 0;
    border: solid red 1px;
}

.label-canvas {
    z-index: 2;
}

.specimen-canvas {
    z-index: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
<h1>
  Dual canvas test
</h1>
<div style="position: relative; height: 300px">
  <canvas class="canvas specimen-canvas" id="specimenCanvas" width="300" height="300"></canvas>
  <canvas class="canvas label-canvas" id="labelsCanvas" width="300" height="300"></canvas>
</div>

【问题讨论】:

    标签: javascript canvas zooming fabricjs pan


    【解决方案1】:

    作为旁注,我认为你有点过于复杂了。您真的不需要分别存储panX/panYzoomX/zoomY(我猜是缩放平移),它们已经在您的line 坐标中。只是说,因为它们可能导致了混乱/调试。然而,修复的核心思想是您应该将line 的坐标乘以newZoom / previousZoom 比率而不是整个缩放值。我已经更新了你的 sn-p,它似乎按预期工作:

    /*
    
      "mouse:wheel" event is where zooms are handled
      "mouse:move" event is where panning is handled
    
    */
    
    // create Fabric JS canvas'
    var labelsCanvas = new fabric.Canvas("labelsCanvas");
    var specimenCanvas = new fabric.Canvas("specimenCanvas");
    //set defaults
    var startingPositionForLine = 100;
    const noZoom = 1;
    var wasPanned = false;
    var panY2 = startingPositionForLine;
    var panX2 = startingPositionForLine;
    var zoomY2 = startingPositionForLine;
    var zoomX2 = startingPositionForLine;
    // set starting zoom for specimen canvas
    var specimenZoom = noZoom;
    
    var prevZoom = noZoom;
    
    /* 
    
      Add pointer, label and background image into canvas
    
    */
    
    // create a pointer line
    var line = new fabric.Line([150, 35, panX2, panY2], {
      fill: "red",
      stroke: "red",
      strokeWidth: 3,
      strokeDashArray: [5, 2],
      // selectable: false,
      evented: false
    });
    
    // create text label
    var text = new fabric.Text("Label 1", {
      left: 100,
      top: 0,
      // selectable: false,
      evented: false,
      backgroundColor: "red"
    });
    
    // add both into "Labels" canvas
    labelsCanvas.add(text);
    labelsCanvas.add(line);
    
    // add a background image into Specimen canvas
    fabric.Image.fromURL(
      "https://upload.wikimedia.org/wikipedia/commons/c/cb/Skull_brain_human_normal.svg",
      function(oImg) {
        oImg.left = 0;
        oImg.top = 0;
        oImg.scaleToWidth(300);
        oImg.scaleToHeight(300);
        specimenCanvas.add(oImg);
      }
    );
    
    window.specimenCanvas = specimenCanvas
    
    /* 
    
      Handle mouse events
    
    */
    
    // zoom the specimen image canvas via a mouse scroll-wheel event
    labelsCanvas.on("mouse:wheel", function(opt) {
      // scroll value e.g. 5, 6 -1, -18
      var delta = opt.e.deltaY;
      // zoom level in specimen
      var zoom = specimenCanvas.getZoom();
      var lastZoom = zoom
    
      // make zoom smaller
      zoom = zoom + delta / 200;
      // use sane defaults for zoom
      if (zoom > 20) zoom = 20;
      if (zoom < 0.01) zoom = 0.01;
    
      // save the zoom
      specimenZoom = zoom;
      // set the specimen canvas zoom
      specimenCanvas.setZoom(zoom);
    
      // move line to sync it with the zoomed image
      var zoomRatio = zoom / lastZoom
      console.log('zoom ratio: ', zoomRatio)
      line.set({
        x2: line.x2 * zoomRatio,
        y2: line.y2 * zoomRatio
      });
      
      // console.log("zoomed line ", line.x2);
    
      // render the changes
      this.requestRenderAll();
      // block default mouse behaviour
      opt.e.preventDefault();
      opt.e.stopPropagation();
    
      // console.log(labelsCanvas.viewportTransform[4]);
    
      // stuff I've tried to fix errors
      line.setCoords();
      specimenCanvas.calcOffset();
    });
    
    // pan the canvas
    labelsCanvas.on("mouse:move", function(opt) {
      if (this.isDragging) {
        
        // pick up the click and drag event
        var e = opt.e;
        
        // sync the label position with the panning
        text.left = text.left + (e.clientX - this.lastPosX);
    
        // UNZOOMED canvas is being panned
        if (specimenZoom === noZoom) {
          x2ToUse = panX2;
          y2ToUse = panY2;
          
          // move the image using the difference between 
          // the current position and last known position 
          line.set({
            x1: line.x1 + (e.clientX - this.lastPosX),
            y1: line.y1,
            x2: line.x2 + (e.clientX - this.lastPosX),
            y2: line.y2 + (e.clientY - this.lastPosY)
          });
          
          // stuff I've tried
          // zoomX2 = line.x2;
          // zoomY2 = line.y2;
        } 
        
        // ZOOMED canvas is being panned
        else 
        {
          // move the image using the difference between 
          // the current position and last known ZOOMED position 
          line.set({
            x1: line.x1 + (e.clientX - this.lastPosX),
            y1: line.y1,
            x2: line.x2 + (e.clientX - this.lastPosX),
            y2: line.y2 + (e.clientY - this.lastPosY)
          });
        }
    
        // hide label/pointer when it is out of view
        if (text.left < 0 || line.y2 < 35) {
          text.animate("opacity", "0", {
            duration: 15,
            onChange: labelsCanvas.renderAll.bind(labelsCanvas)
          });
          line.animate("opacity", "0", {
            duration: 15,
            onChange: labelsCanvas.renderAll.bind(labelsCanvas)
          });
        } 
        // show label/pointer when it is in view
        else 
        {
          text.animate("opacity", "1", {
            duration: 25,
            onChange: labelsCanvas.renderAll.bind(labelsCanvas)
          });
          line.animate("opacity", "1", {
            duration: 25,
            onChange: labelsCanvas.renderAll.bind(labelsCanvas)
          });
        }
    
    
        specimenCanvas.viewportTransform[4] += e.clientX - this.lastPosX;
    
        specimenCanvas.viewportTransform[5] += e.clientY - this.lastPosY;
    
        this.requestRenderAll();
        specimenCanvas.requestRenderAll();
    
        this.lastPosX = e.clientX;
        this.lastPosY = e.clientY;
        prevZoom = specimenCanvas.getZoom()
      }
    
      // console.log(line.x2);
    
      wasPanned = true;
    });
    
    labelsCanvas.on("mouse:down", function(opt) {
      var evt = opt.e;
      if (evt.altKey === true) {
        this.isDragging = true;
        this.selection = false;
        this.lastPosX = evt.clientX;
        this.lastPosY = evt.clientY;
      }
    });
    
    labelsCanvas.on("mouse:up", function(opt) {
      this.isDragging = false;
      this.selection = true;
    });
    .canvas-container {
        position: absolute!important;
        left: 0!important;
        top: 0!important;
    }
    
    .canvas {
        position: absolute;
        top: 0;
        right: 0;
        border: solid red 1px;
    }
    
    .label-canvas {
        z-index: 2;
    }
    
    .specimen-canvas {
        z-index: 1;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
    <h1>
      Dual canvas test
    </h1>
    <div style="position: relative; height: 300px">
      <canvas class="canvas specimen-canvas" id="specimenCanvas" width="300" height="300"></canvas>
      <canvas class="canvas label-canvas" id="labelsCanvas" width="300" height="300"></canvas>
    </div>

    【讨论】:

    • 感谢 shkaper,你是冠军!如果我理解正确,您建议放弃变量,而是直接将这些值从 line.x2/y2 中取出,因为变量是不必要的步骤?这就说得通了。很抱歉我的投票没有显示在这里,因为我还是新手,但我非常感谢您的帮助。
    • @zaynbuksh 不客气,我很高兴它有帮助! 如果我理解正确,您建议放弃变量,而是直接将这些值从 line.x2/y2 中取出,因为变量是不必要的步骤? 没错。但这正是我的感受,我花了一些时间查看代码。
    猜你喜欢
    • 2022-07-01
    • 2017-08-16
    • 2019-02-28
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多