Leaflet: binding to "touchstart" events on map?

Created on 21 Mar 2013  路  7Comments  路  Source: Leaflet/Leaflet

I try to make this work on iOS / Safari:

var map = L.map('map').setView([51.505, -0.09], 13);
map.on('touchstart', function() {
  // does not get here when touching the map
  alert('touchstart')
})

But couldn't figure out how. It's not listed in the API reference but I wonder if there is any way to support this event?

Or maybe there is even a better way to achieve the following:
I want to add a marker to a map, by touch and holding for a second. It was easy to set this up for mouse events, but I can't find a way to make the same for touch events.

Any idea?

Most helpful comment

I found a workaround on the mailing list:
https://groups.google.com/forum/?fromgroups=#!topic/leaflet-js/M-6fIg7K1CU

Here it is, slightly adjusted, so it adds support for both, touchstard & touchend:

L.Map.mergeOptions({
  touchExtend: true
});

L.Map.TouchExtend = L.Handler.extend({

  initialize: function (map) {
    this._map = map;
    this._container = map._container;
    this._pane = map._panes.overlayPane;
  },

  addHooks: function () {
    L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
    L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
  },

  removeHooks: function () {
    L.DomEvent.off(this._container, 'touchstart', this._onTouchStart);
    L.DomEvent.off(this._container, 'touchend', this._onTouchEnd);
  },

  _onTouchStart: function (e) {
    if (!this._map._loaded) { return; }

    var type = 'touchstart';

    var containerPoint = this._map.mouseEventToContainerPoint(e),
        layerPoint = this._map.containerPointToLayerPoint(containerPoint),
        latlng = this._map.layerPointToLatLng(layerPoint);

    this._map.fire(type, {
      latlng: latlng,
      layerPoint: layerPoint,
      containerPoint: containerPoint,
      originalEvent: e
    });
  },

  _onTouchEnd: function (e) {
    if (!this._map._loaded) { return; }

    var type = 'touchend';

    this._map.fire(type, {
      originalEvent: e
    });
  }
});
L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);

But maybe there is a more elegant solution?

All 7 comments

I found a workaround on the mailing list:
https://groups.google.com/forum/?fromgroups=#!topic/leaflet-js/M-6fIg7K1CU

Here it is, slightly adjusted, so it adds support for both, touchstard & touchend:

L.Map.mergeOptions({
  touchExtend: true
});

L.Map.TouchExtend = L.Handler.extend({

  initialize: function (map) {
    this._map = map;
    this._container = map._container;
    this._pane = map._panes.overlayPane;
  },

  addHooks: function () {
    L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
    L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
  },

  removeHooks: function () {
    L.DomEvent.off(this._container, 'touchstart', this._onTouchStart);
    L.DomEvent.off(this._container, 'touchend', this._onTouchEnd);
  },

  _onTouchStart: function (e) {
    if (!this._map._loaded) { return; }

    var type = 'touchstart';

    var containerPoint = this._map.mouseEventToContainerPoint(e),
        layerPoint = this._map.containerPointToLayerPoint(containerPoint),
        latlng = this._map.layerPointToLatLng(layerPoint);

    this._map.fire(type, {
      latlng: latlng,
      layerPoint: layerPoint,
      containerPoint: containerPoint,
      originalEvent: e
    });
  },

  _onTouchEnd: function (e) {
    if (!this._map._loaded) { return; }

    var type = 'touchend';

    this._map.fire(type, {
      originalEvent: e
    });
  }
});
L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);

But maybe there is a more elegant solution?

If you just want to listen to a long press on the map, then add a 'contextmenu' event listener. The event fires on a right click or a long press on the map

Closing as contextmenu should work perfectly for the original case.

contextmenu doesn't really work well...

contextmenu should work, what doesn't work about it?

The code above didn't work for me because this._map.mouseEventToContainerPoint(e) didn't work on touch events. Instead (coffeescript):

L.Map.mergeOptions
  touchExtend: true

L.Map.TouchExtend = L.Handler.extend

  initialize: (map) ->
    this._map = map
    this._container = map._container
    this._pane = map._panes.overlayPane

  addHooks: ->
    L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this)
    L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this)
    L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this)

  removeHooks:  () ->
    L.DomEvent.off(this._container, 'touchstart', this._onTouchStart)
    L.DomEvent.off(this._container, 'touchend', this._onTouchEnd)
    L.DomEvent.off(this._container, 'touchmove', this._onTouchMove)

  _onTouchEvent: (e, type) ->
    return unless this._map._loaded

    touch = e.touches[0]
    containerPoint = L.point(touch.clientX, touch.clientY)
    layerPoint = this._map.containerPointToLayerPoint(containerPoint)
    latlng = this._map.layerPointToLatLng(layerPoint)

    this._map.fire type, 
      latlng: latlng
      layerPoint: layerPoint
      containerPoint: containerPoint
      originalEvent: e

  _onTouchStart:  (e) ->
    @_onTouchEvent(e, 'touchstart')

  _onTouchEnd:  (e) ->
    return unless this._map._loaded
    this._map.fire 'touchend', 
      originalEvent: e

  _onTouchMove:  (e) ->
    @_onTouchEvent(e, 'touchmove')

L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend)

Expanding on the post above... for touch events the touch.clientX and touch.clientY may not be accurate if the map is not full screen. When I tried the above code the coordinates were off by a bit. I took a look at the way Leaflet figures out the coordinates from event data in L.DomEvent. getMousePosition() and replicated the logic with touch events.

Instead of doing

touch = e.touches[0]
containerPoint = L.point(touch.clientX, touch.clientY)

I did

var touch = e.touches[0],
    rect = this._container.getBoundingClientRect(),
    lat = touch.clientX - rect.left - this._container.clientLeft,
    lng = touch.clientY - rect.top - this._container.clientTop,
    containerPoint = L.point(lat, lng),
Was this page helpful?
0 / 5 - 0 ratings

Related issues

gdbd picture gdbd  路  3Comments

remilev picture remilev  路  4Comments

JonnyBGod picture JonnyBGod  路  4Comments

onethread picture onethread  路  3Comments

ssured picture ssured  路  3Comments