Enlcxx / angular2-resizing-cropping-image

Resize, rotate and crop images(cropper) for Angular 8
https://alyle.io/components/image-cropper
32 stars 6 forks source link

Image Cropper on load image, the image is not centered #22

Open Luhari opened 4 years ago

Luhari commented 4 years ago

Hi, I've added your Image Cropper to my project and implemented it almost identically than the example found in the documentation.

The difference is that instead of getting the image from an URL, I'm retrieving it from an input. Therefore i'm using the method selectInputEvent.

Everything works fine, except for one thing that is, as the title describes, once loaded the image, it is not centered.

I'm not sure if it's really a bug from the library or mine. This is what I get when i load an image in the cropper: image

But when I interact with it, the image centers by itfelf. (The next image is after clicking the button to center the image) image

I tried to force the centering using cropping.center() at triggering load. But it still behaves the same way.

Here is my code:

// Component
export class ImageCropperComponent implements AfterViewInit {
  classes = this.theme.addStyleSheet(styles);
  croppedImage?: string;
  result: string;
  scale: number;
  @ViewChild(LyImageCropper, { static: false }) cropper: LyImageCropper;
  @Input() image: any;
  @Output() imageCropped: EventEmitter<any> = new EventEmitter();
  @Output() close: EventEmitter<any> = new EventEmitter();
  myConfig: ImgCropperConfig = {
    autoCrop: false, // Default `false`
    width: 250, // Default `250`
    height: 250, // Default `200`
    fill: '#ff2997', // Default transparent if type = png else #000
    type: 'image/png' // Or you can also use `image/jpeg`
  };

  constructor(private theme: LyTheme2) {}

  ngAfterViewInit() {
    if (Platform.isBrowser) {
      const config = {
        scale: 0.745864772531767,
        position: {
          x: 642.380608078103,
          y: 236.26357452128866
        }
      };
      console.log(this.image, this.cropper);
      this.cropper.selectInputEvent(this.image);
    }
  }

  onCropped(e: ImgCropperEvent) {
    this.croppedImage = e.dataURL;
    this.imageCropped.emit(e.dataURL);
    console.log('cropped img: ', e);
  }
  onloaded(e: ImgCropperEvent) {
    console.log('img loaded', e);
  }
  onerror(e: ImgCropperErrorEvent) {
    console.warn(`'${e.name}' is not a valid image`, e);
  }

  closeCropper() {
    this.close.emit();
  }
}

// html

<div class="buttons-container" *ngIf="cropping.isLoaded">
  <button (click)="cropping.zoomIn()" ly-button appearance="icon">
    <ly-icon>zoom_in</ly-icon>
  </button>
  <button (click)="cropping.zoomOut()" ly-button appearance="icon">
    <ly-icon>zoom_out</ly-icon>
  </button>
  <button (click)="cropping.center()" ly-button appearance="icon">
    <ly-icon>filter_center_focus</ly-icon>
  </button>
  <button (click)="cropping.rotate(-90)" ly-button appearance="icon">
    <ly-icon>rotate_90_degrees_ccw</ly-icon>
  </button>
  <button (click)="cropping.fit()" ly-button>Fit</button>
  <button (click)="cropping.fitToScreen()" ly-button>Fit to screen</button>
  <button (click)="cropping.setScale(1)" ly-button>1:1</button>
  <button (click)="closeCropper()" ly-button>Close</button>
</div>

<ly-img-cropper
  [withClass]="classes.cropping"
  #cropping
  [config]="myConfig"
  [(scale)]="scale"
  (cropped)="onCropped($event)"
  (loaded)="cropping.center(); onloaded($event)"
  (error)="onerror($event)"
>
  <span>Drag and drop image</span>
</ly-img-cropper>

<div class="bottom">
  <div *ngIf="cropping.isLoaded" [className]="classes.range">
    <ly-slider
      [thumbVisible]="false"
      [min]="cropping.minScale"
      [max]="1"
      [(ngModel)]="scale"
      (input)="scale = $event.value"
      step="0.000001"
      [color]="'#000000'"
    ></ly-slider>
  </div>

  <button
    *ngIf="cropping.isLoaded"
    (click)="cropping.crop()"
    ly-button
    [color]="'#000000'"
  >
    <ly-icon>crop</ly-icon>crop
  </button>

  <div><img *ngIf="cropping.isCropped" [src]="croppedImage" /></div>
