I have a div whose initial width and height are set by the user. When a user zooms in the browser(ctrl+ or ctrl-) the initial width and height of that div changes, say a user zooms in 175%, the aspect ratio of that div stays the same because the width and height is adjusted. Is there a way to replicate this by dynamically setting transform: scale(x,y). I have tried several thing but can't seem to find a solid solution.
Solution seems pretty straight-forward. Store scale somewhere, then increase/decrease it on button's click:
(function () {
function zoom(element, scale) {
element.style.webkitTransform =
element.style.transform = 'scale(' + scale + ')';
return scale;
}
var zoomable = document.querySelector('.zoomable');
var scale = 1;
document.querySelector('#zoomin')
.addEventListener('click', function () {
scale = zoom(zoomable, scale * 2);
});
document.querySelector('#zoomout')
.addEventListener('click', function () {
scale = zoom(zoomable, scale * 0.5);
});
})();
Demo: http://jsbin.com/aJagofU/1/edit?js,output
Related
I am working on a project where I am using CSS transform to scale up the whole body of a page. After scaling up a bit, content from corners start becoming un-viewable because they are outside visible ranges. Is there a way for the content to still be viewable by scrolling vertically or horizontally using transform scaling?
I am currently using Javascript to scale up the body like so
document.body.style.transform = 'scale(1.5)';
However, this cuts off some content from pages. I need it to work as I continue scaling up from 1.0.
Try adjusting the transform-origin:
document.body.style.transformOrigin = 'top left';
document.body.style.transform = 'scale(' + scaleFactor + ')';
You may also need to adjust the width and height of the body to match the scaling.
var scaleFactor = 1.5;
document.body.style.transformOrigin = 'top left';
document.body.style.transform = 'scale(' + scaleFactor + ')';
document.body.style.width = 100 * scaleFactor + "%";
document.body.style.height = 100 * scaleFactor + "%";
A note concerning transforms. Transforms are imaginary and don't alter physical dimensions including x, y, width and height. So you'll have to manage these physical dimensions manually to match your "transform'd" dimensions in order to keep the scroll bars happy.
I'm scaling a div in order to be visible without scrolling. So I have:
var sliderOffset = $('.field-slideshow-wrapper').offset().top,
windowHeight = $(window).height(),
sliderAllowed = (windowHeight - sliderOffset),
sliderImage = $('.field-slideshow-slide img').height(),
sliderOriginal = (sliderImage + 150),
scale = (sliderAllowed / sliderOriginal);
$('.field-slideshow-wrapper').css({ transform: 'scale(' + scale + ')'});
Now that div is not on the same position from top as before and I need to determine new offset from top, after css({ transform: 'scale(' + scale + ')' is applied, so I can calculate some margin to move this div at the top.
How to determine new offset().top of the element?
There are two ways you could do this and the choice is yours. The transform: scale() shrinks the element toward its center, so the top of the element moves down. The scaled element will still return the non-scaled element's offset().top, so that won't work.
One option is to just make sure the newly scaled element will stick to the top of the old element's space. Just do this:
$('.field-slideshow-wrapper').css({
transform: 'scale(' + scale + ') translateY(-50%)'
});
This makes the element move up by 50% of its new height, thus sticking it to the top of its old dimensions.
The other option is to do some simple calculation. Get the old element's height, then get the number of pixels the scale() method has moved it "down". You can find the number by some calculations (see below), and that's the number you can add to the old offset().top to get the new one:
var sliderOffset = $('.field-slideshow-wrapper').offset().top,
windowHeight = $(window).height(),
elHeight = $('.field-slideshow-wrapper').innerHeight(),
sliderAllowed = (windowHeight - sliderOffset),
sliderImage = $('.field-slideshow-slide img').height(),
sliderOriginal = (sliderImage + 150),
scale = (sliderAllowed / sliderOriginal);
var addProportion = 1-scale / 2;
var newOffset = sliderOffset + (addProportion * elHeight);
Whether you should use innerHeight() or outerHeight() depends on your layout.
well just get the new offset after you've applied the scaling
...
$('.field-slideshow-wrapper').css({ transform: 'scale(' + scale + ')'});
var newOffset = $('.field-slideshow-wrapper').offset().top;
I have a slideshow where all the elements except from the active one has the transform scale(0.925). I need to get the total width of all my elements and apply this to the parent.
$(slides).each(function() {
console.log($(this).outerWidth());
tot_width += $(this).outerWidth();
});
$('ul').css({width: tot_width});
The tot_width is wrong because of the scaling of the elements. How can I fix this?
Assuming, you are displaying them already, you can use
tot_width += $(this)[0].getBoundingClientRect().width;
You could check if each element is active and then multiply by the scale factor:
$(slides).each(function() {
var width = $(this).outerWidth();
if (!($(this).hasClass('active'))) {
width = width * .925;
}
tot_width += width;
});
I am trying to create a simple canvas grid which will fit itself to the player's current zoom level, but also to a certain canvas height/width proportional screen limit. Here is what I got so far:
JS:
var bw = window.innerWidth / 2; //canvas size before padding
var bh = window.innerHeight / 1.3; //canvas size before padding
//padding around grid, h and w
var pW = 30;
var pH = 2;
var lLimit = 0; //9 line limit for both height and width to create 8x8
//size of canvas - it will consist the padding around the grid from all sides + the grid itself. it's a total sum
var cw = bw + pW;
var ch = bh + pH;
var canvas = $('<canvas/>').attr({width: cw, height: ch}).appendTo('body');
var context = canvas.get(0).getContext("2d");
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) { //handling the height grid
context.moveTo(x, 0);
context.lineTo(x, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) { //handling the width grid
context.moveTo(0, x); //begin the line at this cord
context.lineTo(bw, x); //end the line at this cord
lLimit++;
}
//context.lineWidth = 0.5; what should I put here?
context.strokeStyle = "black";
context.stroke();
}
drawBoard();
Now, I succeeded at making the canvas to be at the same proportional level for each screen resolution zoom level. this is part of what I am trying to achieve. I also try to achieve thin lines, which will look the same at all different zooming levels, and of course to remove the blurriness. right now the thickness
of the lines change according to the zooming levels and are sometimes blurry.
Here is jsFiddle (although the jsFiddle window itself is small so you will barely notice the difference):
https://jsfiddle.net/wL60jo5n/
Help will be greatly appreciated.
To prevent blur, you should account for window.devicePixelRatio when setting dimensions of your canvas element (and account for that dimensions during subsequent drawing, of course).
width and height properties of your canvas element should contain values that are proportionally higher than values in CSS properties of the same names. This can be expressed e.g. as the following function:
function setCanvasSize(canvas, width, height) {
var ratio = window.devicePixelRatio,
style = canvas.style;
style.width = '' + (width / ratio) + 'px';
style.height = '' + (height / ratio) + 'px';
canvas.width = width;
canvas.height = height;
}
To remove blurry effect on canvas zoom/scale i used image-rendering: pixelated in css
The problem is that you are using decimal values to draw. Both the canvas width and the position increments in your drawBoard() loop use fractions. The canvas is a bitmap surface, not a vectorial drawing. When you set the width and height of the canvas, you set the actual number of pixels stored in memory. That value cannot be decimal (browsers will probably just trim the decimal part). When you try to draw at decimal positions, the canvas will use pixel interpolation to avoid aliasing, hence the occasional blur.
See a version where I round x before drawing:
https://jsfiddle.net/hts7yybm/
Try rounding the values just before you draw them, but not in your actual logic. That way, the imprecision won't stack as the algorithm keeps adding to the value.
function drawBoard(){
for (var x = 0; lLimit <= 8; x += bw / 8) {
var roundedX = Math.round(x);
context.moveTo(roundedX, 0);
context.lineTo(roundedX, bh);
lLimit++;
}
for (var x = 0; lLimit <= 17; x += bh / 8) {
var roundedX = Math.round(x);
context.moveTo(0, roundedX);
context.lineTo(bw, roundedX);
lLimit++;
}
context.lineWidth = 1; // never use decimals
context.strokeStyle = "black";
context.stroke();
}
EDIT: I'm pretty sure all browsers behave as if the canvas was an img element, so there's no way to prevent aliasing when the user zooms with their browser's zoom function, other than with prefixed css. And even then, I'm not sure the browsers's zoom feature takes that into account.
canvas {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
Also, make sure the canvas doesn't have any CSS-set dimensions. That only stretches the image after it's been drawn instead of increasing the drawing surface. If you want to fill a block with the canvas by giving it 100% width and height, then you need some JS to compute the CSS-given height and width and set the value of the canvas's width and height property based on that. Then you can make your own implementation of a zoom function within your canvas drawing code, but depending on what you're doing it might be overkill.
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.