不同屏幕分辨率和宽高比下的Load From JSON

2022-04-07 00:00:00 canvas javascript html5-canvas fabricjs

我需要能够将loadFromJSON与生成JSON数据时使用的画布分辨率/纵横比一起使用,同时保持图形元素的关系并使画布中的所有内容居中。

我已经尝试了我所见或想出的每一种解决方案。在这一点上,我不知道是我的逻辑不好,是我的算术还是我的编码。

此处包含功能代码(从我失败的尝试中剔除)。只有第一个函数很重要,其余的是UI样板和JSON数据。这里有一个JS小提琴,如果这更容易的话:https://jsfiddle.net/sunny001/a8thqd0z/24/

详细信息:我使用自定义widthheight属性保存JSON数据,以便我知道创建数据时的分辨率/纵横比。然后,我使用这些属性来确定如何缩放对象。canvas始终设置为窗口的大小,大小可以有所不同。我见过一些使用Canvas";Zoom";属性的解决方案,但我不能这样做,因为该应用程序允许用户放大他们正在批注的文档。

背景这是针对桌面electron应用程序的,用户可以在该应用程序中为文本文档添加批注,因此准确定位非常重要。用户可以在窗口模式或全屏模式下创建和显示批注。

数据-lang="js"数据-隐藏="假"数据-控制台="假"数据-巴贝尔="假">
'use strict';


let canvasA
let canvasB

// these are the canvas dimensions
let A = {
  width: 320,
  height: 190
}
let B = {
  width: 225,
  height: 150
}

function loadAnnotation(theCanvas, theData, id) {

  theCanvas.clear()

  var containerWidth = theCanvas.getWidth()
  var containerHeight = theCanvas.getHeight()
  var originalWidth = theData.width
  var originalHeight = theData.height
  var scaleFactor

  theCanvas.loadFromJSON(theData, function() {

    /**
     * the canvas seems to change size "on its own" based on the JSON data
     * width & height properties so this hack resets it 
     * 
     * setDimensions() seems buggy – screen redraw artifacts so using setWidth() & setheight()
     * 
     **/
    if (id == "A") {
      // theCanvas.setDimensions(A.width, A.height)
      theCanvas.setWidth(A.width)
      theCanvas.setHeight(A.height)
    } else {
      // theCanvas.setDimensions(B.width, B.height)
      theCanvas.setWidth(B.width)
      theCanvas.setHeight(B.height)
    }

    // just logging code
    if (id == "A") {
      logA(`data w/h:  ${originalWidth} x ${originalHeight}`)
      logA(`canvas w/h:  ${theCanvas.getWidth()} x ${theCanvas.getHeight()}`)
      logA(`canvas zoom:  ${theCanvas.getZoom()}`)
      logA(`scaleFactor: ${scaleFactor}`)
    } else {
      logB(`data w/h:  ${originalWidth} x ${originalHeight}`)
      logB(`canvas w/h:  ${theCanvas.getWidth()} x ${theCanvas.getHeight()}`)
      logB(`canvas zoom:  ${theCanvas.getZoom()}`)
      logB(`scaleFactor: ${scaleFactor}`)
    }

  }, function(o, object) {

    var widthRatio = containerWidth / originalWidth
    var heightRatio = containerHeight / originalHeight

    // if (widthRatio <= heightRatio) {
    if (widthRatio > heightRatio) {
      scaleFactor = widthRatio
    } else {
      scaleFactor = heightRatio
    }

    object.scaleX = object.scaleX * scaleFactor
    object.scaleY = object.scaleY * scaleFactor
    object.left = object.left * scaleFactor
    object.top = object.top * scaleFactor

    object.setCoords()
  })

  theCanvas.renderAll();
  theCanvas.calcOffset();
}




// Everything below here is UI code & JSON data

