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/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>
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
Couple of things:
.canvas-container
wrapper automatically. You don't
have to provide it. Your code ends up creating two
.canvas-container
elements. That disturbs the layout.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/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>
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/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>
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/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>