'The best way to resize canvas with its objects
I have a Fabric.js canvas inside in a responsive design with boostrap. To resize the canvas, i use canvas.setDimensions({width:w, height:h}); From the width and the height of the current window clientWidth and clientHeight. Here is my canvas and his container :
<div style="width: 100%;" class="canvas-container">
<canvas id="canvas" class='img-fluid'></canvas>
</div>
When the canvas is resized, for each object in the canvas i change proportionally their size and location. I calculate a factor to apply to each object's top/left/width/height using the original and new canvas sizes. This code runs every time the canvas is resized:
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);
Resizing works fine, all the objects change their dimensions correctly. The problem is that most objects cannot be selected after the canvas is resized. Also, clicking on empty areas of the canvas sometimes selects objects...
// 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/[email protected]/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>
As you can see in the snippet, when page is loaded, you can modify the text like you want but if you click to "Full page", you cannot do it anymore or it become difficult to do it.
Here a video explaining the problem : https://a.uguu.se/pgOYGfJy.webm
I have done a lot of research on the internet and tried a lot of things and i would like to know if anyone knows the best way to make a responsive design while keeping the correct values of the canvas objects in order to avoid this problem happening.
Thank you
Solution 1:[1]
Couple of things:
- Fabric.js adds the
.canvas-containerwrapper automatically. You don't have to provide it. Your code ends up creating two.canvas-containerelements. That disturbs the layout. - You can directly use
window.innerHeight, instead of setting width 100% on parent container and reading it from it.
Assuming you want the canvas to fit the viewport you can try this:
// 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/[email protected]/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>
If you want square canvas then don't use window.innerHeight or replace window.innerHeight with window.innerWidth in above code.
If you want canvas to fit entire window, without removing parent elements, then add following CSS rule:
.canvas-container {
position: fixed !important;
top: 0;
left: 0;
}
Square canvas Objects will scale in proportion.
// 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/[email protected]/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>
Bigger canvas: size is not an issue. See in full page.
// 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/[email protected]/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>
Solution 2:[2]
I'm sorry to give you this news, but probably your effort is superfluous. :(
It seems that the backstoreOnly and cssOnly options of the setDimensions method do all the job for us.
I simplified your code a lot and it seems to work exactly as your one. With "exactly" I mean that both the codes are affected by the same problem.
The problem seems not to be due to your (or my) code, seems to be due to fabric itself; it seems the problem manifests itself when the canvas is higher than the viewport.
If we expand the snippets, we can see both of them work fine until the height of the canvas is lesser than the one of the viewport.
To see the problem in action:
- expand the snippet
- resize it to make the canvas higher than the viewport
- drag the text vertically
I searched for a while on the internet, but I found nothing relevant.
// 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/[email protected]/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>
Solution 3:[3]
I deleted the extra code in the resize function and limit the maximum containerWidth value to 1000 and i change the class name of the canvas wrapper div to avoid confusion with Fabric.js code.
@Daniele Ricci : thank you for showing me that the canvas bugs when resizing exceeds 1000px. About your solution : the canvas does not take the full width (I should have specified it in my comment but I was talking about the responsive rule of boostrap) of 540px when the snippet is executed. It's 200px, you cant get the object control buttons in the smallest window size.
@the Hutt : thank you for showing me that Fabric.js itself creates a div with the canvas-container class, i hadn't paid attention to it. About your solution: The problem is that the text does not adapt to the right size (especially on the height of this one) when the canvas is in full page.
// 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/[email protected]/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>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | SKJ |