document.addEventListener("DOMContentLoaded", (event) => {
  var today = new Date();
  var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
  console.log(time)

  var canvasAloadA = document.querySelector("#canA-loadA")
  canvasAloadA.addEventListener("click", function(event) {
    logA()
    loadAnnotation(canvasA, canvasA_data, "A")
  })

  var canvasAloadB = document.querySelector("#canA-loadB")
  canvasAloadB.addEventListener("click", function(event) {
    logA()
    loadAnnotation(canvasA, canvasB_data, "A")
  });

  var clearA = document.querySelector("#canA-clear")
  clearA.addEventListener("click", function(event) {
    canvasA.clear()
    logA()
  });


  var saveA = document.querySelector("#canA-save")
  saveA.addEventListener("click", function(event) {

    canvasA_data = canvasA.toObject(["width", "height"])
    // canvasA_data = JSON.stringify(canvasA.toObject(["width", "height"]));
    // console.log('Save. A', JSON.stringify(canvasA_data))
  });


  var canvasBloadA = document.querySelector("#canB-loadA")
  canvasBloadA.addEventListener("click", function(event) {
    logB()
    loadAnnotation(canvasB, canvasA_data, "B")
  })

  var canvasBloadB = document.querySelector("#canB-loadB")
  canvasBloadB.addEventListener("click", function(event) {
    logB()
    loadAnnotation(canvasB, canvasB_data, "B")
  });

  var clearB = document.querySelector("#canB-clear")
  clearB.addEventListener("click", function(event) {
    canvasB.clear()
    logB()
  });

  var saveB = document.querySelector("#canB-save")
  saveB.addEventListener("click", function(event) {
    canvasB_data = canvasB.toObject(["width", "height"])
    // canvasB_data = JSON.stringify(canvasB.toObject(["width", "height"]));
    console.log('Save. B', JSON.stringify(canvasB_data))
  })

  setUpFabric()
})


function setUpFabric() {

  canvasA = new fabric.Canvas('canvas-A', {
    backgroundColor: '#FFFFFF',
    width: 320,
    height: 190
  })

  loadAnnotation(canvasA, canvasA_data, "A")
  // canvasA_LoadData() // only used to generate initial JSON data
  canvasA.renderAll();

  canvasB = new fabric.Canvas('canvas-B', {
    backgroundColor: '#FFFFFF',
    width: 225,
    height: 150
  });

  loadAnnotation(canvasB, canvasB_data, "B")
  // canvasB_LoadData() // only used to generate initial JSON data

  canvasB.renderAll();
}

/**
 * fitInBox
 * Constrains a box (width x height) to fit in a containing box (maxWidth x maxHeight), preserving the aspect ratio
 * @param width      width of the box to be resized
 * @param height     height of the box to be resized
 * @param maxWidth   width of the containing box
 * @param maxHeight  height of the containing box
 * @param expandable (Bool) if output size is bigger than input size, output is left unchanged (false) or expanded (true)
 * @return           {width, height} of the resized box
 */
function fitInBox(width, height, maxWidth, maxHeight, expandable) {
  "use strict";

  var aspect = width / height,
    initWidth = width,
    initHeight = height;

  if (width > maxWidth || height < maxHeight) {
    width = maxWidth;
    height = Math.floor(width / aspect);
  }

  if (height > maxHeight || width < maxWidth) {
    height = maxHeight;
    width = Math.floor(height * aspect);
  }

  if (!!expandable === false && (width >= initWidth || height >= initHeight)) {
    width = initWidth;
    height = initHeight;
  }

  return {
    width: width,
    height: height
  };
}



function logA(txt) {
  if (txt == undefined) {
    document.getElementById('canA').value = ""
  } else {
    document.getElementById('canA').value += `
${txt}`
  }
}



function logB(txt) {
  if (txt == undefined) {
    document.getElementById('canB').value = ""
  } else {
    document.getElementById('canB').value += `
${txt}`
  }
}


// this is for initial JSON data setup only - not used in demo
function canvasA_LoadData() {

  var elA = document.getElementById('test-imageA');
  var imgA = new fabric.Image(elA, {
    left: 0,
    top: 0,
    selectable: true
  })
  canvasA.add(imgA);

  var containerWidth = canvasA.getWidth()
  var containerHeight = canvasA.getHeight()

  var result = fitInBox(imgA.width, imgA.height, containerWidth, containerHeight, true)

  imgA.scaleToWidth(result.width)

  var xOffset = (containerWidth - imgA.getScaledWidth()) / 2
  var yOffset = (containerHeight - imgA.getScaledHeight()) / 2

  imgA.set({
    left: xOffset,
    top: yOffset
  })
  imgA.setCoords()

  var rect = new fabric.Rect({
    left: 100,
    top: 0,
    fill: 'red',
    width: 20,
    height: 20
  });

  var circle = new fabric.Circle({
    radius: 20,
    stroke: 'green',
    strokeWidth: 12,
    fill: null,
    left: 200,
    top: 130
  });

  var triangle = new fabric.Triangle({
    width: 40,
    height: 40,
    fill: 'blue',
    left: 50,
    top: 140
  });

  var txt = new fabric.Text("Canvas A 320 x 190", {
    fontSize: 24,
    left: 50,
    top: 50,
    fill: 'white'
  })

  canvasA.add(rect, circle, triangle, txt);

  canvasA.calcOffset();
  canvasA.renderAll();
}

