Fix 'pan event being cancelled by the browser' bug

This commit is contained in:
Catalin Constantin Mititiuc 2025-06-16 22:41:28 -07:00
parent 363b551068
commit 18bb0b885a
3 changed files with 81 additions and 76 deletions

View File

@ -101,45 +101,6 @@
</p> </p>
</template> </template>
<div id="debug">
<fieldset name="point">
<legend>hex</legend>
<div>
<label>translatex <input name="translateX" type="number" value="0" /></label>
<label>translatey <input name="translateY" type="number" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scale <input name="scale" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="points">
<legend>grid</legend>
<div>
<label>translatex <input name="translateX" type="number" value="0" /></label>
<label>translatey <input name="translateY" type="number" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scale <input name="scale" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="map2">
<legend>map2</legend>
<div>
<label>translatex <input name="translateX" type="number" value="0" /></label>
<label>translatey <input name="translateY" type="number" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scale <input name="scale" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="map3">
<legend>map3</legend>
<div>
<label>translatex <input name="translateX" type="number" value="0" /></label>
<label>translatey <input name="translateY" type="number" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scale <input name="scale" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
</div>
<div id="map-container"> <div id="map-container">
<div id="toggle-firing-arc-vis"> <div id="toggle-firing-arc-vis">
<div>davion</div> <div>davion</div>
@ -147,6 +108,50 @@
<div>liao</div> <div>liao</div>
<input type="checkbox" data-allegiance="liao" /> <input type="checkbox" data-allegiance="liao" />
</div> </div>
<div id="panel">
<fieldset name="point">
<legend>hex</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.1" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="points">
<legend>grid</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.1" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.1" value="1" /></label>
</div>
</fieldset>
<fieldset name="map2">
<legend>map2</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.001" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.001" value="1" /></label>
</div>
</fieldset>
<fieldset name="map3">
<legend>map3</legend>
<div>
<label>translateX <input name="translateX" type="number" step="0.1" value="0" /></label>
<label>translateY <input name="translateY" type="number" step="0.1" value="0" /></label>
<label>rotate <input name="rotate" type="number" step="0.1" value="0" /></label>
<label>scaleX <input name="scaleX" type="number" step="0.001" value="1" /></label>
<label>scaleY <input name="scaleY" type="number" step="0.001" value="1" /></label>
</div>
</fieldset>
</div>
<svg viewbox="-49 -40 2390 3163" xmlns="http://www.w3.org/2000/svg"> <svg viewbox="-49 -40 2390 3163" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<polygon id="point" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5" /> <polygon id="point" points="0,10 8.66,5 8.66,-5 0,-10 -8.66,-5 -8.66,5" />
@ -197,8 +202,8 @@
<rect id="background" x="-1" y="-1" width="2287" height="3087" /> <rect id="background" x="-1" y="-1" width="2287" height="3087" />
<g id="image-maps"> <g id="image-maps">
<image id="map2" class="map-scans" href="scans/map2.jpg" width="2284" height="1518" x="2" y="2" /> <image id="map2" class="map-scans" href="scans/map2.jpg" width="2284" height="1518" />
<image id="map3" class="map-scans" href="scans/map3.jpg" width="2284" height="1518" x="4" y="1564" /> <image id="map3" class="map-scans" href="scans/map3.jpg" width="2284" height="1518" />
</g> </g>
<g id="firing-arcs"> <g id="firing-arcs">
@ -217,8 +222,6 @@
<use href="#n2" x="19" y="-40" opacity="0.5" /> <use href="#n2" x="19" y="-40" opacity="0.5" />
<use href="#n3" x="36" y="-40" opacity="0.5" /> <use href="#n3" x="36" y="-40" opacity="0.5" />
<use href="#n4" x="54" y="-40" opacity="0.5" /> <use href="#n4" x="54" y="-40" opacity="0.5" />
<!-- <rect id="debug-view-box" x="-100" y="-100" width="3400" height="4500" /> -->
</svg> </svg>
<div id="status"> <div id="status">

View File

