fabricjs / fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser
http://fabricjs.com
Other
29.03k stars 3.51k forks source link

Support percentage width / height values on the canvas #1270

Open Siyfion opened 10 years ago

Siyfion commented 10 years ago

Would it not be nice to also be able to set the canvas height or width to a percentage value? I know this means that internally an event would need to take place on window resize to update the width and height, but is it not worth it?

canvas.setWidth('100%');
canvas.setHeight('80%');

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

kangax commented 10 years ago

I'll mark it as possible feature for now. We'll see if anyone else needs this.

cstigler commented 10 years ago

+1 on this

manfredjb commented 10 years ago

+1 in order to make fabricjs a "responsive app" more easy.

matte00 commented 10 years ago

+1 :+1:

ianserlin commented 10 years ago

+1 on laying the groundwork for an easily responsive fabricjs backed canvas

ghost commented 9 years ago

+1

TimHarker commented 9 years ago

+1

alanmastro commented 9 years ago

+1

handersen commented 9 years ago

+1

kangax commented 9 years ago

Alrighty, marking this as feature.

engrsandeep commented 9 years ago

+1 on this

engrsandeep commented 9 years ago

Is there any deadline of this feature? eagerly waiting for this feature.... Thank you so much for consideration...

andycochran commented 9 years ago

+1

andycochran commented 9 years ago

Can't you simply style the canvas to make it responsive?

#canvas {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  width: 100% !important;
  height: 100% !important;
}
.canvas-container {
  position: relative;
  width: 100% !important;
  height: 0 !important;
  padding-bottom: 100%; // the canvas height as a percentage of its width
}
.upper-canvas {
  width: 100% !important;
  height: 100% !important;
}

Of course, this doesn't change the width of the canvas, it scales it. Thoughts?

atomicjeep commented 9 years ago

The objects would need re-drawn & re-positioned as would the invisible interaction layer, plus we need to handle the proportions of the canvas and it's objects something like this (not tried) http://stackoverflow.com/questions/19937397/show-canvas-in-fullscreen-in-fabric-js/20008922#20008922

andycochran commented 9 years ago

Just realized my solution wouldn't work on a StaticCanvas.

Bnaya commented 8 years ago

Take a look on #1505

keremdemirer commented 7 years ago

+1

naimulhaider commented 7 years ago

+1

malrefaay commented 7 years ago

+1

eduardowallen commented 7 years ago

+1

mkupiniak commented 7 years ago

+1

vikaskallidantheyil commented 7 years ago

+1

asturur commented 7 years ago

i see we have lot of +1. I'm going in the process to make the upper canvas retina enabled. Once i see how the event position change with a css transform, and once i found a nice way to find the css transform for ie10+, then i think we can do this.

Bahaa-Addin commented 6 years ago

+1

matte00 commented 6 years ago

+1

pattisano commented 6 years ago

+1

asturur commented 6 years ago

looks like this is very +1d.

Please ping me again here if in a couple of days i do not draft a proposal for how it could work.

IanYates commented 6 years ago

In our app we use a Knockout to get the fabric canvas onto the screen. I've extended our KO binding handler so that it can manage the fabric instance and support it being dynamically resized. Essentially when it sees that its area has changed a new fabric instance is created. The old instance has its JSON data saved out, along with information about the viewport transform. That's then fed into the new instance which is displayed then displayed on screen. That has worked pretty well but I'm sure there are some holes. Supporting some sort of CSS transform would be much more straightforward but browser quirks with how touch, mouse, pen, etc events get detected in the scaled DOM element may not be fun.

folkevil commented 6 years ago

@IanYates can you provide more information ?

lpcman commented 6 years ago

+1, How are things going on lastest version ?

lpcman commented 6 years ago

+1, How are things going on lastest version ?

a solution in version 2. 4.2-b http://fabricjs.com/docs/fabric.js.html#line7130 image

it works in my code, set width and height 100% : image

kingschnulli commented 5 years ago

+1 - using setDimensions with cssOnly works but the controls will not scale well

asturur commented 5 years ago

The reason behind not supporting this feature is that we need to poll the dom to know how much the container is big and adapt the canvas accordingly. Once the canvas is adapted you may want to zoom the content or not to zoom it, but those behavior are not written in any kind of neutral logic and are heavily dependent on the scope of your project.

We could provide a method called adaptToContainer in order to do so, but this would cover only the 100% case and not any percentage.

We can make a demo with the new resize event for the browsers where this is implemented and with the window.resize that is usually where those things happen.

What do you think?

asturur commented 5 years ago