// this is for initial JSON data setup only - not used in demo
function canvasB_LoadData() {

  var elB = document.getElementById('test-imageB');

  var imgB = new fabric.Image(elB, {
    left: 0,
    top: 0,
    selectable: true
  })
  canvasB.add(imgB)

  var containerWidth = canvasB.getWidth()
  var containerHeight = canvasB.getHeight()

  var result = fitInBox(imgB.width, imgB.height, containerWidth, containerHeight, true)

  imgB.scaleToWidth(result.width)

  var xOffset = (containerWidth - imgB.getScaledWidth()) / 2
  var yOffset = (containerHeight - imgB.getScaledHeight()) / 2

  imgB.set({
    left: xOffset,
    top: yOffset
  })
  imgB.setCoords()

  var rect = new fabric.Rect({
    left: 0,
    top: 0,
    fill: 'orange',
    width: 60,
    height: 60
  })

  var circle = new fabric.Circle({
    radius: 40,
    stroke: 'red',
    strokeWidth: 12,
    fill: null,
    left: 120,
    top: 40
  })

  var triangle = new fabric.Triangle({
    width: 40,
    height: 40,
    fill: 'black',
    left: 50,
    top: 100
  });

  var txt = new fabric.Text("Canvas B 225 x 150", {
    fontSize: 20,
    left: 40,
    top: 40,
    fill: 'blue'
  })

  canvasB.add(rect, circle, triangle, txt);

  canvasB.calcOffset();
  canvasB.renderAll();
}



let canvasB_data = {
  "version": "4.3.0",
  "objects": [{
      "type": "image",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 62.5,
      "top": 0,
      "width": 400,
      "height": 600,
      "fill": "rgb(0,0,0)",
      "stroke": null,
      "strokeWidth": 0,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 0.25,
      "scaleY": 0.25,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "cropX": 0,
      "cropY": 0,
      "src": "https://placekitten.com/400/600",
      "crossOrigin": null,
      "filters": []
    },
    {
      "type": "rect",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 0,
      "top": 0,
      "width": 60,
      "height": 60,
      "fill": "orange",
      "stroke": null,
      "strokeWidth": 1,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1,
      "scaleY": 1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "rx": 0,
      "ry": 0
    },
    {
      "type": "circle",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 61.52,
      "top": 24.21,
      "width": 80,
      "height": 80,
      "fill": null,
      "stroke": "red",
      "strokeWidth": 12,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1.1,
      "scaleY": 1.1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "radius": 40,
      "startAngle": 0,
      "endAngle": 6.283185307179586
    },
    {
      "type": "triangle",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 163,
      "top": 107.89,
      "width": 40,
      "height": 40,
      "fill": "black",
      "stroke": null,
      "strokeWidth": 1,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1,
      "scaleY": 1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0
    },
    {
      "type": "text",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 2.33,
      "top": 39.01,
      "width": 162.216796875,
      "height": 22.599999999999998,
      "fill": "blue",
      "stroke": null,
      "strokeWidth": 1,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1,
      "scaleY": 1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "text": "Canvas B 225 x 150",
      "fontSize": 20,
      "fontWeight": "normal",
      "fontFamily": "Times New Roman",
      "fontStyle": "normal",
      "lineHeight": 1.16,
      "underline": false,
      "overline": false,
      "linethrough": false,
      "textAlign": "left",
      "textBackgroundColor": "",
      "charSpacing": 0,
      "styles": {}
    }
  ],
  "background": "#FFFFFF",
  "width": 225,
  "height": 150
}

