Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control scroll zoom and add gesture handler warning #10962

Closed
wants to merge 47 commits into from

Conversation

avpeery
Copy link
Contributor

@avpeery avpeery commented Aug 24, 2021

Closes #10227.

In order to address the issue of incidentally zooming the map instead of scrolling the page, this PR introduces a new feature that allows for scroll-zoom control, by requiring pressing either CMD or CTRL key while scrolling in order to zoom. There is also an alert (that can be opted on of) that appears when scrolling over the map without pressing CMD or CTRL key. This feature has been implemented as a control that can be added on. With this control implemented, you can still zoom with pinching.

There is also an option to set HTML or text in the warning. There is also an option to add new CSS classes to allow for some customization. If the alert HTML is not set by the client, the default alert will be "⌘ + scroll to zoom the map" (for MacOS) or "CTRL + scroll to zoom the map".

Check out the debug page: zoom_control.html which is also accessible as a manual testing page.

You can add the control to the map like so:

const scrollZoomBlockerControl = new mapboxgl.ScrollZoomBlockerControl();
map.addControl(scrollZoomBlockerControl);

Here is a screen recording of the feature:

Screen.Recording.2021-09-10.at.9.56.02.AM.mov

Launch Checklist

  • briefly describe the changes in this PR
  • include before/after visuals or gifs if this PR includes visual changes
  • write tests for all new functionality
  • document any changes to public APIs
  • manually test the debug page
  • tagged @mapbox/map-design-team @mapbox/static-apis if this PR includes style spec API or visual changes
  • apply changelog label ('bug', 'feature', 'docs', etc) or use the label 'skip changelog'
  • add an entry inside this element for inclusion in the mapbox-gl-js changelog: <changelog></changelog>

@avpeery avpeery marked this pull request as draft August 24, 2021 17:57
@avpeery avpeery linked an issue Aug 24, 2021 that may be closed by this pull request
Copy link
Contributor

@rreusser rreusser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some thoughts and feedback after a read-through, though I'm glad to discuss in more detail!

