Noticed that some code that uses gl-js hacks it to snap to integer zoom levels. We should support this in core.
:+1:
{ ratchety: true, ... }
A note for context: the biggest advantage (in my mind) of enabling "ratchety zoom" is improving rendering performance.
How would this improve rendering performance?
My impression was that some users were using rachety zooming to avoid some calculations on fractional zoom changes. Is that not your understanding, @mourner?
@lucaswoj I don't think there's a significant performance difference between a frame when zooming and a frame when panning. Fractional vs integer zoom levels shouldn't affect performance as well.
The only use case I can think of for snapping to integer zoom levels is compatibility with stuff like Leaflet, but e.g. https://github.com/mapbox/mapbox-gl-leaflet doesn't need a GL JS option for that because it synchronizes based on its own events.
@tmcw can you elaborate more on use cases and how that would work exactly? Would it be completely discrete snapping (one frame per integer zoom), or animated zooming between integer zooms that you can't stop?
The ArcGIS API is one example - There's another scroll-zooming example I can dig up that snapped to integer zoom levels. Every example I've seen so far supports animated zoom but then snaps to a level, and I'm pretty sure that it's about two things:
"Making raster imagery look nice, maybe? Since you don't have over or under-scaled images?"
That is the point for me.
PS.: Do you have those hacks? I need them for now...
@zavan You can use some iteration of this to get the behavior you're looking for:
map.on('moveend', function() {
map.setZoom(Math.round(map.getZoom()));
});
Do you support this feature now? The set zoom function sharply shakes map and isn't good for me.
This worked for me:
import { debounce } from 'lodash'
import { Map } from 'mapbox-gl'
const map = new Map({
scrollZoom: false
})
map.on('wheel', debounce(({ originalEvent: { wheelDelta } }) => {
map.setZoom(map.getZoom() + Math.sign(wheelDelta))
}, 100))
@vwong I assume your solution will not work when using the fitBounds function, right?
So the only valid workaround is to use @lucaswoj answer?
@HarelM It should work. Here's a fuller version of my example. Works on touch devices too.
const integerZoom = debounce(({ originalEvent: { deltaY, detail, wheelDelta } }) => {
const delta = wheelDelta || -deltaY || -detail
const zoom = Math.round(map.getZoom()) + (delta > 0 ? 1 : -1)
map.setZoom(zoom)
}, 50, { leading: true, trailing: false })
const ratchet = () => {
const zoom = Math.round(map.getZoom())
if (zoom !== map.getZoom()) {
map.setZoom(zoom)
}
}
const ratchetOn = () => {
map.setZoom(Math.round(map.getZoom()))
map.on('idle', ratchet)
map.on('wheel', integerZoom)
map.scrollZoom.disable()
}
const ratchetOff = () => {
map.off('idle', ratchet)
map.off('wheel', integerZoom)
map.scrollZoom.enable()
}
In my use case, I needed to turn ratchet on/off depending on other external factors. Good luck!
@zavan You can use some iteration of this to get the behavior you're looking for:
map.on('moveend', function() { map.setZoom(Math.round(map.getZoom())); });
this snippet will cause
ERROR RangeError: Maximum call stack size exceeded
@zavan You can use some iteration of this to get the behavior you're looking for:
map.on('moveend', function() { map.setZoom(Math.round(map.getZoom())); });this snippet will cause
ERROR RangeError: Maximum call stack size exceeded
@geoyogesh to not have the ERROR RangeError:
js
map.on('moveend', function () {
const zoom = Math.round(map.getZoom())
if (zoom !== map.getZoom()) {
map.setZoom(zoom)
}
})
@geojs i wanted rachety zoom mode because i am using dynamic map service and mapbox aggregively fetches data during factional zoom... i disabled zoom scroll which made api fetch calls more friendly
map.scrollZoom.disable();
user expecirece was bad while using map.setZoom so i prefered disabling zoom scroll
Thanks
Most helpful comment
@zavan You can use some iteration of this to get the behavior you're looking for: