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();
|
||||
img.onload = () => {
|
||||
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
|
||||
this.#css_w = max_w;
|
||||
this.#css_h = max_h;
|
||||
// Let CSS determine the width, then read back the actual rendered value.
|
||||
// Using parentElement.clientWidth directly would include the parent's
|
||||
// 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
|
||||
this.#scale = Math.min(
|
||||
(max_w * 0.9) / img.width,
|
||||
(max_h * 0.9) / img.height,
|
||||
(css_w * 0.9) / img.width,
|
||||
(css_h * 0.9) / img.height,
|
||||
);
|
||||
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
this.#canvas.width = this.#css_w * dpr;
|
||||
this.#canvas.height = this.#css_h * dpr;
|
||||
this.#canvas.style.width = this.#css_w + 'px';
|
||||
this.#canvas.style.height = this.#css_h + 'px';
|
||||
this.#canvas.width = css_w * dpr;
|
||||
this.#canvas.height = css_h * dpr;
|
||||
this.#canvas.style.width = css_w + 'px';
|
||||
this.#canvas.style.height = css_h + 'px';
|
||||
this.#ctx.scale(dpr, dpr);
|
||||
|
||||
// Camera: start centered, image fitted within canvas
|
||||
const img_w = img.width * this.#scale;
|
||||
const img_h = img.height * this.#scale;
|
||||
this.#cam_z = 1;
|
||||
this.#cam_x = (max_w - img_w) / 2;
|
||||
this.#cam_y = (max_h - img_h) / 2;
|
||||
this.#cam_x = (css_w - img_w) / 2;
|
||||
this.#cam_y = (css_h - img_h) / 2;
|
||||
|
||||
// Default corners: 15% inset in image coords
|
||||
const mx = img.width * 0.15;
|
||||
@@ -159,9 +164,7 @@ export class Grid_Setup {
|
||||
const hit = this.#find_handle(sp);
|
||||
if (hit !== -1) {
|
||||
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 {
|
||||
this.#panning = true;
|
||||
this.#pan_last = sp;
|
||||
@@ -173,16 +176,17 @@ export class Grid_Setup {
|
||||
const sp = this.#screen_pos(e);
|
||||
if (this.#drag_idx !== -1) {
|
||||
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) {
|
||||
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 {
|
||||
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[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();
|
||||
} else if (this.#panning) {
|
||||
this.#cam_x += sp.x - this.#pan_last.x;
|
||||
|
||||
Reference in New Issue
Block a user