let canvasA_data = {
  "version": "4.3.0",
  "objects": [{
      "type": "image",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 17.5,
      "top": 0,
      "width": 600,
      "height": 400,
      "fill": "rgb(0,0,0)",
      "stroke": null,
      "strokeWidth": 0,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 0.47,
      "scaleY": 0.47,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "cropX": 0,
      "cropY": 0,
      "src": "https://placekitten.com/600/400",
      "crossOrigin": null,
      "filters": []
    },
    {
      "type": "rect",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 277.89,
      "top": 73.23,
      "width": 20,
      "height": 20,
      "fill": "red",
      "stroke": null,
      "strokeWidth": 1,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1,
      "scaleY": 1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "rx": 0,
      "ry": 0
    },
    {
      "type": "circle",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 92.67,
      "top": -5.57,
      "width": 40,
      "height": 40,
      "fill": null,
      "stroke": "green",
      "strokeWidth": 12,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1.73,
      "scaleY": 1.73,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "radius": 20,
      "startAngle": 0,
      "endAngle": 6.283185307179586
    },
    {
      "type": "triangle",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 17.2,
      "top": 146.93,
      "width": 40,
      "height": 40,
      "fill": "blue",
      "stroke": null,
      "strokeWidth": 1,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1,
      "scaleY": 1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0
    },
    {
      "type": "text",
      "version": "4.3.0",
      "originX": "left",
      "originY": "top",
      "left": 51.99,
      "top": 96.51,
      "width": 195.984375,
      "height": 27.119999999999994,
      "fill": "white",
      "stroke": null,
      "strokeWidth": 1,
      "strokeDashArray": null,
      "strokeLineCap": "butt",
      "strokeDashOffset": 0,
      "strokeLineJoin": "miter",
      "strokeMiterLimit": 4,
      "scaleX": 1,
      "scaleY": 1,
      "angle": 0,
      "flipX": false,
      "flipY": false,
      "opacity": 1,
      "shadow": null,
      "visible": true,
      "backgroundColor": "",
      "fillRule": "nonzero",
      "paintFirst": "fill",
      "globalCompositeOperation": "source-over",
      "skewX": 0,
      "skewY": 0,
      "text": "Canvas A 320 x 190",
      "fontSize": 24,
      "fontWeight": "normal",
      "fontFamily": "Times New Roman",
      "fontStyle": "normal",
      "lineHeight": 1.16,
      "underline": false,
      "overline": false,
      "linethrough": false,
      "textAlign": "left",
      "textBackgroundColor": "",
      "charSpacing": 0,
      "styles": {}
    }
  ],
  "background": "#FFFFFF",
  "width": 320,
  "height": 190
}
#container {
  display: grid;
  grid-template-columns: 330px 235px;
  gap: 20px;
  grid-template-rows: 300px, 20px, 20px, 100px;
}

.canvas-wrapper {
  margin-left: 10px;
  grid-column: 1;
  justify-self: center;
}

.panel {
  grid-column: 2;
  margin-top: 20px;
}

.buttons {
  font-family: sans-serif;
  font-size: 10pt;
  width: 100px;
  margin: 3px 0;
}

.labels {
  font-family: sans-serif;
  font-size: 12pt;
}
<!DOCTYPE html>
<html>

<head>
  <title></title>

  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/fabric@4.3.0/dist/fabric.js"></script>
  <script type="text/javascript" src="js/fabric-resizing.js" defer></script>

  <link rel="stylesheet" href="css/fabric-resize.css">
</head>

<body>
  <div id="container" width="100%" height="100%">
    <div class="canvas-wrapper">
      <div class="labels">Canvas A - 320 x 190 px</div>
      <div>
        <canvas id="canvas-A" width="320" height="200" style="border:1px solid #000000;"></canvas>
      </div>
    </div>
    <div class="panel">
      <textarea name="canA" id="canA" cols="23" rows="8"></textarea>
      <div>
        <button id="canA-loadB" class="buttons">Load B Json</button>
        <button id="canA-loadA" class="buttons">Load A Json</button>
      </div>
      <div>
        <button id="canA-clear" class="buttons">Clear A</button>
        <button id="canA-save" class="buttons">Save A Json</button>
      </div>
    </div>
    <div class="canvas-wrapper">
      <div class="labels">Canvas B - 225 x 150 px</div>
      <canvas id="canvas-B" width="225" height="150" style="border:1px solid #000000;"></canvas>
    </div>
    <div class="panel">
      <textarea name="canB" id="canB" cols="23" rows="8"></textarea>
      <div>
        <button id="canB-loadA" class="buttons">Load A Json</button>
        <button id="canB-loadB" class="buttons">Load B Json</button>
      </div>
      <div>
        <button id="canB-clear" class="buttons">Clear B</button>
        <button id="canB-save" class="buttons">Save B Json</button>
      </div>
    </div>
    <div class="labels" style="position:fixed; left:50px; top:455px;">
      600 x 400 px
    </div>
    <div>
      <img id="test-imageA" src="https://placekitten.com/600/400" style="position:fixed; left:50px; top:475px; width:25%; border:none;" />
    </div>
    <div class="labels" style="position:fixed; left:250px; top:455px;">
      400 x 600 px
    </div>
    <div>
      <img id="test-imageB" src="https://placekitten.com/400/600" style="position:fixed; left:250px; top:475px; height:25%; border:none;" />
    </div>
  </div>
