ng-magnizoom
Version:
Angular image magnifier (based on HTML canvas)
332 lines (325 loc) • 15 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Component, Input, Output, ViewChild, NgModule } from '@angular/core';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
class MagnizoomService {
constructor() { }
}
/** @nocollapse */ MagnizoomService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MagnizoomService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
/** @nocollapse */ MagnizoomService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MagnizoomService, providedIn: 'root' });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: MagnizoomService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: function () { return []; } });
class NgMagnizoomComponent {
get canvasWidth() { return this.image && this.image.width || 800; }
get canvasHeight() { return this.image && this.image.height || 600; }
constructor() {
this.zoomMode = 'COVER';
this.minZoomFactor = 1.2;
this.maxZoomFactor = 3;
this.zoomFactor = 2;
this.zoomFactorChange = new EventEmitter();
this.lensSizeUnit = 'NORMALIZED';
this.lensSize = { width: 0.5, height: 0.5 };
this.zoomCenterUnit = 'NORMALIZED';
this.zoomCenterChange = new EventEmitter();
this.updateOnMouseEvents = true;
this.imageReady = false;
}
ngOnInit() {
this.initContext();
this.loadImage(this.imageSrc);
}
ngOnChanges(changes) {
if (!changes) {
return;
}
if (changes.lensSize || changes.zoomCenter || changes.zoomFactor) {
this.updateParameters();
}
if (changes.imageSrc && !changes.imageSrc.firstChange) {
this.loadImage(changes.imageSrc.currentValue);
}
}
initContext() {
this.canvas = this.mainCanvasRef.nativeElement;
this.context = this.canvas.getContext('2d');
}
loadImage(src) {
this.image = new Image();
this.image.onload = () => {
this.imageReady = true;
this.updateParameters();
setTimeout(() => this.update());
};
this.image.src = src;
}
updateParameters() {
if (this.lensSizeUnit === 'NORMALIZED') {
if (this.imageReady) {
this._lensSize = {
width: this.lensSize.width * this.image.width,
height: this.lensSize.height * this.image.height
};
}
}
else {
this._lensSize = {
width: this.lensSize.width,
height: this.lensSize.height
};
}
if (!this.zoomCenter) {
this._centerPosition = undefined;
}
else if (this.zoomCenterUnit === 'NORMALIZED') {
if (this.imageReady) {
this._centerPosition = {
x: this.zoomCenter.x * this.image.width,
y: this.zoomCenter.y * this.image.height
};
}
}
else {
this._centerPosition = {
x: this.zoomCenter.x,
y: this.zoomCenter.y
};
}
this._zoomFactor = this.zoomFactor;
if (this._zoomFactor > this.maxZoomFactor) {
this._zoomFactor = this.maxZoomFactor;
}
if (this._zoomFactor < this.minZoomFactor) {
this._zoomFactor = this.minZoomFactor;
}
this.update();
}
update() {
var _a, _b, _c, _d;
this.render();
let currUnitCenter, needUpdate = false;
if (!this._centerPosition) {
currUnitCenter = undefined;
needUpdate = currUnitCenter !== this.zoomCenter;
}
else if (this.zoomCenterUnit === 'NORMALIZED') {
if (this.imageReady) {
currUnitCenter = {
x: this._centerPosition.x / this.image.width,
y: this._centerPosition.y / this.image.height
};
needUpdate = currUnitCenter.x !== ((_a = this.zoomCenter) === null || _a === void 0 ? void 0 : _a.x) || currUnitCenter.y !== ((_b = this.zoomCenter) === null || _b === void 0 ? void 0 : _b.y);
}
}
else {
currUnitCenter = {
x: this._centerPosition.x,
y: this._centerPosition.y
};
needUpdate = currUnitCenter.x !== ((_c = this.zoomCenter) === null || _c === void 0 ? void 0 : _c.x) || currUnitCenter.y !== ((_d = this.zoomCenter) === null || _d === void 0 ? void 0 : _d.y);
}
if (needUpdate) {
this.zoomCenterChange.emit(currUnitCenter);
}
}
render() {
if (!this.context || !this.imageReady) {
return;
}
this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight); // clear canvas
this.context.lineWidth = 1; // border width
this.context.drawImage(this.image, 0, 0); // bg image
if (this._centerPosition) {
switch (this.zoomMode) {
case 'LENS':
this.renderLensMode();
break;
case 'COVER':
this.renderCoverMode();
break;
}
}
}
renderLensMode() {
this.context.lineWidth = 5; // border width
const zoomRect = this.getZoomRect();
this.context.fillRect(zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h); // bg (clear)
const clippingRect = this.getClippingRect();
// zoom image
this.context.drawImage(this.image, clippingRect.x, clippingRect.y, clippingRect.w, clippingRect.h, zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h);
this.context.strokeRect(zoomRect.x, zoomRect.y, zoomRect.w, zoomRect.h); // border
}
renderCoverMode() {
const covertRect = this.getCoverRect();
this.context.drawImage(this.image, covertRect.x, covertRect.y, covertRect.w, covertRect.h, 0, 0, this.canvasWidth, this.canvasHeight); // cover image
}
getZoomRect() {
const w = this._lensSize.width;
const h = this._lensSize.height;
const x = this._centerPosition.x - (w / 2);
const y = this._centerPosition.y - (h / 2);
return this.clampRect(x, y, w, h);
}
getClippingRect() {
const w = this._lensSize.width / this._zoomFactor;
const h = this._lensSize.height / this._zoomFactor;
const x = this._centerPosition.x - (w / 2);
const y = this._centerPosition.y - (h / 2);
return this.clampRect(x, y, w, h);
}
getCoverRect() {
const w = this.canvasWidth / this._zoomFactor;
const h = this.canvasHeight / this._zoomFactor;
// const x = this.mousePosition.x - (w / 2);
// const y = this.mousePosition.y - (h / 2);
const x = this._centerPosition.x - this._centerPosition.x / this._zoomFactor;
const y = this._centerPosition.y - this._centerPosition.y / this._zoomFactor;
return this.clampRect(x, y, w, h);
}
clampRect(x, y, w, h) {
if (x <= 0) {
x = 0;
}
if (x + w >= this.canvasWidth) {
x = this.canvasWidth - w;
}
if (y < 0) {
y = 0;
}
if (y + h >= this.canvasHeight) {
y = this.canvasHeight - h;
}
return { x, y, w, h };
}
calculateMousePosition(clientX, clientY) {
const boundingRect = this.canvas.getBoundingClientRect();
const boundingRectX = (clientX - boundingRect.left);
const boundingRectY = (clientY - boundingRect.top);
let elementX = boundingRectX, elementY = boundingRectY;
let elementWidth = boundingRect.width, elementHeight = boundingRect.height;
const computedStyle = window.getComputedStyle(this.canvas, null);
const domMatrix = new DOMMatrix(computedStyle.transform);
if (!domMatrix.isIdentity) {
const { a, b } = domMatrix;
const transformRot = Math.atan2(b, a);
const transformScale = Math.round(Math.sqrt(a * a + b * b) * 100) / 100;
if (transformRot) {
const boundingRectCenterX = boundingRect.width / 2;
const boundingRectCenterY = boundingRect.height / 2;
const dx = (boundingRectX - boundingRectCenterX);
const dy = (boundingRectY - boundingRectCenterY);
const d = Math.sqrt(dx * dx + dy * dy);
const r = Math.atan2(dy, dx);
elementWidth = this.canvas.clientWidth * transformScale;
elementHeight = this.canvas.clientHeight * transformScale;
elementX = (elementWidth / 2) + (d * Math.cos(r - transformRot));
elementY = (elementHeight / 2) + (d * Math.sin(r - transformRot));
}
}
const viewToModelX = this.canvasWidth / elementWidth;
const viewToModelY = this.canvasHeight / elementHeight;
const x = elementX * viewToModelX;
const y = elementY * viewToModelY;
this._centerPosition = { x, y };
}
onMouseLeave(event) {
if (!this.updateOnMouseEvents) {
return;
}
this._centerPosition = null;
this.update();
}
onMouseEnterOrMove(event) {
if (!this.updateOnMouseEvents) {
return;
}
this.calculateMousePosition(event.clientX, event.clientY);
this.update();
}
onMouseScroll(event) {
if (!this.updateOnMouseEvents) {
return;
}
let newZoomFactor = this._zoomFactor;
newZoomFactor -= event.deltaY / 1000;
if (newZoomFactor < this.minZoomFactor) {
newZoomFactor = this.minZoomFactor;
}
if (newZoomFactor > this.maxZoomFactor) {
newZoomFactor = this.maxZoomFactor;
}
if (this._zoomFactor !== newZoomFactor) {
this._zoomFactor = newZoomFactor;
if (this.zoomFactor !== this._zoomFactor) {
this.zoomFactorChange.emit(this._zoomFactor);
}
this.update();
}
event.preventDefault();
event.stopPropagation();
}
}
/** @nocollapse */ NgMagnizoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ NgMagnizoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: NgMagnizoomComponent, selector: "ng-magnizoom", inputs: { imageSrc: "imageSrc", zoomMode: "zoomMode", minZoomFactor: "minZoomFactor", maxZoomFactor: "maxZoomFactor", zoomFactor: "zoomFactor", lensSizeUnit: "lensSizeUnit", lensSize: "lensSize", zoomCenterUnit: "zoomCenterUnit", zoomCenter: "zoomCenter", updateOnMouseEvents: "updateOnMouseEvents", imageStyle: "imageStyle", imageClass: "imageClass" }, outputs: { zoomFactorChange: "zoomFactorChange", zoomCenterChange: "zoomCenterChange" }, viewQueries: [{ propertyName: "mainCanvasRef", first: true, predicate: ["mainCanvas"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<canvas #mainCanvas\n class=\"main-canvas\"\n [ngClass]=\"imageClass\"\n [ngStyle]=\"imageStyle\"\n [width]=\"canvasWidth\"\n [height]=\"canvasHeight\"\n (mouseleave)=\"onMouseLeave($event)\"\n (mouseenter)=\"onMouseEnterOrMove($event)\"\n (mousemove)=\"onMouseEnterOrMove($event)\"\n (wheel)=\"onMouseScroll($event)\">\n</canvas>\n", styles: [""], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-magnizoom', template: "<canvas #mainCanvas\n class=\"main-canvas\"\n [ngClass]=\"imageClass\"\n [ngStyle]=\"imageStyle\"\n [width]=\"canvasWidth\"\n [height]=\"canvasHeight\"\n (mouseleave)=\"onMouseLeave($event)\"\n (mouseenter)=\"onMouseEnterOrMove($event)\"\n (mousemove)=\"onMouseEnterOrMove($event)\"\n (wheel)=\"onMouseScroll($event)\">\n</canvas>\n" }]
}], ctorParameters: function () { return []; }, propDecorators: { imageSrc: [{
type: Input
}], zoomMode: [{
type: Input
}], minZoomFactor: [{
type: Input
}], maxZoomFactor: [{
type: Input
}], zoomFactor: [{
type: Input
}], zoomFactorChange: [{
type: Output
}], lensSizeUnit: [{
type: Input
}], lensSize: [{
type: Input
}], zoomCenterUnit: [{
type: Input
}], zoomCenter: [{
type: Input
}], zoomCenterChange: [{
type: Output
}], updateOnMouseEvents: [{
type: Input
}], imageStyle: [{
type: Input
}], imageClass: [{
type: Input
}], mainCanvasRef: [{
type: ViewChild,
args: ['mainCanvas', { static: true }]
}] } });
class NgMagnizoomModule {
}
/** @nocollapse */ NgMagnizoomModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
/** @nocollapse */ NgMagnizoomModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, declarations: [NgMagnizoomComponent], imports: [CommonModule], exports: [NgMagnizoomComponent] });
/** @nocollapse */ NgMagnizoomModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, imports: [CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NgMagnizoomModule, decorators: [{
type: NgModule,
args: [{
declarations: [NgMagnizoomComponent],
imports: [
CommonModule
],
exports: [NgMagnizoomComponent]
}]
}] });
/*
* Public API Surface of ng-magnizoom
*/
/**
* Generated bundle index. Do not edit.
*/
export { MagnizoomService, NgMagnizoomComponent, NgMagnizoomModule };
//# sourceMappingURL=ng-magnizoom.mjs.map