Differentiate between scroll and pinch-to-zoom
elizabethprins opened this issue · comments
Hi, thank you for all the great work! I use this package for zooming and panning in a user interface, and it works wonderfully.
The only issue I have is that the onWheel
event handles both the scrolling and the pinch-to-zoom gestures, whereas it would be preferable if scrolling were still possible, as it is for example in the zoom-and-pan interfaces of Sketch and Metro.io.
According to this article, the solution could be to add (D.field "ctrlKey" D.bool)
to the decoder in the onWheel
function:
When you perform a pinch-zoom gesture, Firefox and Chrome produce a wheel event with a
deltaY: ±scale
,ctrlKey: true
.
If ctrlKey
is False
, the message could be a NoOp
and the preventDefault
in the event listener could be removed to allow scrolling.
Probably it would make sense to make this customisable. For some use cases scrolling to zoom is kind of what you want (i.e. maps), others not so much.
Alternatively we could do the Google Maps sort of thing where a little message pops up when you scroll over the interactive area telling the user to hold ctrl to zoom.
Thanks for responding! Yes, I agree that scrolling to zoom can still be the best choice in some use cases.
Also, it turns out I oversimplified the scrolling solution. Because with a regular scroll window, it's possible to drag your canvas out of view and not be able to scroll back to it. So you need a function that actually handles 'panning on scroll'.
It would be great if the onWheel event could be customized to allow for this! I wouldn't know the best way to implement this, but here is a (still non-customizable) solution that I found works for my case:
onWheel : Zoom -> (OnZoom -> msg) -> Attribute msg
onWheel _ tagger =
custom "wheel"
(D.field "ctrlKey" D.bool
|> D.andThen
(\isCtrlKeyPressed ->
let
decoder =
if isCtrlKeyPressed then
zoomOnWheelDecoder
else
panOnWheelDecoder
in
D.map
(\onZoom ->
{ message = tagger onZoom
, stopPropagation = True
, preventDefault = True
}
)
decoder
)
)
zoomOnWheelDecoder : Decoder OnZoom
zoomOnWheelDecoder =
D.map3
(\deltaY deltaMode position ->
Wheeled
(-deltaY
* (if deltaMode == 0 then
0.004
else if deltaMode == 1 then
0.05
else
1
)
)
position
)
(D.field "deltaY" D.float)
(D.field "deltaMode" D.int)
Events.decodeMousePosition
panOnWheelDecoder : Decoder OnZoom
panOnWheelDecoder =
D.map3
(\scrollX scrollY position ->
Scrolled position scrollX scrollY
)
(D.field "wheelDeltaX" D.int)
(D.field "wheelDeltaY" D.int)
Events.decodeMousePosition
where Scrolled
does the following:
Scrolled position0 scrollX scrollY ->
let
position1 =
Transform.invert
( Tuple.first position0 - (toFloat scrollX / 4)
, Tuple.second position0 - (toFloat scrollY / 4)
)
model.transform
transform_ =
translate position0 position1 model.transform
in
Zoom
{ model
| transform = transform_ |> constrain model.extent model.translateExtent
, transition = Nothing
}