</body>

</html>


解决方案

在尝试了许多不同的方法(也非常痛苦)后,终于解决了这个问题。

要点是选择所有对象,然后缩放和居中。一个棘手的问题是,我的数据中的第一个对象是一个图像,所有绘制的元素都需要与其保持注册。因此,我首先更正图像路径,然后,在缩放所选内容后,将所选内容切换为使图像在屏幕上保持居中。

现在看一下代码,我可以看到优化它的地方(例如,因为我的数据中只有一个图像,所以在找到图像后不需要遍历所有剩余的数据)


loadData(data) {

    var jsonObj = JSON.parse(data);

    jsonObj.objects.forEach(element => {
      if (element.type == "image") {
        var imgPath = element.src.split("assets")

        if (imgPath.length > 1) {
          element.src = upath.joinSafe(projectDirectory, "assets", imgPath[1])
        }
      }
    });

    var self = this;
    this.canvas.loadFromJSON(jsonObj, function () {

      var selection = new fabric.ActiveSelection(self.canvas.getObjects(), { canvas: self.canvas });


      // var selectionWidth = (selection.width >  self.canvas.getWidth()) ?  self.canvas.getWidth() : selection.width
      // var selectionHeight = (selection.height > self.canvas.getHeight()) ? self.canvas.getHeight() : selection.height

      var sizeObj = self.resizer(
        { width: self.canvas.getWidth(), height: self.canvas.getHeight() },
        { width: selection.width, height: selection.height });

      // console.log('sizeObj', sizeObj);

      // selection.scaleToWidth(sizeObj.width, false)
      // selection.scaleToWidth(sizeObj.width, true)
      selection.scaleToHeight(sizeObj.height)
      // selection.scaleToHeight(sizeObj.height, true)

      selection.center();


      /**
       * ------------------------------------------
       * This keeps the IMAGE centered on the canvas instead of 
       * just centering  the selection – otherwise the image will shift 
       */
      var selectionObjs = selection.getObjects();
      var imgObj = selectionObjs[0]
      var matrix = selection.calcTransformMatrix();
      var finalPosition = fabric.util.transformPoint({ x: imgObj.left, y: imgObj.top }, matrix);

      sizeObj = self.resizer(
        { width: self.canvas.getWidth(), height: self.canvas.getHeight() },
        { width: imgObj.getScaledWidth(), height: imgObj.getScaledHeight() });

      selection.left += sizeObj.x - finalPosition.x;
      selection.top += sizeObj.y - finalPosition.y;
      // ------------------------------------------

      selection.setCoords()
      selection.destroy()

      self.canvas.renderAll();
      self.canvas.calcOffset()

    }, function (o, object) {

    })

    self.setObjectsSelectable(self.toolbarIsVisible)
    self.toolActive = false
  }

resizer(canvas, imageObj) {
    var imageAspectRatio = imageObj.width / imageObj.height;
    var canvasAspectRatio = canvas.width / canvas.height;
    var renderableHeight, renderableWidth, xStart, yStart;

    // If image's aspect ratio is less than canvas's we fit on height
    // and place the image centrally along width
    if (imageAspectRatio < canvasAspectRatio) {
      renderableHeight = canvas.height;
      renderableWidth = imageObj.width * (renderableHeight / imageObj.height);
      xStart = (canvas.width - renderableWidth) / 2;
      yStart = 0;
    }

    // If image's aspect ratio is greater than canvas's we fit on width
    // and place the image centrally along height
    else if (imageAspectRatio > canvasAspectRatio) {
      renderableWidth = canvas.width
      renderableHeight = imageObj.height * (renderableWidth / imageObj.width);
      xStart = 0;
      yStart = (canvas.height - renderableHeight) / 2;
    }

    // Happy path - keep aspect ratio
    else {
      renderableHeight = canvas.height;
      renderableWidth = canvas.width;
      xStart = 0;
      yStart = 0;
    }
    return { x: xStart, y: yStart, width: renderableWidth, height: renderableHeight }
  }

相关文章