fabric.js resize canvas to fit screen

X4V1 picture X4V1 · Jun 16, 2015 · Viewed 13.9k times · Source

I'm currently developping an application on mobile device (android and iPhone) with ionic and cordova. I would like to edit a picture. I use fabric.js library to do that. Fabric.js converts an image and other items into canvas. Then I add some image on the canvas (stickers) and export it as an image(dataURL). So the size of the canvas is really important because it is a quality factor (export an image based on small canvas will result in poor quality). The canvas size is about 607*1080 pixel (depending on the device that took the photo). I would like to fit it in the screen without losing quality (like explained below I can't resize the canvas without losing quality).

I tried 3 ideas but no one has worked.

  1. Resize canvas : result in quality lost (image exported will have canvas size)
  2. Adapt viewport : cordova doesn't allow to change the viewport on mobile device. There is a workaround but it will break my design (very small navbar, smaller menu, ...)
  3. Use CSS3 zoom properties : It works like a charm on desktop but there is an event mapping problem on mobile device (I didn't try on ios but the problem happend on android Webview). Event mapping is not zoomed. The canvas is zoomed out but it is impossible to interact with canvas elements. If a canvas element (sticker) is located at x:100 y:100, when I set zoom property to 0.5, the element will be location at (50,50) and no more at (100,100). Is there any way to correct the event mapping depending on zoom property of the android webview?

I don't know if there are other ways to do that but I'm stucked with this issue for 3 days and I've spend a lot of time on forums, fabricjs doc and google and I don't find any working solution or something else that can help me.

EDIT: There are 2 working solution below. Check my answer to see how to do that with css.

Answer

SilentTremor picture SilentTremor · Jun 16, 2015

1. Use css to set your right resolution for canvas:

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d");

canvas.width = 800;
canvas.height = 600;

css:

/* media query 1 */
    #canvas{
       width: 400px;
       height: 250px; 
    }

/* media query 2 */
#canvas{
       width: 200px;
       height: 120px; 
    }

pros:

All the resolution is given by canvas width and height (800/600)

cons:

Fit well for square canvases aspect ration (1:1), problems with output for weird aspect ratios where you need to compute your css height based,

original height / original width * new width = new height;

this is out of my league in css...

Read some time ago on fabric.js git issues section where, technique (css resize) isn't recommended due to slowness and incorrect objects transactions, this may be changed I know I tested and objects were incorrect re-sized (random colored pixels, transactions shadows remain in canvas , in other words a mess)

2. Re-size your canvas to exact dimension (resolution) when you need to submit, and then get the image generated

Just re-size your canvas at window re-size (responsive canvas) and when you need to export to exact dimension you need to re size your canvas to exact pixel resolution, or create another canvas hidden.

this example is for 1:1 ration canvas (you need to apply aspect ratio to canvas and objects inside):

$(window).resize(function (){
 if (canvas.width != $(".container").width()) {
            var scaleMultiplier = $(".container").width() / canvas.width;
            var objects = canvas.getObjects();
            for (var i in objects) {
                objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
                objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
                objects[i].left = objects[i].left * scaleMultiplier;
                objects[i].top = objects[i].top * scaleMultiplier;
                objects[i].setCoords();
            }

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

At submit export canvas data re-size canvas to desired resolution, voila:

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

                canvas.setWidth(canvas.getWidth() * scaleMultiplier);
                canvas.setHeight(canvas.getHeight() * scaleMultiplier);
                canvas.renderAll();
                canvas.calcOffset();
            }
     return canvas.toDataURL();
   }
   );

It's not a big deal with the rations different then 1:1 you need to compute scale multiplayer for height as well, easy to understand ratio: http://andrew.hedges.name/experiments/aspect_ratio/