@ -294,10 +294,11 @@ if (recVis == 'false') {
let info = document.getElementById('status'); let info = document.getElementById('status');
// Object.values(settingsPanel.querySelectorAll('fieldset')).forEach(fieldset => { Object.values(settingsPanel.querySelectorAll('fieldset')).forEach(fieldset => {
[].forEach(fieldset => { // [].forEach(fieldset => {
const identityMtx = [1, 0, 0, 1, 0, 0];
const target = document.getElementById(fieldset.name); const target = document.getElementById(fieldset.name);
const transform = getComputedStyle(target).transform.match(/-?\d+\.?\d*/g); const transform = getComputedStyle(target).transform.match(/-?\d+\.?\d*/g) || identityMtx;
const inputs = fieldset.querySelectorAll('input'); const inputs = fieldset.querySelectorAll('input');
if (transform) { if (transform) {
@ -310,7 +311,8 @@ let info = document.getElementById('status');
const scaleY = Math.sqrt(b**2 + d**2); const scaleY = Math.sqrt(b**2 + d**2);
let values = { let values = {
scale: Math.round(scaleX * 10) / 10, scaleX: Math.round(scaleX * 1000) / 1000,
scaleY: Math.round(scaleY * 1000) / 1000,
translateX: e, translateX: e,
translateY: f, translateY: f,
rotate: Math.round(radToDeg((Math.acos(a / scaleX) + Math.asin(b / scaleY)) / 2) * 10) / 10 rotate: Math.round(radToDeg((Math.acos(a / scaleX) + Math.asin(b / scaleY)) / 2) * 10) / 10
@ -323,12 +325,13 @@ let info = document.getElementById('status');
input.addEventListener('pointerenter', e => e.target.focus()); input.addEventListener('pointerenter', e => e.target.focus());
input.addEventListener('input', e => { input.addEventListener('input', e => {
let { scale, translateX, translateY, rotate} = Object.values(inputs).reduce((acc, input) => { let { translateX, translateY, rotate, scaleX, scaleY } =
Object.values(inputs).reduce((acc, input) => {
acc[input.name] = input.value; acc[input.name] = input.value;
return acc; return acc;
}, {}); }, {});
let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scale})`; let transform = `translate(${translateX}px, ${translateY}px) rotate(${rotate}deg) scale(${scaleX}, ${scaleY})`;
target.style.transform = transform; target.style.transform = transform;
}); });
@ -888,11 +891,14 @@ svg.addEventListener('wheel', e => {
let vb = `${newX} ${newY} ${newWidth} ${newHeight}` let vb = `${newX} ${newY} ${newWidth} ${newHeight}`
// TODO attribute change event?
localStorage.setItem('viewBox', vb); localStorage.setItem('viewBox', vb);
svg.setAttributeNS(null, 'viewBox', vb); svg.setAttributeNS(null, 'viewBox', vb);
}); });
svg.addEventListener('pointerdown', e => { svg.addEventListener('pointerdown', e => {
e.preventDefault();
const minPanDistanceThreshold = 5; const minPanDistanceThreshold = 5;
let dist, ctm, let dist, ctm,
@ -904,16 +910,19 @@ svg.addEventListener('pointerdown', e => {
function pointerMove(e) { function pointerMove(e) {
movePt.x = e.clientX; movePt.x = e.clientX;
movePt.y = e.clientY; movePt.y = e.clientY;
if (!pan) {
dist = Math.sqrt((movePt.x - startPt.x)**2 + (movePt.y - startPt.y)**2); dist = Math.sqrt((movePt.x - startPt.x)**2 + (movePt.y - startPt.y)**2);
if (!pan && dist >= minPanDistanceThreshold) { if (dist >= minPanDistanceThreshold) {
pan = true; pan = true;
svg.setPointerCapture(e.pointerId);
startPt.x = e.clientX; startPt.x = e.clientX;
startPt.y = e.clientY; startPt.y = e.clientY;
} }
}
if (pan) { if (pan) {
svg.setPointerCapture(e.pointerId);
ctm = svg.getScreenCTM().inverse(); ctm = svg.getScreenCTM().inverse();
const [svgStartPt, svgMovePt] = [startPt, movePt].map(p => p.matrixTransform(ctm)), const [svgStartPt, svgMovePt] = [startPt, movePt].map(p => p.matrixTransform(ctm)),
@ -928,7 +937,6 @@ svg.addEventListener('pointerdown', e => {
} }
function pointerUp(e) { function pointerUp(e) {
svg.releasePointerCapture(e.pointerId);
svg.removeEventListener('pointermove', pointerMove); svg.removeEventListener('pointermove', pointerMove);
svg.removeEventListener('pointerup', pointerUp); svg.removeEventListener('pointerup', pointerUp);
} }

View File

@ -24,6 +24,10 @@ svg image.map-scans {
image-rendering: auto; image-rendering: auto;
} }
svg text {
user-select: none;
}
div#status { div#status {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -159,19 +163,17 @@ g#grid {
} }
#map2 { #map2 {
transform: rotate(0.1deg); transform-origin: 0px 0px;
transform: translate(-0.9px, -2.4px) scale(0.999, 1.007);
} }
#map3 { #map3 {
transform: rotate(0.1deg); transform-origin: 0px 0px;
/* transform: translateY(1518px); */
transform: translate(1.3px, 1564.1px) rotate(0.1deg) scale(0.999, 1.002);
} }
image.map-scans { #panel {
/* transform: scale(1.39, 1.407) rotate(0.07deg); */
/* opacity: 0.33; */
}
#debug {
display: none; display: none;
position: absolute; position: absolute;
right: 0; right: 0;
@ -180,7 +182,7 @@ image.map-scans {
padding: 2px; padding: 2px;
} }
#debug fieldset label { #panel fieldset label {
display: block; display: block;
text-align: right; text-align: right;
} }
@ -259,14 +261,6 @@ text.counter, #troop-counter text {
user-select: none; user-select: none;
} }
rect#map {
fill: black;
opacity: 0;
/* fill-opacity: 0;
stroke: red;
stroke-width: 4px; */
}
polygon.firing-arc[data-troop-allegiance="davion"] { polygon.firing-arc[data-troop-allegiance="davion"] {
fill: red; fill: red;
} }