Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chrome-specific issue when using D3 zoom

I'm working on a D3 application and have bumped into a strange zooming issue that occurs in Chrome but not Firefox (these are the only browsers I've tested my code in). I've boiled the problem down to the code snippet below.

Basically, in certain conditions, the zoom handler I have defined does not get called when I scroll over my canvas element (using either my mouse's scroll button or two fingers on my laptop's trackpad). However, clicking and dragging on the canvas to try to pan it does successfully invoke the zoom handler.

Details of the issue:

  • Zooming works as expected as long as the "click" event handler does not get called. Once it's called, scroll zooming will not work. And it has something to do with invoking ctx.getImageData in the click event handler.

  • If I omit the .call at the end of the canvas selection that invokes clearRect, the zooming issue occurs even if you did not trigger a "click" event

  • The issue occurs in other versions of D3 besides v5. I've tried v6 and v7.

  • The Chrome version I'm using is 92.0.4515.159

  • There are a couple ways I know of to resolve the issue:

    • The issue is resolved if the canvas element's opacity is not set to 0.
    • It can also be resolved by calling window.addEventListener('wheel', () => {})
    • From what I recall, another solution is defining the zoom behavior on a parent element of the canvas instead.

The code below creates a canvas element in the top left corner (it's not visible since its opacity is 0). Zooming on the element will print a "zooming" message to the console. Clicking on the canvas will print "click". You'll find that with the code as is, if you click on the element and then try to scroll zoom, "zooming" will not get printed. You can also run the code in JSFiddle: https://jsfiddle.net/yr3jdvhw/37/

I'd like to know what's causing this issue. As I mentioned, I could just set the canvas's opacity to a non-zero value to avoid the problem. But I'd really like to know the root cause.

<html>
  <head>
    <script src="https://d3js.org/d3.v5.min.js"></script>
  </head>
  <body>
    <canvas class="my-canvas"></canvas>
   
    <script>
      const myCanvas = d3.select(".my-canvas")
      .style('opacity', 0)
      .on('click', function () {
        console.log("click")
        let ctx = this.getContext('2d')
        const pixel = ctx.getImageData(0, 0, 1, 1)
      })
      .call(d3.zoom().on('zoom', () => {
        console.log("zooming")
      }))
      .call(function (s) {
        let ctx = s.node().getContext('2d')
        ctx.clearRect(0, 0, s.node().width, s.node().height)
      })
    </script>
  </body>
</html>
like image 301
Eric Avatar asked May 19 '26 06:05

Eric


1 Answers

This is a Chrome bug, I just opened a new issue about it, let's hope this will get fixed soon.

From my investigations, the root issues are that they do handle wheel events at the compositor level and that when your canvas gets deaccelerated, it doesn't take the good path in the compositor anymore and thus isn't seen as a "wheel event handler region" anymore.

Calling getImageData() currently deaccelerates your canvas, it goes from the GPU to the CPU and stays there, which is why calling this method causes the issue. Similarly, until you perform any drawing operation, the canvas isn't moved to the GPU yet, and thus here too the bug reproduces.

Note that hopefully in a few versions { willReadFrequently } will be available without a flag, and that at this moment most canvases will always be accelerated.

like image 125
Kaiido Avatar answered May 21 '26 09:05

Kaiido



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!