The original request canvas.setWidth(100%) can still work, the problem is that this 100% is one shot and won't persist with following resizes.

kingschnulli commented 5 years ago

I'm using a combination of setDimensions and setZoom now, this will scale the controls and everything just as needed. As you said, this is very userland specific, I will provide a jsfiddle to demonstrate the behaviour. Maybe the method adaptToContainer could do this when called with a specific width/height.

arnevdv commented 5 years ago

I implemented this like this:

window.onresize = (event) => {
    fitResponsiveCanvas();
 };

function fitResponsiveCanvas() {
   // canvas dimensions
   let canvasSize = {
      width: 1200,
      height: 700
   };
   // canvas container dimensions
   let containerSize = {
      width: document.getElementById('canvas-container').offsetWidth,
      height: document.getElementById('canvas-container').offsetHeight
   };
   let scaleRatio = Math.min(containerSize.width / canvasSize.width, containerSize.height / canvasSize.height);
   canvas.setWidth(containerSize.width);
   canvas.setHeight(containerSize.height);
   //set canvas zoom aspect
   canvas.setZoom(scaleRatio)
}
<div id="canvas-container">
   <canvas id="canvas"></canvas>
</div>

The only thing you have to do is add some css to your container:

#canvas-container {
   height: 80vh;
   width: 100%;
}
DaviesGit commented 5 years ago

+1

ymebrugts commented 3 years ago

+1 - This should be a possibility in 2021. Everything on the web is responsive now, including complex applications.

asturur commented 3 years ago

is already a possibility, just that you have to handle it. Put a resize event on your canvas container and change the size of it. Is a couple of lines of cose

ymebrugts commented 3 years ago

I've not found this in the official documentation. If you're referring to the code above, looks good and I'd implement it in a heartbeat if it was official. Making a complex drawing library responsive in this way makes me wonder about why it's not in the library and the documentation to begin with. As such I assumed it would create unforseen problems and my application has to be stable above responsive. For example, I'm worried 'setZoom' as a means to get it to be responsive will mess up the .toJson since the zoom will be reverted to default on reload and load of json.

From your response I gather FabricJS sees this as the accepted solution to this problem?

asturur commented 3 years ago

Yes is an accepted solution for me. I use it too. FabricJS does not handle the canvas as an application, or at least tries to not do it where different frameworks or generic code solutions are available.

The canvas is in pixels and so is not really responsive per se.

There are a couple of improvements you can add to the solution above ( that i did not remember was here ).

window.onresize = (event) => {
    fitResponsiveCanvas();
 };

function fitResponsiveCanvas() {
   // canvas dimensions
   let canvasSize = {
      width: 1200,
      height: 700
   };
   // canvas container dimensions
   let containerSize = {
      width: document.getElementById('canvas-container').offsetWidth,
      height: document.getElementById('canvas-container').offsetHeight
   };
   canvas.setDimensions(containerSize);
   // how you want to handle your zoom is really application dependant.
   let scaleRatio = Math.min(containerSize.width / canvasSize.width, containerSize.height / canvasSize.height);
   canvas.setZoom(scaleRatio)
}
ymebrugts commented 3 years ago

Thanks! I'll implement it this way.

dennisideaonce commented 3 years ago

This is still a pain in 2021. I have a drawing canvas inside the main canvas. setDimension and setzoom work well on the same device. The drawing rect. has proper dimensions in all the devices.

But the inner objects don't scale when open different resolutions.

asturur commented 3 years ago

It mostly depends how do you want to handle your zoom level on the canvas and apply it on bigger/smaller canvases. The fact that you want the inner object to scale, it means that you are implementing a kind of fit to page behaviour that does not exist in fabricJS since there is no a page. If there is page is a construct of the application.

dennisideaonce commented 3 years ago

I am stuck in a problem, don't know if this is related to the current thread.

I have created a rectangle inside the canvas like this: Objects and the drawing board at the right position

The grey part is the canvas there is a small rectangle inside. This is the code I have written:

