The following is using create-react-app
created the boilerplate and rendered the only component is TestComponent
. In TestComponent
it rendered a SVG showing a few rectangles. (
To ensure there is no overlay among these rectangles, I have removed all the horizontal rectangles. And the mouseover events are the same things, the third and the fourth vertical rectangles mouseover events not working.
I am keen to believe this is a brower bug but I am not sure. The reason is if I change the stoke-width to 20px, the third and fourth rectangle responsed to the mouseover event. However, if the strike-width is 1px, no matter how many times I move across these two rectangles, it has no response at all.
I am using the latest Chrome browser - Version 104.0.5112.81 (Official Build) (64-bit).
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import TestComponent from "./components/test.jsx";
import reportWebVitals from "./reportWebVitals";
const root = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<TestComponent />
</React.StrictMode>,
root
);
reportWebVitals();
test.jsx
import React, { Component } from "react";
export default class TestComponent extends Component {
onRectMouseOver = (e) => {
console.log(`${e.clientX} ${e.clientY}`);
};
render() {
const rects = JSON.parse(
"[[0, 45, 45, 2370], [1955, 45, 45, 2370], [355, 45, 45, 2370], [710, 45, 45, 2370], [1065, 45, 45, 2370], [1420, 45, 45, 2370], [1775, 45, 45, 2370], [45, 845, 310.0, 45], [45, 1645, 310.0, 45], [400, 845, 310.0, 45], [400, 1645, 310.0, 45], [755, 845, 310.0, 45], [755, 1645, 310.0, 45], [1110, 845, 310.0, 45], [1110, 1645, 310.0, 45], [1465, 845, 310.0, 45], [1465, 1645, 310.0, 45], [1820, 845, 135.0, 45], [1820, 1645, 135.0, 45], [0, 2415, 2000, 45], [0, 0, 2000, 45]]"
);
return (
<div style={{ width: 5000, height: 3000, backgroundColor: "grey" }}>
<svg
viewBox="0 0 5000 3000"
xmlns="http://www.w3.org/2000/svg"
transform="scale(1,-1)"
>
<g>
{rects &&
rects.map((rect, index) => {
let [x, y, width, height] = rect;
return (
<rect
x={x}
y={y}
width={width}
height={height}
key={index}
style={{ fill: "None", strokeWidth: 1, stroke: "black" }}
onm ouseOver={(e) => {
this.onRectMouseOver(e);
}}
>
<title>
x: {x}; y: {x}; width: {width}; height:
{height}
</title>
</rect>
);
})}
</g>
</svg>
</div>
);
}
}
CodePudding user response:
I am keen to believe this is a Chrome broswer bug.
It is working on Firefox - 103.0.2 (64-bit).
CodePudding user response:
Since you were using fill: none
(and the default value of pointer-events property), only the borders will ever trigger an event, and not the interior. And each of your rectangles has very narrow strokes, only 1px wide (might occupy 2px because of "1px border blur"*), so if you move very fast, it's possible to skip more than just 3rd and 4th rectangles (in fact it's not that hard to skip most of the rectangles).
You can see how easy it is to miss the rectangles, e.g. try to use right-click and inspect, or use "Select an element (Ctrl Shift C)" of in the Chrome DevTools.
I can reproduce the effect on Firefox as well, by moving the mouse very fast (not limited to 3rd or 4th rectangles).
(*by "1px border blur", I mean, e.g. the 3B of https://vecta.io/blog/5-most-common-problems-faced-by-svg-users 3B. And there is a similar problem with canvas: https://usefulangle.com/post/17/html5-canvas-drawing-1px-crisp-straight-lines )
Also, just using Chrome, I managed to trigger your third and the fourth vertical rectangles simply by using super-slow horizontal movements of the mouse. So, I guess the actual problem is that your mouse pointer can move more than 2px per unit of time, so it's easy to "jump" over the border without triggering anything. I think the browsers worked as they should.
Work around 1:
One possible work around is by using fill: transparent
(which is the same as fill: rgba(0,0,0,0)
), so the interior can trigger mouse events. Then it's not that easy to miss a rectangle.
(There may be side effects, though, since the sensitive areas are larger now).
[edit: one side effect is that the rendering is more expensive, as it would have to paint over the interior, and then the "opacity" means that you calculate the result by combining the colors of current object and the colors of the background, even though it has no visible effect due to 0 opacity (i.e. output is the same as just background)]
Work around 2: Change pointer-events css property.
Here pointer-events: fill
works even though the fill is none
(I used to wonder if it was intentional, as I thought with fill: none
there should be not any 'fill' to trigger any pointer event. But I found from the description in https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events that it's intentional)
And it works both with Chrome and Firefox.
You may also change it to pointer-events: visible
if you also want the border to trigger events (instead of just the interior), but then there is not much difference when your borders are very thin.
(There is also pointer-events: all
but then it may be surprising when visibility is hidden)