I'm fitting a game container to the device screen with an optimal scale ratio:
class Main {
// ...
private handleResize() {
let scale = this.getFittingScale();
let container = document.getElementById('gameContainer');
container!.style.transform = `scale(${scale})`;
container!.style.left = `${window.innerWidth / 2 - baseResolution.width / 2}px`;
container!.style.top = `${window.innerHeight / 2 - baseResolution.height / 2}px`;
}
private getFittingScale(): number {
let ratio = baseResolution.width / baseResolution.height;
let windowRatio = window.innerWidth / window.innerHeight;
// choose optimal scale ratio
if (windowRatio > ratio) {
return window.innerHeight / baseResolution.height;
} else return window.innerWidth / baseResolution.width;
}
// ...
}
The issue I've with this is that text fonts scale together inside the <div/>. Is there a way to prevent this text font scaling except for a few specific elements?
Related
I want that the scale of my 3D Model appears the same on every screen. It should depend on the screen-width, screen-height and the resolutuion. On mobile screens it should appear a bit smaller.
Here is the Code i currently have:
//add moon model
let moonObject = new URL("/assets/Moon.glb", import.meta.url).href;
let moon;
const glftLoader = new GLTFLoader();
glftLoader.load(moonObject, (gltfScene) => {
moon = gltfScene;
let scale = Math.min(window.innerWidth, window.innerHeight) / 6000 - 0.13;
if (window.innerWidth < 600) {
scale = 0.018
}
moon.scene.scale.set(scale, scale, scale);
moon.scene.position.set(1, 1, 0);
moon.scene.rotation.y = 180;
scene.scene.add(gltfScene.scene);
});
This is what it tried it looks good on some screens but on others it appears too small and on some too big:
let scale = Math.min(window.innerWidth, window.innerHeight) / 6000 - 0.13;
I have a canvas with images that are placed with a rect trough which the image is shown. Outside the rect the background is shown instead of the image (so it's cropping the image). I'm trying to prevent the image not covering the cropping rect, the image should always be bigger and totally covering the rect. I got this working on straight images/rects but I can't get it to work when a rotation is involved.
I've used the code in this question to get it working on straight images:
Prevent Fabric js Objects from scaling out of the canvas boundary
canvas.on('object:moving', (event: any) => {
// this eventlistener makes sure that a moving image won't leave it's placeholder area uncovered
//
const activeObject = event.target;
// the cropping Rect
const containerRect = retrieveObjectFromCanvas(this.state.canvas, `${this.selectedImage.value.id}-container`);
// apply new coords of current move action
activeObject.setCoords();
const newCoords = activeObject.getCoords();
const containerCoords = containerRect.getCoords();
// left
if (newCoords[0].x >= containerCoords[0].x) {
activeObject.left = (containerRect.left - (containerRect.width / 2) + ((activeObject.width * activeObject.scaleX) / 2));
}
// top
if (newCoords[0].y >= containerCoords[0].y) {
activeObject.top = (containerRect.top - (containerRect.height / 2) + ((activeObject.height * activeObject.scaleY) / 2));
}
// right
if (newCoords[2].x <= containerCoords[2].x) {
activeObject.left = (containerRect.left + (containerRect.width / 2) - ((activeObject.width * activeObject.scaleX) / 2));
}
// bottom
if (newCoords[2].y <= containerCoords[2].y) {
activeObject.top = (containerRect.top + (containerRect.height / 2) - ((activeObject.height * activeObject.scaleY) / 2));
}
activeObject.setCoords();
});
canvas.on('object:scaling', (event:any) => {
// this eventlistener makes sure that a scaling image won't leave it's placeholder area uncovered
//
const activeObject = event.target;
activeObject.set({ lockScalingFlip: true });
const boundingRect = activeObject.getBoundingRect();
// the cropping Rect
const containerRect = retrieveObjectFromCanvas(this.state.canvas, `${this.selectedImage.value.id}-container`);
// apply new coords of current move action
activeObject.setCoords();
const newCoords = activeObject.getCoords();
const containerCoords = containerRect.getCoords();
// left
let corners = ['tl', 'ml', 'bl'];
if ((includes(corners, event.transform.corner)) && newCoords[0].x >= containerCoords[0].x) {
const newScale = (boundingRect.width) / activeObject.width;
activeObject.scaleX = newScale;
activeObject.left = (containerRect.left - (containerRect.width / 2) + ((activeObject.width * activeObject.scaleX) / 2));
}
// top
corners = ['tl', 'mt', 'tr'];
if (includes(corners, event.transform.corner) && newCoords[0].y >= containerCoords[0].y) {
const newScale = (boundingRect.height) / activeObject.height;
activeObject.scaleY = newScale;
activeObject.top = (containerRect.top - (containerRect.height / 2) + ((activeObject.height * activeObject.scaleY) / 2));
}
// right
corners = ['tr', 'mr', 'br'];
if (includes(corners, event.transform.corner) && newCoords[2].x <= containerCoords[2].x) {
const newScale = (boundingRect.width) / activeObject.width;
activeObject.scaleX = newScale;
activeObject.left = (containerRect.left + (containerRect.width / 2) - ((activeObject.width * activeObject.scaleX) / 2));
}
// bottom
corners = ['bl', 'mb', 'br'];
if (includes(corners, event.transform.corner) && newCoords[2].y <= containerCoords[2].y) {
const newScale = (boundingRect.height) / activeObject.height;
activeObject.scaleY = newScale;
activeObject.top = (containerRect.top + (containerRect.height / 2) - ((activeObject.height * activeObject.scaleY) / 2));
}
activeObject.setCoords();
});
}
There are no error messages, but the image will not stop at the borders of the rect, but scale and move unpredictable. I'm not sure if it's related but the Coords calculate from the center of the object/rect to the top-left of the canvas.
Edit: A clarification. Checking if the rect is contained within the picture can be done with .isContainedWithinObject(). The hard part is calculating the values for the image so it's just outside the boundary. In the code above I calculate the top, left, and scale but those calculations only work on images with no skew or rotation.
I have a code responsible for cropping image and saving its cropped areas in a list of divs. Each of these divs represents each of the cropped images. But the problem is - I dont want them to be so huge, I want them to have fixed height and width, e.g. max 100x100px.
Codesandbox with working image cropping: https://codesandbox.io/s/fast-frost-0b5tt
Code revelant to cropping logic:
const width = pos.w + "px";
const height = pos.h + "px";
const marginLeft = - pos.x + "px";
const marginTop = - pos.y + "px";
return (
<div
key={i}
style={{
width: width,
height: height,
backgroundSize: "400px 300px",
backgroundPosition: `top ${marginTop} left ${marginLeft}`,
backgroundImage: "url('https://boygeniusreport.files.wordpress.com/2016/11/puppy-dog.jpg?quality=98&strip=all&w=400')"
}}
/>
);
You will see that image cropping works great, however newly created images have dynamic height and width.
Question: How to make these newly created divs to have fixed width and height, but without destroying it? Thanks!
Edit: New link added (the old one was missing styles)
The goal is to make the children (cropped images) to have static height and width, but keep the background image in correct position. But seems like im too stopid to do it by myself
The following change within your render function will create a 100px square centered on the selection's center point, or as close as possible keeping within the image limits (I'm not sure how to reference the original image's width & height from here).
...
const previews = crops.map(({ pos }, i) => {
const width = 100 + "px";
const height = 100 + "px";
let margx = (pos.w / 2) - 50 + pos.x;
let margy = (pos.h / 2) - 50 + pos.y;
if(margx < 0) margx = 0;
if(margy < 0) margy = 0;
// this needs origional image width & height (400,300) to get max values
const maxx = 400-100;
const maxy = 300-100;
if(margx > maxx) margx = maxx;
if(margy > maxy) margy = maxy;
const marginLeft = - margx + "px";
const marginTop = - margy + "px";
return (
<div
...
If you fix both height and width, the previews will look distorted. So I would recommend
only fixing the height.
const fixedHeight = 100;
const zoom = fixedHeight / pos.h;
const backgroundWidth = 400 * zoom;
const backgroundHeight = 300 * zoom;
const width = pos.w * zoom;
const height = fixedHeight;
const marginLeft = -pos.x * zoom;
const marginTop = -pos.y * zoom;
See results in this codesandbox demo.
I can propose something in between solutions proposed by #MunimMunna and #ArleighHix improving both solutions.
See result
// setup base image size
const imageBaseWidth = 400;
const imageBaseHeight = 300;
// choose thumbnail size and aspect ratio
const thumbHeight = 100;
const thumbWidth = 200;
// we check which axis needs to be filled to border
const zoomX = thumbWidth / pos.w;
const zoomY = thumbHeight / pos.h;
// you can improve it further by defining max zoom in level so that thumbnails don't show ugly pixels
// just use additional zoom = Math.min(zoom, maxZoom) and some more logic for handling min max margin offset so it wont go outside image bounds
const zoom = Math.max(zoomX, zoomY);
// scaling base image to best fit available space
const backgroundWidth = imageBaseWidth * zoom;
const backgroundHeight = imageBaseHeight * zoom;
// calculate offset to top left corner of biggest rect in selected region that keeps target aspect ratio
const marginLeft = thumbWidth / 2 - (pos.w / 2 + pos.x) * zoom;
const marginTop = thumbHeight / 2 - (pos.h / 2 + pos.y) * zoom;
return (
<div
className="preview"
key={i}
style={{
width: thumbWidth + "px",
height: thumbHeight + "px",
backgroundSize: `${backgroundWidth}px ${backgroundHeight}px`,
backgroundPosition: `top ${marginTop}px left ${marginLeft}px`,
backgroundImage: "url('https://boygeniusreport.files.wordpress.com/2016/11/puppy-dog.jpg?quality=98&strip=all&w=400')"
}}
/>
);
It chooses bigest posible region inside selection that keeps aspect ratio of targeted thumbnails and zooms in on it if required. You can setup target thumbnails size with fixedHeight, fixedWidth
I'd try a rewrite without the jcrop library, to have better access to all the props, size, positioning etc.
https://stackblitz.com/edit/react-7ubxaa
I am working with fabricjs for an application. On mobile I need to scale the whole canvas down so it can fit the screen.
The issue is that on the canvas there are 10 objects in total, between images, texts and fabric js background objects.
I tried scaling the whole canvas using CSS transform and it seems to work, but the whole area gets smaller, as in this picture
The original canvas is the entire picture, but when I apply the transform, the canvas becomes the red area, and when moving objects with fabricjs, they get clipped when outside the red area.
Is there any way to fix this in CSS ?
edit
Basically I need to scale everything inside the canvas, but the canvas itself needs to always be 100% in height and with of the current window.
Or is there any way to resize the whole canvas with all the objects ? I also tried fabric js setZoom() method, but it gets even more messed up.
Thank you
The best way is find a scale ratio that fit your screen (is unclear how you plan to fit a square canvas on a long screen).
Then leave the css untrasformed. ratio is the scale ratio that fit the canvas in screen;
resize the canvas to actual screen dimensions:
canvas.setDimensions({ width: originalWidth * ratio, height: originalHeight * ratio });
canvas.setZoom(ratio)
This should give you a smaller canvas with everything in place.
Possible Duplicate: Scale fabric.js canvas objects
It seems Fabric.js doesn't support this natively... at least, I haven't found the options to do it.
Until Fabric.js provides this, the best way may be to iterate through the objects and scale them manually to match the new screen size based on the ratio of the previous screen size. For example...
const canvas = new fabric.Canvas({});
let prevScreenWidth = window.innerWidth;
let prevScreenHeight = window.innerHeight;
window.addEventListener('resize', handleResize());
/*********************************************************
* Get the new screen size ratio in relation to the previous screen size
* to determine the scale factor.
*********************************************************/
function handleResize() {
// Error Handling: Make sure the scale ratio denominator is defined.
prevScreenWidth = prevScreenWidth || window.innerWidth;
prevScreenHeight = prevScreenHeight || window.innerHeight;
// Get the current/new screen width and height (scale ratio numerator).
const currScreenWidth = window.innerWidth;
const currScreenHeight = window.innerHeight;
// Determine width and height scale ratios
const widthRatio = currScreenWidth / prevScreenWidth;
const heightRatio = currScreenHeight / prevScreenHidth;
// Get the average of the width and height scale ratios
const scaleFactor = (widthRatio + heightRatio) / 2;
// Scale the objects with the screen size adjustment ratio average as the scale factor
scaleObjects(scaleFactor);
// Record the current screen sizes to be used for the next resize.
prevScreenWidth = window.innerWidth;
prevScreenHeight = window.innerHeight;
}
/*********************************************************
* Iterate through each object and apply the same scale factor to each one.
*********************************************************/
function scaleObjects(factor) {
// Set canvas dimensions (this could be skipped if canvas resizes based on CSS).
canvas.setDimensions({
width: canvas.getWidth() * factor,
height: canvas.getHeight() * factor
});
if (canvas.backgroundImage) {
// Need to scale background images as well
const bi = canvas.backgroundImage;
bi.width = bi.width * factor; bi.height = bi.height * factor;
}
const objects = canvas.getObjects();
for (let i in objects) {
const scaleX = objects[i].scaleX;
const scaleY = objects[i].scaleY;
const left = objects[i].left;
const top = objects[i].top;
const tempScaleX = scaleX * factor;
const tempScaleY = scaleY * factor;
const tempLeft = left * factor;
const tempTop = top * factor;
objects[i].scaleX = tempScaleX;
objects[i].scaleY = tempScaleY;
objects[i].left = tempLeft;
objects[i].top = tempTop;
objects[i].setCoords();
}
canvas.renderAll();
canvas.calcOffset();
}
See this previous answer for more information on the scaleObjects() method... Scale fabric.js canvas objects
I have a class named Engine.Renderer. It just creates a new canvas and give me the possibility to easily update and render the active canvas' scene. When a new canvas is created, I apply those settings to its context:
this.context.imageSmoothingEnabled = false;
this.context.mozImageSmoothingEnabled = false;
this.context.webkitImageSmoothingEnabled = false;
In CSS, I've added those lines:
main canvas {
position: absolute;
top: 0;
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: optimize-contrast;
-ms-interpolation-mode: nearest-neighbor
}
I have also write a function that adjusts the canvas to the window:
[...]
resize: function () {
var width = window.innerWidth;
var height = width / 16 * 9;
if ( height > window.innerHeight ) {
height = window.innerHeight;
width = height * 16 / 9;
}
if ( width > window.innerWidth ) {
width = window.innerWidth;
height = width / 16 * 9;
}
width = Number( width ).toFixed();
height = Number( height ).toFixed();
this.canvas.style.width = width + "px";
this.canvas.style.height = height + "px";
this.container.style.width = width + "px";
this.container.style.height = height + "px";
this.container.style.left = ( ( window.innerWidth - width ) / 2 ) + "px";
// the new scale
this.scale = ( width / this.canvas.width ).toFixed( 2 );
}
[...]
Now, I have a class named Character. This class is able to create and render a new character on the given canvas. The render part looks like this:
context.drawImage(
this.outfit,
this.sprite * this.dimension.width,
this.direction * this.dimension.height,
this.dimension.width,
this.dimension.height,
this.position.x,
this.position.y,
// set the character sizes to normal size * scale
this.dimension.width * this.renderer.scale,
this.dimension.height * this.renderer.scale
);
I have two problems with it:
the game performance is even worse than before (~9 FPS when rendering a single character on ~1400x800px canvas);
character' image is not so much blurred as before, but I still can see a little blur;
How can I solve those problems?
Try using integer values for positions and sizes:
context.drawImage(
this.outfit,
this.sprite * this.dimension.width,
this.direction * this.dimension.height,
this.dimension.width,
this.dimension.height,
this.position.x|0, // make these integer
this.position.y|0,
// set the character sizes to normal size * scale
(this.dimension.width * this.renderer.scale)|0,
(this.dimension.height * this.renderer.scale)|0
);
Also, setting canvas size with CSS/style will affect interpolation. From my own tests the CSS settings for interpolation does not seem to affect canvas content any longer.
It's better, if you need a fixed small size scaled up, to set the canvas size properly and instead use scale transform (or scaled values) to draw the content:
this.canvas.width = width;
this.canvas.height = height;
Update: Based on the comments -
When changing the size of the canvas element the state is reset as well meaning the image smoothing settings need to be reapplied.
When image smoothing is disabled the browser will use nearest-neighbor which means best result is obtained when scaling 2^n (2x, 4x, 8x or 0.5x, 0.25x etc.) or otherwise "clunkyness" may show.
A modified fiddle here.