// THIS METHOD WHEN THE COMPONENT IS INITIALIZED
// THAT IS VERY FIRST TIME
// htmlCanvas = Canvas from HTML file
initCanvas(htmlCanvas) {
    this.mockupDrawableAreaCanvasSize = size;
    this.mainCanvasDivRef = htmlCanvas;
    fabric.textureSize = 4096;
    fabric.charWidthsCache = {};
    fabric.Object.prototype.borderScaleFactor = 2;
    fabric.Object.prototype.objectCaching = false;
    fabric.Object.prototype.noScaleCache = true;
    fabric.Object.prototype.lockScalingFlip = true;
    fabric.Object.prototype.hasRotatingPoint = true;
    fabric.Object.prototype.transparentCorners = false;
    fabric.Object.prototype.cornerColor = "rgb(255,255,255)";
    fabric.Object.prototype.cornerSize = 8;
    fabric.Object.prototype.cornerStrokeColor = "#48B774";
    fabric.Object.prototype.borderColor = "#48B774";
    fabric.Object.prototype.fill = "#FFFFFF";
    fabric.Object.prototype.cornerStyle = "rect";
    fabric.Object.prototype.borderOpacityWhenMoving = .5;
    fabric.Object.prototype.snapAngle = 90;
    fabric.Object.prototype.snapThreshold = 5;
    fabric.Object.prototype.charWidthsCache = {};

    this.canvas = new fabric.Canvas(htmlCanvas.nativeElement, this.defaultOption);

    this.canvas.backgroundColor = '#e8e8e8';
    this.canvas.uniScaleTransform = true;

    this.canvas.requestRenderAll()

    // -------
    const {
        width,
        height
    } = this.getDimensions();
    this.drawingBoard = new fabric.Rect({
        excludeFromExport: false,
        hasControls: false,
        height,
        selectable: false,
        borderColor: "transparent",
        strokeWidth: 0,
        stroke: 'transparent',
        fill: '#ffffff',
        width,
        preserveObjectStacking: false,
        absolutePositioned: true,
        clip_id: 'io_main_canvas',
        originX: 'center',
        originY: 'center',
        lockUniScaling: true,
    });
}
// Here mockupDrawableAreaCanvasSize = {width: 1080, height: 1080}
getDimensions() {
    let containerWidth: any = parseInt(this.mockupDrawableAreaCanvasSize.width, 10);
    let containerHeight: any = parseInt(this.mockupDrawableAreaCanvasSize.height, 10);
    const footer: any = document.querySelector('.footer-toolbar');
    const container = document.querySelector('.upper-canvas');
    const lowerCanvasEl = container && window.getComputedStyle(container, null);
    let canvasHeight = parseInt(lowerCanvasEl.height, 10) - 1.5 * parseInt(footer.offsetHeight, 10);
    const canvasWidth = parseInt(lowerCanvasEl.width, 10);

    // Complete fit
    if (containerWidth <= canvasWidth && containerHeight <= canvasHeight) {
        return {
            width: containerWidth,
            height: containerHeight
        }
    }
    if (canvasWidth < containerWidth) {
        containerWidth = canvasWidth - 80;
        canvasHeight = (containerWidth / (containerWidth / containerHeight)) - 6 * parseInt(footer.offsetHeight, 10)
    }
    let width = (containerWidth / containerHeight) * canvasHeight;

    return {
        width,
        height: canvasHeight
    }
}

This code works well on all the screens meaning the drawingboard rect has the right position + size on every resolution. The problem is with the content(objects) inside the this.drawingBoard.

I add the object to the canvas and centralize the object.

The objects on the smaller screens go very right corner, as shown in the image, as shown in the below snap

Screenshot 2021-06-02 at 7 52 11 PM

I am into this problem for the last 3 days, tried n number of solutions like- Grouping, centering the group, and Ungrouping also didn't work perfectly Second I tried repositioning the object by reducing some pixels that didn't even help.

finding no help on the internet. Can you please lead me to something, I know I have missed something but finding myself clueless about that?

I am expecting the objects to be at the same place where I added them initially.

@asturur

dennisideaonce commented 3 years ago

This solution worked well for me,

Screenshot 2021-06-02 at 11 29 50 PM
 const ratioX = this.drawingBoard.width / originalWidth;
  const ratioY = this.drawingBoard.height / originalHeight;
  o.left *= ratioX;
  o.top *= ratioY;
  o.scaleX *= ratioX;
  o.scaleY *= ratioY;
  o.setCoords();

But still, there are some pixels on the left!

asturur commented 3 years ago

the best way to do in my opinion is to group everything, calculate % position of the group, scale the group by ratio, apply the same % position and ungroup.

Is the most generic but also the one that works ok most of the time.

dennisideaonce commented 3 years ago

Hi @asturur calculate % position of the group can you please guide me on this little bit more? any example or something

dennisideaonce commented 3 years ago
 group.left = this.drawingBoard.left;
            group.top = this.drawingBoard.top;
            group.width = this.drawingBoard.getScaledWidth();
            group.height = this.drawingBoard.getScaledHeight();
            this.canvas.add(group);

I tried this and it seems to be working. But on lower sized devices the canvas objects goes down

https://prnt.sc/14entwy