zoom: 12.5,
center: [-77.01866, 38.888],
style: 'mapbox://styles/mapbox/streets-v10',
scrollZoom: {controlled: true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about requireCtrl? My only concern with the word controlled is that it has React connotations ("controlled components") which might suggest this has something to do with externally controlling zoom input rather than how it interacts with keyboard events.

@@ -154,6 +168,10 @@ class ScrollZoomHandler {

wheel(e: WheelEvent) {
if (!this.isEnabled()) return;
if (this.isControlled() && !e.ctrlKey && !e.metaKey) {
const scrollZoomControl = new mapboxgl.ScrollZoomControl({closeButton: false}).setHTML();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick search for addControl suggests that AttributionControl is the only control automatically added to the map. Do you think this is best as an opt-in control the developer must add or as an opt-out control you'd have to remove?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this, I think its better to have an opt-in Control for this and not clutter the Map constructor params more. So it might be nice to add this in like this

const map = new mapboxgl.Map({..});

map.addControl(new mapboxgl.ZoomBlockerControl({}));

The reason I think this would be more ergonomic is because it would be easier for users to customize it. With something like this.

const map = new mapboxgl.Map({..});
const zoomBlocker = new mapboxgl.ScrollBlockerControl({..})
           .setHTML(`<b>whatever content the user wants</b>`);
map.addControl(zoomBlocker);

Then instead of having this logic here the blocking of the zoom event can be done in the onAdd method of the control, this way the rest of the codebase is unaware of the blocker control running.

const that = this;

if (this.options.closeFadeOut) {
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use CSS transitions to avoid a timing loop, e.g.:

...-container {
  opacity: 1;
  transition: opacity 300ms ease-in-out;
}

and then either set the opacity directly or toggle a class as needed? (I don't think we have to worry about transitionend events since nothing happens at the end of the transition.)

Copy link
Contributor

@arindam1993 arindam1993 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for starting this, this has been such a heavily requested feature and will be great addition.

My main suggestion is to go with making it opt-in and, trying to make the rest of the codebase unaware of it.

@@ -154,6 +168,10 @@ class ScrollZoomHandler {

wheel(e: WheelEvent) {
if (!this.isEnabled()) return;
if (this.isControlled() && !e.ctrlKey && !e.metaKey) {
const scrollZoomControl = new mapboxgl.ScrollZoomControl({closeButton: false}).setHTML();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to this, I think its better to have an opt-in Control for this and not clutter the Map constructor params more. So it might be nice to add this in like this

const map = new mapboxgl.Map({..});

map.addControl(new mapboxgl.ZoomBlockerControl({}));

The reason I think this would be more ergonomic is because it would be easier for users to customize it. With something like this.

const map = new mapboxgl.Map({..});
const zoomBlocker = new mapboxgl.ScrollBlockerControl({..})
           .setHTML(`<b>whatever content the user wants</b>`);
map.addControl(zoomBlocker);

Then instead of having this logic here the blocking of the zoom event can be done in the onAdd method of the control, this way the rest of the codebase is unaware of the blocker control running.

* .setHTML("<h1>'Use ⌘ + scroll to zoom the map'</h1>")
* .addTo(map);
*/
export default class ScrollZoomControl extends Evented {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a better name would be ScrollZoomBlockerControl ?

Copy link
Contributor

@ryanhamley ryanhamley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good. I had a few nits on the code, but I'm seeing some unexpected behavior when I use the debug page. I know this is a draft so apologies if you're already addressing these, but here's what I've noticed.

  • The text flickers just before the control fades out.
  • If you hold down CMD and really zoom in/out then let go of CMD, the warning pops up and the zoom ends. We should ensure that this control is deactivated for the length of the "zoom event", including inertia. There's no wheelend event or anything like that, but it looks like the scroll_zoom handler has some similar logic to handle successive events as a single "event".

Comment on lines 60 to 61
* new mapboxgl.ScrollZoomControl().setHTML();
* map.addControl(scrollZoomControl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should be const scrollZoomControl = new mapbox...

src/ui/control/scroll_zoom_control.js Show resolved Hide resolved
src/ui/control/scroll_zoom_control.js Show resolved Hide resolved
@avpeery
Copy link
Contributor Author

avpeery commented Sep 9, 2021

@ryanhamley Ah, thanks for clarifying - I can see the bug now. In response to letting zoom happen while the warning is displayed, I have a new change (haven't pushed it yet) that shortens the time of the warning and the fade happens more quickly, so I think it'll be less in the way / make more sense to allow for zoom with CMD or CTRL while the warning is displayed.

@SnailBones SnailBones added this to the v2.5 milestone Sep 9, 2021
@asheemmamoowala asheemmamoowala removed this from the v2.5 milestone Sep 13, 2021
display: flex;
align-items: center;
width: 100%;
height: 100%;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is height: 100% 100% of the height or is it 100% of the width? I thought it meant 100% of the width (even though it's the height property) and that you have to do bottom: 0 to get full height. Though maybe because it's absolute positioned, this does work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I did not know this, I originally had top: 0; left: 0; bottom: 0; right: 0; but based on a preliminary search it seems documented that height: 100% is 100% of the parent element height?

Copy link
Member

@mourner mourner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior itself looks great and it's awesome that it's finally being addressed, but it seems like @arindam1993's suggestion to make this a Control had an unintended consequence of introducing more complexity that it could have if it were a scrollZoom handler option (as proposed e.g. in #10614):

  • It has to adhere to the Control interface, which relies on configurable corner positions, and for this PR led to creating a separate full-container position available for controls which feels like an awkward exception.
  • It adds the expectation of being highly configurable, with methods like add/remove/toggleClassName, setHTML with sanitization, etc., where this narrow interaction use case doesn't really require that flexibility.

So in the end it feels like there's more code in this PR than the use case needs. Personally I would prefer a more minimalistic approach, where:

  • There's just one unique class for the overlay without need to change it. Users will be able to use it for styling.
  • There's just one text for it defined in default_locale.js with the ability to change it via locale option like other labels.
  • There's only Ctrl for the behavior (not Cmd) so there's no need to use different labels for Mac and others, because Ctrl is universal.
  • Fade out delay and duration and whether the overlay is even visible can be fully controlled through CSS without any JS options.

This may feel like nitpicking, but it's hard to remove code like this once it's added, and configurability is overrated for "reasonable defaults" features like this, so I always try to minimize the amount of code where possible for new features.

@avpeery
Copy link
Contributor Author

avpeery commented Oct 1, 2021

Closing this PR as #11029 is the preferred approach and has been merged into main.

@avpeery avpeery closed this Oct 1, 2021
@avpeery avpeery deleted the avpeery/pinch-to-zoom branch October 5, 2022 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable pinch-to-zoom, disable scroll zooming
7 participants