d3 / d3-zoom

Pan and zoom SVG, HTML or Canvas using mouse or touch input.
https://d3js.org/d3-zoom
ISC License
505 stars 143 forks source link

Can't get d3.zoom working for touch events #141

Closed dmosberger closed 5 years ago

dmosberger commented 6 years ago

I suspect I'm doing something wrong, but after searching far and wide, I don't see the problem.

Background: I'm trying to use d3.zoom (v5.4.0) to pan and zoom a screen area (as in an oscilloscope screen). It's working perfectly fine with the mouse: dragging moves the screen as expected and wheel-action zooms in/out. Beautiful! However, nothing happens on an Android phone (or the Chrome developer's console mobile emulator) when trying to pan with a touch move or zooming with a pinch gesture.

I use a rectangle for receiving the events:

this.chart.zoomRect = this.chart.el.append('rect')
  .attr('stroke', 'none')
  .attr('fill', '#ff000020')
  .attr('pointer-events', 'all');

and then add the zoom behavior like this:

this.chart.zoomRect
  .call(d3.zoom()
    .filter(() => { console.log('event ' + d3.event.type, d3.event); return true})
    .touchable((a,b,c) => {console.log('touchable', a,b,c); return true})
    .on('zoom', () => {
      console.log('event.transform', d3.event.transform);
      const k = d3.event.transform.k;
      const x = d3.event.transform.x;
      this.channelsGroup.attr('transform', (`translate(${x} 0)` + `scale(${k} 1)`));
    }))

The filter() call, never logs any 'touchmove' events. It does get a 'touchstart' event both on pan and pinch gestures, but nothing during touchmove or pinch-move. On the other hand, the touchable() function does seem to get called during the move events.

I tried adding attribute 'touch-action: none' to the zoomRect, thinking that maybe the browser is trying to handle the touchmove events, but that made no difference.

dmosberger commented 6 years ago

Turns out the main problem was that I was getting unexpected ngAfterViewChecked() calls (I'm doing this in Angular) which in turned lead to spurious calls to my resize handler which would then reset the zoom behavior. Oops. Now that I fixed this, panning is working great.

Still no luck with pinching for zooming though.

dmosberger commented 6 years ago

Oh, just to be clear: now when using the pinch gesture (Shift + drag in the Chrome web developer tools for mobile), I do get zoom events, but d3.event.transform.k is always 1. Only x and y change. For example:

screen.service.ts:207 event.transform Transform {k: 1, x: -88, y: -170} 5 12.9
screen.service.ts:207 event.transform Transform {k: 1, x: -88, y: -170} 5 12.9
screen.service.ts:207 event.transform Transform {k: 1, x: -88, y: -167} 5 12.9
screen.service.ts:207 event.transform Transform {k: 1, x: -88, y: -166} 5 12.9
screen.service.ts:207 event.transform Transform {k: 1, x: -88, y: -165} 5 12.9
dmosberger commented 6 years ago

Sorry about the monologue. Turns out pinch zoom gesture does work on an actual phone, just not in the Chrome web-developers console.

Maarondesigns commented 6 years ago

I can't seem to get d3 zoom to work properly with d3 geo projections. I've converted Ivy Wang's Drag to Rotate the Globe to v4 and combined it with zoom. It works properly with mouse functions as long as I include svg.on("mousedown.zoom", null); , otherwise d3.zoom mousedown overrides the d3.drag functions. I still can't get it to work with touch. Double tapping zooms on a laptop (Asus, windows 10, google chrome), but on mobile (IOS 11.4) it doesn't. Two finger zooming works on a laptop but on mobile only changes lineweights I've included in my zoom function. Also, rotating the projection doesn't work unless I include

   svg.on("mousedown.zoom", null)
      .on("touchstart.zoom", null)
      .on("touchmove.zoom", null)
      .on("touchend.zoom", null);

which disables touch zooming all together. These things seem to work together on Jason Davies site though when you try to zoom on mobile it usually zooms the entire page. I would love to know if I am relatively close to getting this to work, or if it simply won't work combining these zoom and drag functions. Is there a working example of this somewhere? I haven't been able to find anything in v4 with map projections and that works on touch.

Here a code snippet:

var svg = d3
    .select("body")
    .append("svg")
    .attr("id", "world")
    .attr("width", width)
    .attr("height", height)
    .call(d3.zoom().on("zoom", zoomed));

   svg.on("mousedown.zoom", null)
      .on("touchstart.zoom", null)
      .on("touchmove.zoom", null)
      .on("touchend.zoom", null);

  function zoomed() {
     let k = d3.event.transform.k;
     d3.selectAll("#world").attr("transform", "scale(" + k + ")");
     d3.selectAll(".graticule").attr("stroke-width", 0.5 / k);
     d3.selectAll(".pin").attr("stroke-width", 2 / k);
     d3.selectAll(".travelPath").attr("stroke-width", 2 / k);
     d3.selectAll(".land").attr("stroke-width", 1 / k);
     d3.selectAll(".border").attr("stroke-width", 1 / k);
   }

And my whole project is on my Codepen. Zoom function start on line 287 and drag on line 344.

PiN73 commented 5 years ago

If someone faced with shaking, slow pan (dragging) speed and inability to go back after zooming / moving outside of svg:

Try to wrap the svg with div and attach event to the div instead of svg: d3.select(svgContainer).call(d3.zoom().on("zoom", zoomed));

(In zoomed function, apply transform to svg just as before)

mbostock commented 5 years ago

Closing to inactivity. Possibly related to #162.