Openlayers3 - fractional zoom on touch device using pinch gesture?
<p>Openlayers3 can display a map at a fractional zoom level programmatically by specifying a fractional number in <code>setZoom()</code>.</p>

<p>However, I want my users to be able to get a fractional zoom level on a mobile touch-driven device (ie, a smart phone).  Pinch or reverse pinch zooms out/in by jumping to the nearest whole zoom level when the fingers are removed from the screen.</p>

<p>How can a touch/mobile user get Openlayers3 to stay at the exact extent (fractional zoom level) that the user has pinched to?</p>

<p>Is there something I can add to the javascript for my Openlayers3 map or view to get this to work as desired?</p>

<p>In particular, I note that Openlayers3 has a <code>ol.interaction.PinchZoom()</code> class ( <a href="https://openlayers.org/en/latest/apidoc/ol.interaction.PinchZoom.html" rel="nofollow noreferrer">https://openlayers.org/en/latest/apidoc/ol.interaction.PinchZoom.html</a> ).  This is a subclass of <code>ol.interaction.Pointer</code> which has a <code>handleUpEvent</code> and a <code>handleEvent</code> (which should be a function).  So in theory, I should be able to either replace the <code>handleUpEvent</code> in <code>PinchZoom</code> OR replace the default <code>PinchZoom</code> interaction with one that has a custom event handler function.  But with both of these approaches, I can't get my new handleUpEvent function to be called.</p>

<p>(While trawling the OL code for <code>PinchZoom</code> I have found that the default <code>PinchZoom</code> does what I want if one finger is lifted from the touch-screen before the other finger, but I still want to get this working when both fingers are lifted simultaneously.)</p>

<p>Here is what I've tried so far... </p>

<p><strong>FIRST ATTEMPT</strong> - This just attempts to replace the standard <code>PinchZoom</code>'s <code>handleUpEvent</code> with a custom one, and set this as the only interaction for the map.  However, the event function is never called (never logs anything).</p>

<pre><code>function handleUpEvent(evt) {
    console.log("Up event handler");
    return true; //  Stop drag
}

map = new ol.Map({
    layers: [],
    target: 'map',
    controls: controls,
    interactions: [new ol.interaction.PinchZoom({handleEvent: handleUpEvent})],
    view: new ol.View({projection: projCode})
});
</code></pre>

<p><strong>SECOND ATTEMPT</strong> - This attempt is based on the actual OL code for creating the standard <code>PinchZoom</code> interaction.  In this case, all my event handler functions DO get called, the number of touches ( <code>targetPointers</code> ) is always zero (as logged).  I'm no javascript guru, but I suspect that this is because the symbols in ol.js are different to in ol-debug.js which I'm basing this on.  In fact I had to declare <code>targetPointers</code> myself to even get this to run, even though it is declared by OL itself already (but presumably using a different symbol name in the non-debug version).</p>

<pre><code>function handleDownEvent(mapBrowserEvent) {
    console.log("DOWN event handler");
    this.anchor_ = null;
    this.lastDistance_ = undefined;
    this.lastScaleDelta_ = 1;
    mapBrowserEvent.map.render();
    return true;  //  Start drag
}

function handleDragEvent(mapBrowserEvent) {
    if ( this.targetPointers.length &lt; 2 ) {
        console.log("DRAG event ignored - touches ", this.targetPointers.length);
    } else {
        console.log("DRAG event handled");
        var scaleDelta = 1.0;
        var touch0 = this.targetPointers[0];
        var touch1 = this.targetPointers[1];
        var dx = touch0.clientX - touch1.clientX;
        var dy = touch0.clientY - touch1.clientY;

        // distance between touches
        var distance = Math.sqrt(dx * dx + dy * dy);

        if (this.lastDistance_ !== undefined) {
            scaleDelta = this.lastDistance_ / distance;
        }
        this.lastDistance_ = distance;
        if (scaleDelta != 1.0) {
            this.lastScaleDelta_ = scaleDelta;
        }

        var map = mapBrowserEvent.map;
        var view = map.getView();
        var resolution = view.getResolution();

        // scale anchor point.
        var viewportPosition = map.getViewport().getBoundingClientRect();
        var centroid = ol.interaction.Pointer.centroid(this.targetPointers);
        centroid[0] -= viewportPosition.left;
        centroid[1] -= viewportPosition.top;
        this.anchor_ = map.getCoordinateFromPixel(centroid);

        // scale, bypass the resolution constraint
        map.render();
        ol.interaction.Interaction.zoomWithoutConstraints(
            map, view, resolution * scaleDelta, this.anchor_);
    }
}

function handleUpEvent(mapBrowserEvent) {
    console.log("UP event handler");
    return true;  //  Stop drag
}

var pinchZoom = new ol.interaction.Pointer({
        handleDownEvent: handleDownEvent,
        handleDragEvent: handleDragEvent,
        handleUpEvent: handleUpEvent
        });

pinchZoom.targetPointers = [];

map = new ol.Map({
    interactions: ol.interaction.defaults().extend([pinchZoom]),
    layers: [
             new ol.layer.Tile({
                               source: new ol.source.TileJSON({url: 'https://api.tiles.mapbox.com/v3/mapbox.geography-class.json?secure'})
                               })
             ],
    target: 'map',
    view: new ol.View({center: [0, 0], zoom: 3})
});
</code></pre>