使用其对象调整 canvas 大小的最佳方法

The best way to resize canvas with its objects

我有一个 Fabric.js canvas 里面有一个带有 boostrap 的响应式设计。 要调整 canvas 的大小,我使用 canvas.setDimensions({width:w, height:h}); 来自当前window的宽度和高度的clientWidth和clientHeight。这是我的 canvas 和他的容器 :

<div style="width: 100%;" class="canvas-container">
   <canvas id="canvas" class='img-fluid'></canvas>
</div>

调整 canvas 大小时,对于 canvas 中的每个对象,我会按比例更改它们的大小和位置。我使用原始和新的 canvas 大小计算了一个应用于每个对象 top/left/width/height 的系数。每次调整 canvas 大小时运行此代码:

 function resizeCanvas() {
    const outerCanvasContainer = $('.canvas-container')[0];
    const ratio = canvas.getWidth() / canvas.getHeight();
    const containerWidth   = outerCanvasContainer.clientWidth;
    const containerHeight  = outerCanvasContainer.clientHeight;
    const scale = containerWidth / canvas.getWidth();
    const zoom  = canvas.getZoom() * scale;

    canvas.setDimensions({width: containerWidth, height: containerWidth / ratio});
    canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);

    canvas.calcOffset();
    canvas.forEachObject(function(o) {
       o.setCoords();
    });

        if (canvas.width != containerWidth) {
          var scaleMultiplier = newWidth / canvas.width;
          var objects = canvas.getObjects();
          var factorX = newWidth / canvas.getWidth();
          var factorY = newWidth / canvas.getHeight();
          for (var i in objects) {
              objects[i].scaleX = objects[i].scaleX *factorX;
              objects[i].scaleY = objects[i].scaleY *factorX;
              objects[i].left = factorX * objects[i].left;
              objects[i].top = factorX * objects[i].top ;
              objects[i].setCoords();
          };

          canvas.setWidth(canvas.getWidth() * factorX);
          canvas.setHeight(canvas.getHeight() * factorY);
          canvas.renderAll();
          canvas.calcOffset();
        }
}

$(window).resize(resizeCanvas);

调整大小效果很好,所有对象都正确地改变了它们的尺寸。 问题是在 canvas 调整大小后无法选择大多数对象。 此外,单击 canvas 的空白区域有时会选择对象...

// Create a new instance of Canvas
var canvas = new fabric.Canvas("canvas");
canvas.setDimensions({width: 1000, height: 1000});
resizeCanvas();

// Create a new Text instance
var text = new fabric.Text('Texte', {
    fill: '#000',
    fontSize: 100,
    borderColor: '#000',
    cornerColor: '#000'
});

// Render the Text on Canvas
canvas.add(text);
canvas.centerObject(text);        
canvas.setActiveObject(text);

// function to resize canvas and its objects
function resizeCanvas() {
    const outerCanvasContainer = $('.canvas-container')[0];
    const ratio = canvas.getWidth() / canvas.getHeight();
    const containerWidth   = outerCanvasContainer.clientWidth;
    const containerHeight  = outerCanvasContainer.clientHeight;
    const scale = containerWidth / canvas.getWidth();
    const zoom  = canvas.getZoom() * scale;

    canvas.setDimensions({width: containerWidth, height: containerWidth / ratio});
    canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);

    canvas.calcOffset();
    canvas.forEachObject(function(o) {
      o.setCoords();
    });

  if (canvas.width != containerWidth) {
    var containerWidth_ = $(outerCanvasContainer).width();
    var scaleMultiplier = containerWidth_ / canvas.width;
    var objects = canvas.getObjects();
    var factorX = containerWidth_ / canvas.getWidth();
    var factorY = containerWidth_ / canvas.getHeight();
    for (var i in objects) {
        objects[i].scaleX = objects[i].scaleX *factorX;
        objects[i].scaleY = objects[i].scaleY *factorX;
        objects[i].left = factorX * objects[i].left + 15;
        objects[i].top = factorX * objects[i].top + 10;
        objects[i].setCoords();
    };
    
    canvas.setWidth(canvas.getWidth() * factorX);
    canvas.setHeight(canvas.getHeight() * factorY);
    canvas.renderAll();
    canvas.calcOffset();
  }
}

