Image zoom and pan with edge detection

26 views Asked by At

I'm doing this with angular but the question is more generic. I have a div (the container) that is 500x500px (with a blue background). I have a wider zoomable image that is at least (at page load) the same size of the container. When the image has been zoomed the pan gesture is available. The problem is related to control the pan behavior in order to be bounded to the container. The container blue background should never be visible! In other words when one of the image borders overlaps with one of the container border the pan movement should stop. Imho it's not a "fix my code" question type... I struggle with this problem from a matematical point of view.

import { Component, ElementRef, ViewChild } from '@angular/core';
import { fundusImg } from './img';

@Component({
  selector: 'gv-test',
  standalone: true,
  template: `
    <h1>Gio image {{offsetX}}</h1>
    
    <div id="image-container" #imageContainer 
        (mousedown)="startPan($event)" 
        (mouseup)="endPan()" 
        (wheel)="zoom($event)" 
        (mousemove)="pan($event)"
        (mouseleave)="endPan()">
      <img id="zoom-image" #zoomImage [src]="img" alt="img to zoom and pan">
    </div>
    
  `,
  styles: `
    #image-container {
      width: 500px;
      height: 500px;
      overflow: hidden;
      position: relative;
      border: 1px solid grey;
      cursor: move;

      background-color: blue;
    }

    #zoom-image {
      width: 100%;
      height: 100%;
      position: absolute;
      position: absolute;
    }
`
})

export class TestImgComponent {
  img = fundusImg; 
  
  // ZOOM
  scale = 1;
  zoomMax = 10;
  zoomStep = -0.002;

  // Pan 
  isPanning = false;
  startX = 0;
  startY = 0;
  offsetX = 0;
  offsetY = 0;

  @ViewChild('imageContainer', {read: ElementRef, static: true}) imageContainer!: ElementRef;
  @ViewChild('zoomImage', {read: ElementRef, static: true}) zoomImage!: ElementRef;

  zoom(event: WheelEvent): void {
      event.preventDefault();
      this.scale += event.deltaY * this.zoomStep;
      
      this.scale = +Math.min(Math.max(1, this.scale), this.zoomMax).toFixed(1); 
      
      this.zoomImage.nativeElement.style.transform = `scale(${this.scale})`;
      this.zoomImage.nativeElement.style.transition = `transform 0.5s ease`;
  }

  
  startPan(event: MouseEvent) {
    event.preventDefault();
    this.isPanning = true;
    this.startX = event.clientX - this.offsetX * this.scale;
    this.startY = event.clientY - this.offsetY * this.scale;
    this.zoomImage.nativeElement.style.transition = `none`;
  }

  endPan(): void {
    this.isPanning = false; 
  }

  pan(event: MouseEvent) {
    if (this.isPanning && this.scale > 1) {
      
      this.offsetX = (event.clientX - this.startX) / this.scale;
      this.offsetY = (event.clientY - this.startY) / this.scale;

      this.zoomImage.nativeElement.style.transform = `scale(${this.scale}) translate(${this.offsetX}px, ${this.offsetY}px)`;
    }
  }
}
0

There are 0 answers