Fabric JS: синхронизировать положение объекта после панорамирования / масштабирования в двух экземплярах - PullRequest
0 голосов
/ 12 ноября 2018

Ткань JS вопрос.

SCREENSHOT так выглядит мое приложение Fabric JS

CODEPEN https://codepen.io/zaynbuksh/pen/VVKRxj?editors=0011 ( alt-click-drag для панорамирования, колесо прокрутки для увеличения)

TLDR; Как заставить линию придерживаться после панорамирования и масштабирования несколько раз?


Я разрабатываю приложение Fabric JS для маркировки изображений образцов. Как часть этого, люди хотят иметь возможность увеличивать то, на что указывает каждая метка. Меня попросили, чтобы этикетки оставались видимыми при увеличении изображения образца. Из исследований люди рекомендуют два полотна, накладываемые друг на друга.

Я создал два экземпляра Fabric JS canvas, наложенных друг на друга. Холст внизу содержит фоновое изображение, которое можно увеличивать и панорамировать, холст над ним показывает линию / метку указателя, которая не масштабируется (чтобы метка оставалась видимой всегда).

Сначала все работает - линия синхронизируется с изображением, когда я перемещаюсь и увеличиваю только время first . После этого у меня возникают проблемы с синхронизацией строки с изображением.

Проблема проявляется, когда я перемещаюсь, затем увеличиваю два или более раз. Проблема повторяется каждый раз, когда я поворачиваюсь и затем увеличиваю, то есть линия перемещается, когда я изменяю масштаб, но затем остается в синхронизация при панорамировании, перемещение снова при повторном увеличении, панорамирование и т. д.


(панорама обрабатывается перетаскиванием по нажатию Alt, 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>

1 Ответ

0 голосов
/ 12 ноября 2018

Как примечание, я думаю, что вы немного усложняете вещи. Вам на самом деле не нужно хранить panX / panY и zoomX / zoomY (увеличенное панорамирование, как я уже догадался), они уже находятся в ваших line координатах. Просто говорю, потому что они, вероятно, способствовали путанице / отладке. Однако основная идея исправления заключается в том, что вы должны умножать координаты line не на все значение масштабирования, а на коэффициент newZoom / previousZoom. Я обновил ваш фрагмент, похоже, он работает как положено:

/*

  "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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...