I need some advice on how to set up zooming in and out of a HTML5 JavaScript canvas. I have the functions and switch statement set up just need advice on how to actually enable zoom.
I was considering just making it use the browsers zoom function as I only need it to work on Chrome. Is this recommended?
Thanks
Just use ctx.scale(x, y)
Here is a direct link on MDN, however I advise you to read the whole article.
You can scale in the canvas using the browser's zoom, but just be warned that a canvas is a raster image, so zooming in will introduce aliasing (jaggies). I haven't yet seen a way to draw on a zoomed-in canvas (i.e. canvas pixels are larger than display pixels) without aliasing. And it turns out to be very hard to even work out how much you are zoomed in: How to detect page zoom level in all modern browsers?
If, however, you zoom by redrawing on some event, even using ctx.scale(x, y) as #rezoner points out, rather than allowing the browser to zoom, then you can still draw without aliasing. For a smooth zoom, I would suggest using scale as a quick zoom while the use is zooming quickly, and performing a full-redraw every 1.3x or so and again when they stop zooming. That should give you something like the Google Maps behaviour where you get a rough zoom immediately (good responsiveness), but it updates the view as you continue to zoom.
Related
I would like to comment on my problem in case someone has an idea of what it is due to or proposes an alternative solution.
Currently I have a large ag-charts graph that does not fit and it is always necessary to scroll to be able to see it in its entirety but when scrolling the axes are not maintained, so I resorted to the alternative of cloning the graph canvas twice to generate 2 new canvas clipped with "position:fixed" so that they always stay at the ends.
So far there is no problem except that if the browser zoom and/or the windows scale differs from 100%, it affects the cropping of said canvas in an inconsistent way. I was not able to get a rule of 3 to apply the correct zoom multiplier ( with ctx.drawImage() ) due to its inconsistency.
The error stops happening when you test it with a normal canvas that has not been generated by ag-charts
Here I have a very simple Live Demo to test it.
(To reproduce the bug you need to have the zoom applied before the generation of the graph).
I also tried to get the zoom level using:
(Math.round(window.devicePixelRatio*100)/100, -1);
and applying the correspondent ratio using:
.drawImage(canvas,0, 0, canvas.width*ratio, canvas.height*ratio);
but it only works in specific percentages of zoom
I am experiencing some strange behaviour of fill opacity settings. This happens on Circle and semi-Circle markers. FillOpacity is not set explicitly and is using default values.
My scenario is described below (simplified version can be tested in following JS Bin
I am having more than 100k semiCircles, which are defined in a canvas element for faster rendering.
For each zoom level bigger than 10 different radii are defined, so that to have a readable map. The higher the zoom, the smaller the radius. After "zoomend" event is fired, the current zoom is defined and the corresponding radius is set to the circle with:
layerSites1.eachLayer(function(layer) {
return layer.setRadius(rad1);
});
After zooming in (usually to the maximum) and when I start zooming out the density of fillOpacity changes - it becomes darker. This can be fixed even with the smallest move of the map. Sometimes the complete circle is with darker fillOpacity, sometimes just a part of it. Below you can find examples for both cases:
The above mentioned JS Bin example can be tested - the change of fillOpacity is more visible on the red circles. You need to fully zoom in and then zoom out, so that to observer the effect. If this does not happen the first time, try again to zoom in and then zoom out.
I will be grateful for any suggestion whether I am doing something wrong or it is an issue of browser rendering, or something else. I tested it with Chrome, Chromium, IE and Firefox.
Thank you!
It seems there's a timing issue between zoomend events and the actual redrawing of the canvas. Maybe an issue worth raising on GitHub, but in the meantime, letting the call stack clear and only then setting the radius seems to do the trick:
setTimeout(function() {
layerSites1.eachLayer(function(layer) {
return layer.setRadius(rad1);
});
}, 0);
Here's an updated JsBin
vI know to some extent this is a known issue that has been asked here but never answered, but I'm hoping for some additional insight.
I have a simple SVG image in a div on a web page. I want to be able to zoom the image using pinch gestures and have the SVG render sharply. It doesn't need to be sharp during the zoom, but should be sharp after the gesture is complete.
I'm doing my testing on a Windows 8.1 computer with a touch monitor, on an iPad, and on an old Android Galaxy Tab. On the main computer, I'm using IE11 and Chrome. For handling touch gestures, I'm using Hammer.js. I'm doing the zooming by modifying the transform CSS using the jQuery css() function (I'm setting scale3d and translate3d).
IE11 on my computer works exactly like I would like it to. It keeps the SVG image sharp throughout the pinch zoom and the image is always sharp regardless of when I set the CSS.
Chrome on my computer always renders the SVG blocky when I am zoomed in using pinch zoom. It is blocky during the zoom and does not get sharp afterwards. If I add a call to zoom the image after the page (x13) is loaded, the image is sharp. If I use a setTimeout() call to reset the zoom and then rezoom the image after the pinch zoom ends, the image is sharp.
On the iPad and Android tablet, the SVG never renders sharply, regardless of when I zoom it in code.
Does anyone have any ideas of how to reliably end up with a sharp rendering of SVG after a pinch-zoom is completed? Does anyone have a better solution? Using the SVG viewBox to zoom and pan during the gesture does not provide good enough performance for our more complex SVG images. I'm thinking of attempting to use the hardware accelerated scale3d/translate3d during the gesture and then attempt to convert this to new SVG viewBox settings afterwards, but this is a challenge and I'm not sure panning will work correctly after this is done. Any pointers or ideas are greatly appreciated.
Having just tried to answer your other SVG Question I will have a try at this one.
Michael Mullany is spot on that GPU translations will never give you a sharp image. This is because the GPU just maps one array of pixels to a new one. Because it has no concept of lines or rectangles the resolution is set by the time the GPU gets the image. The CPU will always struggle to re-render in time for a smooth drag.
What happens to allow some browsers to produce a sharp image is they do clever manipulations like rendering more pixels than they need so some zooming can happen without loss of resolution. These however are completely inconsistent and just not possible to rely on.
The as I see it 'correct' solution is to translate and zoom with hardware accelerated transformations during a pinch or zoom and then to refresh the viewbox with no css transformation when the translation has finished.
Challenges to overcome are
The css transformation will use screen coordinates but the viewBox manipulation will use the SVG coordinate system.
There are browser inconsistencies in both screenCTM and device pixel ratio
limits so that people don't drag or zoom too far need to be calculated in both systems
Pinch centerpoints also need to be calculated in both
To improve performance further the css transforms will be wrapped into updating on the requestAnimationFrame loop.
My working solution is hammerhead2. I am concentrating on desktops and android mobiles first and it seams to be working for them. To simplify the problem zoom is always centered to the screen center. I do however thing this is currently one of the only solutions. Here is an example of it working.
My understanding is that CTRL-mousewheel zooms are an accessibility feature, built into the browser in question (I am currently testing in Chrome and Opera, both if which use the CTRL- zoom).
This won't ever really be needed for accessibility, however, as the app will not be for the general public. And CTRL-wheel zoom has a nice benefit out-of-box as compared with standard mousewheel zoom over elements: It seems that Angular ui-sortables work perfectly at any zoom/scale.
Unfortunately, from other answers I've found on SO, there is no way, when using CTRL-wheel, to zoom selectively : the browser can only zoom everything. For me, this includes position:fixed overlays scaling at the same time as the main viewport, which is no good.
So I set up some code for scrolling as per whichever element the mouse cursor is over, eg. scale the main viewport using the mousewheel, only if the mouse if over that viewport element.
The problem is that ui-sortable does not behave correctly, when using it for only a single element. I've used scale with transform-origin set at 50% 50% but still, when I drag the ui-sortables at any scale other than 1:1, they appear way off to the left. Any ideas on how to begin to tackle this?
This isn't a problem in Angular's sortable adaptation, rather it's an issue in the underlying jQuery-ui 1.9.2 positioning functions around line 4000 (_generatePosition or maybe one of the others). I believe that as the browser does not modify the actual dimension values during scale or zoom, and the formulae provided in that library do not account for scaling (that I can see), there is no easy fix, since jQuery ui.sortable needs scale-accurate values to calculate correct displacements. (I tried modifying the formula to account for this, but without success.)
The simplest workaround for the present is to manually change the width() / height() of your individual list elements, as seen here. This may require being selective about just what you scale using width() / height(), and what you change using scale (which is generally easier).
I am trying to develop a "Web App" (for lack of better terminology) that displays a sort of interactive map using the HTML5 Canvas element for mobile devices. The map has about 30 buildings (made from semi-transparent PNG images on it) of different shapes and sizes. I want the user to be able to tap on any of the buildings to get information about them. The buildings are often within the same theoretical bounding box of others, so it isn't as simple as just detecting when the user taps in a bounding box (imagine a U shaped building with another building inside the U). Therefore, it should use some sort of raster pixel detection. To make matters more complicated, I need to allow the user to zoom in/out and pan around on the map (because 30 buildings and a map are way too much to show a user on a tiny cellphone screen). So scaling and moving smoothly is a necessity.
I am looking for a JavaScript library that can support these functions:
Image pixel detection
Mobile touch events
Canvas (or stage?) scaling
Usable on mobile devices
So far, I have found KineticJS which supports all of these features. But when I try to move the map with more than 4 buildings on the stage, it is way too jumpy. I tried checking into Fabric.js but it doesn't seem to have pixel detection (as it selects the image when you click within the bounding box, not the actual image itself).
Is there any other JavaScript library out there that can do these things?
Thanks for any help!
The KineticJS is pretty good ( the best lib i found so far )
I never tried to use png images with it, but zooming and panning with a few hundreds primitive objects is pretty smooth (without pixel detection - I imagine the pixel detection is quite cpu expensive operation ).
Maybe you could try overlay standard polygons ( a few for one building ) and attach to the same click/tap event.
Also you don't necessary need to move objects on the canvas you can just put bigger canvas in smaller div ( with overflow:hidden) and drag the whole canvas element.