Fix canvas coordinate mismatch and handle jump-on-grab in Grid_Setup
- Canvas width now read via getBoundingClientRect after setting style.width=100%, avoiding the parentElement.clientWidth padding issue that made css_w exceed the actual rendered width and broke hit-testing - All handle drags (corners + midpoints) now use relative delta via drag_prev_img instead of absolute cursor position, preventing handle teleport on grab Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,32 +45,37 @@ export class Grid_Setup {
|
|||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
this.#img = img;
|
this.#img = img;
|
||||||
const max_w = this.#canvas.parentElement.clientWidth || 800;
|
|
||||||
const max_h = Math.floor(window.innerHeight * 0.65);
|
|
||||||
|
|
||||||
// Canvas fills the available space
|
// Let CSS determine the width, then read back the actual rendered value.
|
||||||
this.#css_w = max_w;
|
// Using parentElement.clientWidth directly would include the parent's
|
||||||
this.#css_h = max_h;
|
// padding, causing css_w to exceed the real content area and making
|
||||||
|
// getBoundingClientRect() return a different width than css_w.
|
||||||
|
this.#canvas.style.width = '100%';
|
||||||
|
const css_w = this.#canvas.getBoundingClientRect().width || 800;
|
||||||
|
const css_h = Math.floor(window.innerHeight * 0.65);
|
||||||
|
|
||||||
|
this.#css_w = css_w;
|
||||||
|
this.#css_h = css_h;
|
||||||
|
|
||||||
// Scale: fit image within canvas with slight padding
|
// Scale: fit image within canvas with slight padding
|
||||||
this.#scale = Math.min(
|
this.#scale = Math.min(
|
||||||
(max_w * 0.9) / img.width,
|
(css_w * 0.9) / img.width,
|
||||||
(max_h * 0.9) / img.height,
|
(css_h * 0.9) / img.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
this.#canvas.width = this.#css_w * dpr;
|
this.#canvas.width = css_w * dpr;
|
||||||
this.#canvas.height = this.#css_h * dpr;
|
this.#canvas.height = css_h * dpr;
|
||||||
this.#canvas.style.width = this.#css_w + 'px';
|
this.#canvas.style.width = css_w + 'px';
|
||||||
this.#canvas.style.height = this.#css_h + 'px';
|
this.#canvas.style.height = css_h + 'px';
|
||||||
this.#ctx.scale(dpr, dpr);
|
this.#ctx.scale(dpr, dpr);
|
||||||
|
|
||||||
// Camera: start centered, image fitted within canvas
|
// Camera: start centered, image fitted within canvas
|
||||||
const img_w = img.width * this.#scale;
|
const img_w = img.width * this.#scale;
|
||||||
const img_h = img.height * this.#scale;
|
const img_h = img.height * this.#scale;
|
||||||
this.#cam_z = 1;
|
this.#cam_z = 1;
|
||||||
this.#cam_x = (max_w - img_w) / 2;
|
this.#cam_x = (css_w - img_w) / 2;
|
||||||
this.#cam_y = (max_h - img_h) / 2;
|
this.#cam_y = (css_h - img_h) / 2;
|
||||||
|
|
||||||
// Default corners: 15% inset in image coords
|
// Default corners: 15% inset in image coords
|
||||||
const mx = img.width * 0.15;
|
const mx = img.width * 0.15;
|
||||||
@@ -159,9 +164,7 @@ export class Grid_Setup {
|
|||||||
const hit = this.#find_handle(sp);
|
const hit = this.#find_handle(sp);
|
||||||
if (hit !== -1) {
|
if (hit !== -1) {
|
||||||
this.#drag_idx = hit;
|
this.#drag_idx = hit;
|
||||||
if (hit >= 4) {
|
this.#drag_prev_img = this.#world_to_img(this.#to_world(sp));
|
||||||
this.#drag_prev_img = this.#world_to_img(this.#to_world(sp));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.#panning = true;
|
this.#panning = true;
|
||||||
this.#pan_last = sp;
|
this.#pan_last = sp;
|
||||||
@@ -173,16 +176,17 @@ export class Grid_Setup {
|
|||||||
const sp = this.#screen_pos(e);
|
const sp = this.#screen_pos(e);
|
||||||
if (this.#drag_idx !== -1) {
|
if (this.#drag_idx !== -1) {
|
||||||
const img_pos = this.#world_to_img(this.#to_world(sp));
|
const img_pos = this.#world_to_img(this.#to_world(sp));
|
||||||
|
const dx = img_pos.x - this.#drag_prev_img.x;
|
||||||
|
const dy = img_pos.y - this.#drag_prev_img.y;
|
||||||
if (this.#drag_idx < 4) {
|
if (this.#drag_idx < 4) {
|
||||||
this.#corners[this.#drag_idx] = img_pos;
|
const i = this.#drag_idx;
|
||||||
|
this.#corners[i] = { x: this.#corners[i].x + dx, y: this.#corners[i].y + dy };
|
||||||
} else {
|
} else {
|
||||||
const [a, b] = Grid_Setup.#MIDPOINT_EDGES[this.#drag_idx - 4];
|
const [a, b] = Grid_Setup.#MIDPOINT_EDGES[this.#drag_idx - 4];
|
||||||
const dx = img_pos.x - this.#drag_prev_img.x;
|
|
||||||
const dy = img_pos.y - this.#drag_prev_img.y;
|
|
||||||
this.#corners[a] = { x: this.#corners[a].x + dx, y: this.#corners[a].y + dy };
|
this.#corners[a] = { x: this.#corners[a].x + dx, y: this.#corners[a].y + dy };
|
||||||
this.#corners[b] = { x: this.#corners[b].x + dx, y: this.#corners[b].y + dy };
|
this.#corners[b] = { x: this.#corners[b].x + dx, y: this.#corners[b].y + dy };
|
||||||
this.#drag_prev_img = img_pos;
|
|
||||||
}
|
}
|
||||||
|
this.#drag_prev_img = img_pos;
|
||||||
this.#draw();
|
this.#draw();
|
||||||
} else if (this.#panning) {
|
} else if (this.#panning) {
|
||||||
this.#cam_x += sp.x - this.#pan_last.x;
|
this.#cam_x += sp.x - this.#pan_last.x;
|
||||||
|
|||||||
Reference in New Issue
Block a user