The question IS: What is causing the event.offsetX
& event.offsetY
to SOMETIMES have a value +/-10 than what they should be. Things I have observed that affect this are:
- Passing through certain areas of an SVG.
- Preforming a
console.log(event.offsetX)
can remediate this depending on if it’s performed before or after other lines.
I do not know why either of these should have any impact. I have never experienced any other behavior like this.
The question IS NOT / answers ARE NOT:
- Why am I deleting and recreating an element? Both because I can, and this code is to just demonstrate the problem.
- How can I perform this differently. I am not looking for alternate solutions. I know how to do things differently. I am trying to understand what is causing the undesired results in THIS code.
- My viewbox is not set to the same size as the SVG element and I need to use a certain preserveAspectRatio setting. No I do not. There is no requirement for either of these. They are certainly allowed to be different.
- My SVG pointer does not line up with the HTML cursor. I know; that is not what I want it to do. I do not need answers showing me how to convert a HTML point to a SVG point. I know how to do this this is not what I am looking for.
- I need to use getCTM()/getSreecCTM(). No I do not. Those do not provide what I am looking to do, and are irrelevant to the question.
- This is working for some people. If you are getting some completely different error, I cannot help you with your device.
- This is not an SVG animation problem. Logging/debugging can show the offsetX & offsetY values sometimes have a value of +/-10 of what they should be.
I was working on an application to track movement in an SVG viewbox relative to the size of the SVG element itself. The sample code below has been reduced to remove any extra functions, variables, calculations, etc. as much as possible to demonstrate just the problem.
When moving the mouse cursor across the SVG, the green (purple & yellow edits added per comments) pointer tracker its x & y positions by 10px each when moving through the approximate zones marked by the red bands. Also moving left to right vs. right to left has slightly different trigger points.
const svg = document.getElementById('svg1');
svg.addEventListener('pointermove', event => {
// console.log(event.offsetX);
const id1 = 'pointer1';
const id2 = 'pointer2';
const id3 = 'pointer3';
const pointerOld1 = document.getElementById(id1);
if (pointerOld1) pointerOld1.remove();
const pointerOld2 = document.getElementById(id2);
if (pointerOld2) pointerOld2.remove();
const x = event.offsetX;
const y = event.offsetY;
const pointerNew1 = document.createElementNS(svg.namespaceURI, 'circle');
pointerNew1.setAttributeNS(null, 'id', id1);
pointerNew1.setAttributeNS(null, 'cx', x * 0.25);
pointerNew1.setAttributeNS(null, 'cy', y * 0.5);
pointerNew1.setAttributeNS(null, 'r', '15px');
pointerNew1.setAttributeNS(null, 'stroke', 'green');
pointerNew1.setAttributeNS(null, 'stroke-width', '5px');
pointerNew1.setAttributeNS(null, 'fill', 'none');
svg.append(pointerNew1);
const point = new DOMPoint(x, y).matrixTransform(svg.getCTM().inverse());
const pointerNew2 = document.createElementNS(svg.namespaceURI, 'circle');
pointerNew2.setAttributeNS(null, 'id', id2);
pointerNew2.setAttributeNS(null, 'cx', point.x);
pointerNew2.setAttributeNS(null, 'cy', point.y);
pointerNew2.setAttributeNS(null, 'r', '15px');
pointerNew2.setAttributeNS(null, 'stroke', 'purple');
pointerNew2.setAttributeNS(null, 'stroke-width', '5px');
pointerNew2.setAttributeNS(null, 'fill', 'none');
svg.append(pointerNew2);
const pointer3 = document.getElementById(id3);
if (!pointer3) {
const pointer3 = document.createElementNS(svg.namespaceURI, 'circle');
pointer3.setAttributeNS(null, 'id', id3);
pointer3.setAttributeNS(null, 'r', '10px');
pointer3.setAttributeNS(null, 'stroke', 'yellow');
pointer3.setAttributeNS(null, 'stroke-width', '5px');
pointer3.setAttributeNS(null, 'fill', 'none');
svg.append(pointer3);
}
else {
pointer3.setAttributeNS(null, 'cx', x * 0.25);
pointer3.setAttributeNS(null, 'cy', y * 0.5);
}
});
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100" width="400" height="200"
style="border: 2px solid;">
<line x1="0" y1="0" x2="100px" y2="100" stroke="blue" />
<line x1="100" y1="0" x2="0" y2="100" stroke="blue" />
<rect x="14" y="0" width="1" height="100" fill="red"></rect>
<rect x="16" y="0" width="1" height="100" fill="red"></rect>
<rect x="18" y="0" width="1" height="100" fill="red"></rect>
<rect x="20" y="0" width="1" height="100" fill="red"></rect>
<rect x="22" y="0" width="12" height="100" fill="red"></rect>
<rect x="75" y="0" width="1" height="100" fill="red"></rect>
<rect x="77" y="0" width="12" height="100" fill="red"></rect>
</svg>
I found some “work-arounds” including:
- If I uncomment the
console.log(event.offsetX);
line, it performs fine. - If I move the x & y constant declarations before the
if (pointerOld) pointerOld.remove();
line, it also works fine.
However, I am trying to understand why it is behaving like this in the first place? Am I doing something wrong? Or is this a bug (in HTML/JavaScript/SVG/web browser/OS)?
15
First of all, like herrstrietzel write in the comment, the coordinate system for the SVG is different from the HTML page. Use the screenToSVG()
function mentioned here.
Second, you don’t need to create a new circle element on each event call back. Reuse the one that is already there.
const pointerMove = event => {
let svg = event.target.closest('svg');
let pointer = svg.querySelector('.pointer');
let x = event.clientX;
let y = event.clientY;
let coordinates = screenToSVG(svg, x, y);
pointer.setAttribute('cx', coordinates.x);
pointer.setAttribute('cy',coordinates.y);
}
let svg1 = document.getElementById('svg1');
svg1.addEventListener('pointermove', pointerMove);
function screenToSVG(svg, screenX, screenY) {
var p = svg.createSVGPoint();
p.x = screenX;
p.y = screenY;
return p.matrixTransform(svg.getScreenCTM().inverse());
}
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100" width="400" height="200" style="border: 2pt solid;">
<line x1="0" y1="0" x2="100px" y2="100" stroke="blue" />
<line x1="100" y1="0" x2="0" y2="100" stroke="blue" />
<rect x="14" y="0" width="1" height="100" fill="red"></rect>
<rect x="16" y="0" width="1" height="100" fill="red"></rect>
<rect x="18" y="0" width="1" height="100" fill="red"></rect>
<rect x="20" y="0" width="1" height="100" fill="red"></rect>
<rect x="22" y="0" width="12" height="100" fill="red"></rect>
<rect x="75" y="0" width="1" height="100" fill="red"></rect>
<rect x="77" y="0" width="12" height="100" fill="red"></rect>
<circle class="pointer" r="15" stroke="green" stroke-width="5" fill="none"/>
</svg>
5