$(window).resize(resizeCanvas);
body { background-color:#000 }
#canvas{border: 1px solid #666}
.upper-canvas {max-width: 1000px;max-height: 1000px;}
.container{margin:0;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/521/fabric.min.js"></script>
<script src="https://raw.githubusercontent.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<div class="container mx-auto">
   <div class="col-12">           
      <div class="row">
        <div style="width: 100%;" class="canvas-container">
            <canvas id="canvas" class='img-fluid'>
              <p>This is fallback content for users of assistive technologies or of browsers that don't have full support for the Canvas API.</p>
            </canvas>
        </div>
      </div>
    </div>   
</div>

正如您在代码片段中看到的,当页面加载时,您可以随意修改文本,但如果您单击“整页”,您将无法再这样做,或者变得很难做到。

这里有一个解释问题的视频:https://a.uguu.se/pgOYGfJy.webm

我在互联网上做了很多研究并尝试了很多东西,我想知道 是否有人知道在保持正确值的同时进行响应式设计的最佳方法canvas objects 为了避免这个问题的发生。

谢谢

很抱歉告诉你这个消息,但你的努力可能是多余的。 :(

似乎 setDimensions 方法的 backstoreOnlycssOnly 选项为我们完成了所有工作。

我大大简化了您的代码,它似乎与您的代码完全一样。 “完全”是指这两个代码都受到同一个问题的影响。

问题似乎不是你(或我)的代码引起的,似乎是织物本身引起的;当 canvas 高于视口时,问题似乎就显现出来了。

如果我们展开片段,我们可以看到它们都工作正常,直到 canvas 的高度小于视口的高度。

查看实际问题:

  1. 展开代码段
  2. 调整大小使 canvas 高于视口
  3. 垂直拖动文本

我在网上搜索了一段时间,没有找到相关内容。

// Create a new instance of Canvas
var canvas = new fabric.Canvas("canvas");
canvas.setDimensions({ width: 1000, height: 1000 }, { backstoreOnly: true });
resizeCanvas();

// Create a new Text instance
var text = new fabric.Text('Texte', {
  fill: '#000',
  fontSize: 100,
  top: 50,
  left: 50,
  borderColor: '#000',
  cornerColor: '#000'
});

// Render the Text on Canvas
canvas.add(text);

// function to resize canvas and its objects
function resizeCanvas() {
  const { clientWidth } = $('.canvas-container')[0];
  const { innerHeight } = window; // viewport height
  const side = Math.min(clientWidth, innerHeight, 1000) + "px";

  canvas.setDimensions({ width: side, height: side }, { cssOnly: true });
}

$(window).resize(resizeCanvas);
body {
  background-color: #000
}

#canvas {
  border: 1px solid #666
}

.upper-canvas {
  max-width: 1000px;
  max-height: 1000px;
}

.container {
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/521/fabric.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

<div class="container mx-auto">
  <div class="col-12">
    <div class="row">
      <div style="width: 100%;" class="canvas-container">
        <canvas id="canvas" class='img-fluid'>
              <p>This is fallback content for users of assistive technologies or of browsers that don't have full support for the Canvas API.</p>
            </canvas>
      </div>
    </div>
  </div>
</div>

两件事:

  1. Fabric.js 自动添加 .canvas-container 包装器。你不 必须提供它。您的代码最终创建了两个 .canvas-container 个元素。那打乱了布局。
  2. 您可以直接使用 window.innerHeight,而不是在父容器上设置宽度 100% 并从中读取它。

假设您希望 canvas 适合视口,您可以试试这个:

// Create a new instance of Canvas
var canvas = new fabric.Canvas('canvas');

let width = window.innerWidth <= 1000 ? window.innerWidth : 1000;
let height = window.innerHeight <= 1000 ? window.innerHeight : 1000;
canvas.setDimensions({width, height});

// do initial resizing after we add the text
setTimeout(resizeCanvas);

// Create a new Text instance
var text = new fabric.Text('Texte', {
  fill: '#000',
  fontSize: 60,
  borderColor: '#000',
  cornerColor: '#000',
});

// Render the Text on Canvas
canvas.add(text);
canvas.centerObject(text);
canvas.setActiveObject(text);

// function to resize canvas and its objects
function resizeCanvas() {
  const newWidth = window.innerWidth <= 1000 ? window.innerWidth : 1000;
  const newHeight = window.innerHeight <= 1000 ? window.innerHeight : 1000;

  if (canvas.width != newWidth || canvas.height != newHeight) {
    const scaleX = newWidth / canvas.width;
    const scaleY = newHeight / canvas.height;
    var objects = canvas.getObjects();
    for (var i in objects) {
      objects[i].scaleX = objects[i].scaleX * scaleX;
      objects[i].scaleY = objects[i].scaleY * scaleY;
      objects[i].left = objects[i].left * scaleX;
      objects[i].top = objects[i].top * scaleY;
      objects[i].setCoords();
    }
    var obj = canvas.backgroundImage;
    if (obj) {
      obj.scaleX = obj.scaleX * scaleX;
      obj.scaleY = obj.scaleY * scaleY;
    }

    canvas.discardActiveObject();
    canvas.setWidth(canvas.getWidth() * scaleX);
    canvas.setHeight(canvas.getHeight() * scaleY);
    canvas.renderAll();
    canvas.calcOffset();
  }
}

$(window).resize(resizeCanvas);
body {
  background-color: #000 !important;
}

#canvas {
  border: 1px solid #666;
}

.container {
  margin: 0;
  background-color: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/521/fabric.min.js"></script>
<script src="https://raw.githubusercontent.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" />
<div class="container mx-auto">
  <div class="col-12">
    <div class="row">
      <canvas id="canvas" class="img-fluid">
      <p
       >This is fallback content for users of assistive technologies or of browsers that
       don't have full support for the Canvas API.</p
      >
     </canvas>
    </div>
  </div>
</div>

如果你想要正方形 canvas 则不要使用 window.innerHeight 或在上面的代码中将 window.innerHeight 替换为 window.innerWidth
如果您希望 canvas 适合整个 window,而不删除父元素,则添加以下 CSS 规则:

.canvas-container {
  position: fixed !important;
  top: 0;
  left: 0;
}

正方形 canvas 对象将按比例缩放。

// Create a new instance of Canvas
var canvas = new fabric.Canvas('canvas');

let width = 1000;
let height = 1000;

canvas.setDimensions({width, height});

// do initial resizing after we add the text
setTimeout(resizeCanvas);

// Create a new Text instance
var text = new fabric.Text('Texte', {
  fill: '#000',
  fontSize: 60,
  borderColor: '#000',
  cornerColor: '#000',
});

// Render the Text on Canvas
canvas.add(text);
canvas.centerObject(text);
canvas.setActiveObject(text);

// function to resize canvas and its objects
function resizeCanvas() {
  const newWidth = window.innerWidth <= 1000 ? window.innerWidth : 1000;

  if (canvas.width != newWidth) {
    const scaleX = newWidth / canvas.width;
    var objects = canvas.getObjects();
    for (var i in objects) {
      objects[i].scaleX = objects[i].scaleX * scaleX;
      objects[i].scaleY = objects[i].scaleY * scaleX;
      objects[i].left = objects[i].left * scaleX;
      objects[i].top = objects[i].top * scaleX;
      objects[i].setCoords();
    }
    var obj = canvas.backgroundImage;
    if (obj) {
      obj.scaleX = obj.scaleX * scaleX;
      obj.scaleY = obj.scaleY * scaleX;
    }

    canvas.discardActiveObject();
    canvas.setWidth(canvas.getWidth() * scaleX);
    canvas.setHeight(canvas.getHeight() * scaleX);
    canvas.renderAll();
    canvas.calcOffset();
  }
}

$(window).resize(resizeCanvas);
body {
  background-color: #000 !important;
}

#canvas {
  border: 1px solid #666;
}

.container {
  margin: 0;
  background-color: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/521/fabric.min.js"></script>
<script src="https://raw.githubusercontent.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" />
<div class="container mx-auto">
  <div class="col-12">
    <div class="row">
      <canvas id="canvas" class="img-fluid">
      <p
       >This is fallback content for users of assistive technologies or of browsers that
       don't have full support for the Canvas API.</p
      >
     </canvas>
    </div>
  </div>
</div>


更大 canvas: 大小不是问题。查看全页。

// Create a new instance of Canvas
var canvas = new fabric.Canvas('canvas');

let width = 2000;
let height = 2000;
canvas.setDimensions({width, height});

// Create a new Text instance
var text = new fabric.Text('Texte', {
  fill: '#000',
  fontSize: 100,
  borderColor: '#000',
  cornerColor: '#000',
});

// Render the Text on Canvas
canvas.add(text);
canvas.setActiveObject(text);
#canvas {
  border: 1px solid #666;
}

.container {
  margin: 0;
  background-color: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/521/fabric.min.js"></script>
<script src="https://raw.githubusercontent.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" />
      <canvas id="canvas" class="img-fluid">
      <p
       >This is fallback content for users of assistive technologies or of browsers that
       don't have full support for the Canvas API.</p
      >
     </canvas>

我删除了调整大小函数中的额外代码并将最大 containerWidth 值限制为 1000,并且我更改了 canvas 包装器 div 的 class 名称以避免与 Fabric.js代码。

@Daniele Ricci:感谢您向我展示调整大小时超过 1000 像素时的 canvas 错误。关于您的解决方案:执行代码段时,canvas 不占用 540px 的全宽(我应该在评论中指定它,但我说的是 boostrap 的响应规则)。它是 200 像素,您无法获得最小 window 尺寸的对象控制按钮。

@the Hutt:谢谢你告诉我 Fabric.js 本身用 canvas-container class 创建了一个 div,我没有注意它。关于您的解决方案:问题是当 canvas 为整页时,文本无法适应正确的大小(尤其是在这个高度上)。

// Create a new instance of Canvas
var canvas = new fabric.Canvas("canvas");
canvas.setDimensions({width: 1000, height: 1000});
resizeCanvas();

// Create a new Text instance
var text = new fabric.Text('Texte', {
    fill: '#000',
    fontSize: 100,
    borderColor: '#000',
    cornerColor: '#000'
});

// Render the Text on Canvas
canvas.add(text);
canvas.centerObject(text);        
canvas.setActiveObject(text);

// function to resize canvas and its objects
function resizeCanvas() {
  const outerCanvasContainer = $('.canvas-wrapper')[0];
  const ratio = canvas.getWidth() / canvas.getHeight();
  const containerWidth   = ($(outerCanvasContainer).width() > 1000 ? 1000 : outerCanvasContainer.clientWidth);
  console.clear(); console.log(containerWidth);
  const containerHeight  = outerCanvasContainer.clientHeight;
  const scale = containerWidth / canvas.getWidth();
  const zoom  = canvas.getZoom() * scale;

  canvas.setDimensions({width: containerWidth, height: containerWidth / ratio});
  canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
}

$(window).resize(resizeCanvas);
body { background-color:#000 }
#canvas{border: 1px solid #666}
.upper-canvas {max-width: 1000px;max-height: 1000px;}
.container{margin:0;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/521/fabric.min.js"></script>
<script src="https://raw.githubusercontent.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<div class="container mx-auto">
   <div class="col-12">           
      <div class="row">
        <div style="width: 100%;" class="canvas-wrapper">
            <canvas id="canvas" class='img-fluid'>
              <p>This is fallback content for users of assistive technologies or of browsers that don't have full support for the Canvas API.</p>
            </canvas>
        </div>
      </div>
    </div>   
</div>