</div>
Enlcxx commented 4 years ago

Hello, I need more information, like which version of Angular and Alyle UI are you using? What browser are you using?

Are you using it in a dialog box?

Luhari commented 4 years ago

Hi, the answer are the following:

ng version:

Node: 12.16.0
OS: win32 x64
Angular: 8.2.9
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.803.8
@angular-devkit/build-angular     0.803.23
@angular-devkit/build-optimizer   0.803.23
@angular-devkit/build-webpack     0.803.23
@angular-devkit/core              8.3.8
@angular-devkit/schematics        8.3.8
@angular/cdk                      8.2.3
@angular/cli                      8.3.8
@angular/http                     7.2.15
@angular/material                 8.2.3
@angular/pwa                      0.900.7
@angular/service-worker           8.2.14
@ngtools/webpack                  8.3.23
@schematics/angular               8.3.8
@schematics/update                0.803.8
rxjs                              6.5.4
typescript                        3.5.3
webpack                           4.39.2

Here's the package.json:

{
  "name": "project",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve --host 0.0.0.0",
    "dev": "ng serve --host 0.0.0.0 --configuration es5",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@alyle/ui": "^3.1.2",
    "@angular/animations": "~8.2.5",
    "@angular/cdk": "~8.2.3",
    "@angular/common": "~8.2.5",
    "@angular/compiler": "~8.2.5",
    "@angular/core": "~8.2.5",
    "@angular/forms": "^8.2.9",
    "@angular/http": "~7.2.15",
    "@angular/material": "^8.2.3",
    "@angular/platform-browser": "~8.2.5",
    "@angular/platform-browser-dynamic": "~8.2.5",
    "@angular/pwa": "^0.900.7",
    "@angular/router": "~8.2.5",
    "@angular/service-worker": "~8.2.5",
    "@ngrx/effects": "~8.3.0",
    "@ngrx/router-store": "~8.3.0",
    "@ngrx/store": "~8.3.0",
    "@ngx-translate/core": "~11.0.1",
    "@ngx-translate/http-loader": "~4.0.0",
    "bootstrap": "~4.3.1",
    "chart.js": "^2.9.3",
    "cropperjs": "^1.5.6",
    "hammerjs": "^2.0.8",
    "ngx-image-cropper": "^3.1.3",
    "rxjs": "^6.5.4",
    "tslib": "~1.10.0",
    "zone.js": "~0.9.1"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^0.803.23",
    "@angular/cli": "~8.3.4",
    "@angular/compiler-cli": "~8.2.5",
    "@types/jasmine": "~3.3.8",
    "@types/jasminewd2": "~2.0.3",
    "@types/node": "~8.9.4",
    "codelyzer": "~5.0.0",
    "jasmine-core": "~3.4.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~4.1.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~2.0.1",
    "karma-jasmine-html-reporter": "~1.4.0",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.15.0",
    "typescript": "~3.5.3"
  }
}

I did my testing in Google Chrome. And yes, im using it in a modal (just div tags that i display or not using a boolean).

I've been looking for another workaround and it seems that using cropper.center() in ngAfterViewChecked does work (using a boolean just to center it the first time, otherwise it locks the image at the center)

Although the image should centers itself, right? Without having to involve angular cicles.

Enlcxx commented 4 years ago

I see, what is happening is that the image is loading before it is rendered, and therefore it cannot calculate the position. What you can do is use setTimeout.

import { take } from 'rxjs/operators';

...
export class MyComponent {
  constructor(
    private _ngZone: NgZone,
  ) { }

  ngAfterViewInit() {
    if (Platform.isBrowser) {

      // Solution 1: Works with this
      window.setTimeout(() => this.cropper.selectInputEvent(this.image), 100);

      // Solution 2: I have not tried it but it should work
      Promise.resolve().then(() => this.cropper.selectInputEvent(this.image));

      // Solution 3: I have not tried it but it should work
      this._ngZone.onStable.pipe(
        take(1)
      ).subscribe(() => this.cropper.selectInputEvent(this.image));

    }
  }
}

This is a bug, I will try to fix it as soon as possible, but for now you can one of the solutions given.