├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.MD
├── index.ts
├── package.json
├── src
├── image-zoom-container.component.metadata.json
├── image-zoom-container.component.ts
├── image-zoom-lens.component.metadata.json
├── image-zoom-lens.component.ts
├── image-zoom.directive.metadata.json
└── image-zoom.directive.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Angular
2 | /compiled
3 | -*.metadata.json
4 |
5 | # Node
6 | node_modules/*
7 | npm-debug.log
8 |
9 | -# TypeScript
10 | -typings
11 | -*.js
12 | -*.map
13 | -*.d.ts
14 | -
15 |
16 | # JetBrains
17 | .idea
18 | .project
19 | .settings
20 | .idea/*
21 | *.iml
22 |
23 | # Windows
24 | Thumbs.db
25 | Desktop.ini
26 |
27 | # Mac
28 | .DS_Store
29 | **/.DS_Store
30 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Node
2 | node_modules/*
3 | npm-debug.log
4 |
5 | # Angular
6 | /compiled
7 |
8 | # DO NOT IGNORE COMPILED FILES FOR NPM
9 | # TypeScript
10 | *.ts
11 | !*.d.ts
12 |
13 | # JetBrains
14 | .idea
15 | .project
16 | .settings
17 | .idea/*
18 | *.iml
19 |
20 | # Windows
21 | Thumbs.db
22 | Desktop.ini
23 |
24 | # Mac
25 | .DS_Store
26 | **/.DS_Store
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | sudo: false
3 | node_js:
4 | - '4.2.1'
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Nick Richardson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # angular2-image-zoom
2 | Angular 2.x.x Compatible
3 |
4 | ## Installation
5 |
6 | To install this library, run:
7 |
8 | ```bash
9 | $ npm install angular2-image-zoom --save
10 | ```
11 |
12 | ## Examples
13 | First, import the ImageZoom Module
14 |
15 | ```typescript
16 | import {ImageZoomModule} from 'angular2-image-zoom';
17 | ```
18 |
19 | Then, add ImageZoom to your modules import array
20 |
21 | ```typescript
22 | @NgModule({
23 | imports : [CommonModule, ImageZoomModule, ...],
24 | })
25 | ```
26 | Then just add the [imageZoom] tag to your img element
27 |
28 | ```html
29 |
30 | ```
31 |
32 | # Options
33 | Name | Type | Default | Description |
34 | ---|---|---|--- |
35 | imageZoom | string | null | Link to larger [src] image
36 | allowZoom | boolean | true | Whether or not zooming is enabled
37 | clickToZoom | boolean | false | Force a user to click before zooming begins, click again to stop zooming
38 | scrollZoom | boolean | true | Allow mousewheel to change zoom level
39 | windowPosition | number | 1 | Position of zoom window. (1-16)
40 | lensStyle | string | 'WINDOW' | Type of zoom ('LENS', 'WINDOW')
41 | lensWidth | number | 300 | Width of zoom lens
42 | lensHeight | number | 300 | Height of zoom lens
43 | lensBorder | number | 2 | Size of lens border (in pixels)
44 | delay | number | 100 | Delay before zoom lens appears (in milliseconds)
45 | zoomLevel | number | auto-calculated | Initial zoom level (smaller number indicates larger zoom)
46 | minZoomLevel | number | .2 | TODO
47 | maxZoomLevel | number | auto-calculated | TODO
48 | zoomLevelIncrement | number | .1 | Size of each change in zoom level while scrolling
49 |
50 |
51 |
52 |
53 |
54 |
55 | This library is a work in progress and any issues/pull-requests are welcomed!
56 |
57 | ## TODO
58 | 1. Create unit and E2E tests
59 | 2. ~~Allow window to be placed anywhere~~
60 | 3. ~~Show viewing lens over image to show zoom area~~
61 | 4. ~~Allow mouse scrolling to change zoom level~~
62 | 5. ~~Allow inner zoom lens~~
63 | 6. Lots more....
64 |
65 | ## Development
66 |
67 | To generate all `*
68 | }.js`, `*.js.map` and `*.d.ts` files:
69 |
70 | ```bash
71 | $ npm run tsc
72 | ```
73 |
74 | ## License
75 |
76 | MIT © [Nick Richardson](nick.richardson@mediapixeldesign.com)
77 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import {CommonModule} from '@angular/common';
3 | import {ImageZoom} from './src/image-zoom.directive';
4 | import {ImageZoomLens} from './src/image-zoom-lens.component';
5 | import {ImageZoomContainer} from './src/image-zoom-container.component';
6 |
7 | export * from './src/image-zoom.directive';
8 | export * from './src/image-zoom-lens.component';
9 | export * from './src/image-zoom-container.component';
10 |
11 | @NgModule({
12 | imports : [CommonModule],
13 | exports : [ImageZoom],
14 | declarations : [ImageZoom, ImageZoomContainer, ImageZoomLens],
15 | entryComponents: [ImageZoomContainer, ImageZoomLens]
16 | })
17 | export class ImageZoomModule {
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-image-zoom",
3 | "version": "1.2.2",
4 | "scripts": {
5 | "lint": "tslint src/**/*.ts",
6 | "test": "tsc && karma start",
7 | "prepare": "tsc && ngc",
8 | "tsc": "tsc",
9 | "ngc": "./node_modules/.bin/ngc -p tsconfig.json"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git@github.com:brtnshrdr/angular2-image-zoom.git"
14 | },
15 | "author": {
16 | "name": "Nick Richardson",
17 | "email": "nick.richardson@mediapixeldesign.com"
18 | },
19 | "keywords": [
20 | "angular",
21 | "angular2",
22 | "zoom",
23 | "image-zoom",
24 | "image zoom",
25 | "magnify",
26 | "hover",
27 | "enhance",
28 | "image"
29 | ],
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "git@github.com:brtnshrdr/angular2-image-zoom.git/issues"
33 | },
34 | "main": "./index.js",
35 | "devDependencies": {
36 | "@angular/core": "^4.4.3",
37 | "@angular/common": "^4.4.3",
38 | "@angular/compiler": "^4.4.3",
39 | "@angular/compiler-cli": "^4.4.3",
40 | "@angular/platform-server": "^4.4.3",
41 | "@angular/animations": "^4.4.3",
42 | "@angular/platform-browser": "^4.4.3",
43 | "codelyzer": "~3.2.1",
44 | "rxjs": "^5.0.1",
45 | "tslint": "~5.7.0",
46 | "typescript": "2.3.4",
47 | "zone.js": "^0.8.4",
48 | "@types/protractor": "^4.0.0",
49 | "@types/jasmine": "^2.5.40",
50 | "@types/selenium-webdriver": "^2.53.39"
51 | },
52 | "engines": {
53 | "node": ">=0.8.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/image-zoom-container.component.metadata.json:
--------------------------------------------------------------------------------
1 | [{"__symbolic":"module","version":3,"metadata":{"ImageZoomContainer":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component"},"arguments":[{"selector":"image-zoom-container","template":"","styles":["\n :host {\n position: absolute;\n text-align: center;\n overflow: hidden;\n z-index: 100;\n float: left;\n background: rgb(255, 255, 255) no-repeat;\n pointer-events: none;\n }\n "]}]}],"members":{"onMousemove":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousemove",["$event"]]}]}],"onMouseleave":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseleave",["$event"]]}]}],"onMouseScroll":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["MozMousePixelScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["DOMMouseScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousewheel",["$event"]]}]}],"generateStyles":[{"__symbolic":"method"}],"setBackgroundPostion":[{"__symbolic":"method"}],"setZoomSize":[{"__symbolic":"method"}],"setWindowPosition":[{"__symbolic":"method"}],"setOptions":[{"__symbolic":"method"}],"setVisibility":[{"__symbolic":"method"}],"setParentImageContainer":[{"__symbolic":"method"}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"}]}]}}}},{"__symbolic":"module","version":1,"metadata":{"ImageZoomContainer":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component"},"arguments":[{"selector":"image-zoom-container","template":"","styles":["\n :host {\n position: absolute;\n text-align: center;\n overflow: hidden;\n z-index: 100;\n float: left;\n background: rgb(255, 255, 255) no-repeat;\n pointer-events: none;\n }\n "]}]}],"members":{"onMousemove":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousemove",["$event"]]}]}],"onMouseleave":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseleave",["$event"]]}]}],"onMouseScroll":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["MozMousePixelScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["DOMMouseScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousewheel",["$event"]]}]}],"generateStyles":[{"__symbolic":"method"}],"setBackgroundPostion":[{"__symbolic":"method"}],"setZoomSize":[{"__symbolic":"method"}],"setWindowPosition":[{"__symbolic":"method"}],"setOptions":[{"__symbolic":"method"}],"setVisibility":[{"__symbolic":"method"}],"setParentImageContainer":[{"__symbolic":"method"}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"}]}]}}}}]
--------------------------------------------------------------------------------
/src/image-zoom-container.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, HostListener, ElementRef} from '@angular/core';
2 | import {ImageZoom} from './image-zoom.directive';
3 |
4 | @Component({
5 | selector : 'image-zoom-container',
6 | template : ``,
7 | styles : [`
8 | :host {
9 | position: absolute;
10 | text-align: center;
11 | overflow: hidden;
12 | z-index: 100;
13 | float: left;
14 | background: rgb(255, 255, 255) no-repeat;
15 | pointer-events: none;
16 | }
17 | `]
18 | })
19 | export class ImageZoomContainer {
20 | private visible: boolean;
21 | private windowWidth: number;
22 | private windowHeight: number;
23 | private borderSize: number;
24 | private imageHeight: number;
25 | private imageWidth: number;
26 | private top: number;
27 | private left: number;
28 | private positionX: number;
29 | private positionY: number;
30 | private image: string;
31 |
32 | private el: HTMLElement;
33 |
34 | private parentImageContainer: ImageZoom;
35 |
36 | @HostListener('mousemove', ['$event'])
37 | private onMousemove(event: MouseEvent) {
38 | this.parentImageContainer.onMousemove(event);
39 | }
40 |
41 | @HostListener('mouseleave', ['$event'])
42 | public onMouseleave(event: MouseEvent) {
43 | this.parentImageContainer.onMouseleave(event);
44 | }
45 |
46 | @HostListener('MozMousePixelScroll', ['$event'])
47 | @HostListener('DOMMouseScroll', ['$event'])
48 | @HostListener('mousewheel', ['$event'])
49 | public onMouseScroll(event: any) { // MouseWheelEvent is throwing undefined error in SystemJS
50 | this.parentImageContainer.onMouseScroll(event);
51 | }
52 |
53 |
54 | private generateStyles() {
55 | this.el.style.width = this.windowWidth + 'px';
56 | this.el.style.height = this.windowHeight + 'px';
57 | this.el.style.border = `${this.borderSize}px solid rgb(136, 136, 136)`;
58 | this.el.style.left = this.left + 'px';
59 | this.el.style.top = this.top + 'px';
60 | this.el.style.backgroundImage = `url(${this.image})`;
61 | }
62 |
63 | public setBackgroundPostion(x: number, y: number) {
64 | this.el.style.backgroundPosition = `${x}px ${y}px`;
65 | this.positionX = x;
66 | this.positionY = y;
67 | }
68 |
69 | public setZoomSize(width: number, height: number) {
70 | this.el.style.backgroundSize = `${width}px ${height}px`;
71 | this.imageWidth = width;
72 | this.imageHeight = height;
73 | }
74 |
75 | setWindowPosition(left: number, top: number) {
76 | this.el.style.left = left + 'px';
77 | this.el.style.top = top + 'px';
78 | this.left = left;
79 | this.top = top;
80 | }
81 |
82 |
83 | public setOptions(windowWidth: number, windowHeight: number, borderSize: number, image: string) {
84 | this.windowWidth = windowWidth;
85 | this.windowHeight = windowHeight;
86 | this.borderSize = borderSize;
87 | this.image = image;
88 | this.generateStyles();
89 | }
90 |
91 | public setVisibility(visible: boolean) {
92 | this.el.style.display = visible ? 'block' : 'none';
93 | this.visible = visible;
94 | }
95 |
96 |
97 | public setParentImageContainer(parentImageContainer: ImageZoom) {
98 | this.parentImageContainer = parentImageContainer;
99 | }
100 |
101 | constructor(private _elementRef: ElementRef) {
102 | this.el = this._elementRef.nativeElement;
103 | this.setVisibility(false);
104 | }
105 |
106 | }
--------------------------------------------------------------------------------
/src/image-zoom-lens.component.metadata.json:
--------------------------------------------------------------------------------
1 | [{"__symbolic":"module","version":3,"metadata":{"ImageZoomLens":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component"},"arguments":[{"selector":"image-zoom-lens","template":"","styles":["\n :host {\n float: right;\n overflow: hidden;\n z-index: 999;\n opacity: .4;\n zoom: 1;\n cursor: default;\n border: 1px solid rgb(0, 0, 0);\n position: absolute;\n background: rgb(255, 255, 255) no-repeat 0 0;\n pointer-events: none;\n }\n "]}]}],"members":{"onMousemove":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousemove",["$event"]]}]}],"onMouseleave":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseleave",["$event"]]}]}],"onMouseScroll":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["MozMousePixelScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["DOMMouseScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousewheel",["$event"]]}]}],"setLensSize":[{"__symbolic":"method"}],"setWindowPosition":[{"__symbolic":"method"}],"setOptions":[{"__symbolic":"method"}],"setVisibility":[{"__symbolic":"method"}],"setParentImageContainer":[{"__symbolic":"method"}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"}]}]}}}},{"__symbolic":"module","version":1,"metadata":{"ImageZoomLens":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Component"},"arguments":[{"selector":"image-zoom-lens","template":"","styles":["\n :host {\n float: right;\n overflow: hidden;\n z-index: 999;\n opacity: .4;\n zoom: 1;\n cursor: default;\n border: 1px solid rgb(0, 0, 0);\n position: absolute;\n background: rgb(255, 255, 255) no-repeat 0 0;\n pointer-events: none;\n }\n "]}]}],"members":{"onMousemove":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousemove",["$event"]]}]}],"onMouseleave":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseleave",["$event"]]}]}],"onMouseScroll":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["MozMousePixelScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["DOMMouseScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousewheel",["$event"]]}]}],"setLensSize":[{"__symbolic":"method"}],"setWindowPosition":[{"__symbolic":"method"}],"setOptions":[{"__symbolic":"method"}],"setVisibility":[{"__symbolic":"method"}],"setParentImageContainer":[{"__symbolic":"method"}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"}]}]}}}}]
--------------------------------------------------------------------------------
/src/image-zoom-lens.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, HostListener, ElementRef} from '@angular/core';
2 | import {ImageZoom} from './image-zoom.directive';
3 |
4 | @Component({
5 | selector : 'image-zoom-lens',
6 | template : ``,
7 | styles : [`
8 | :host {
9 | float: right;
10 | overflow: hidden;
11 | z-index: 999;
12 | opacity: .4;
13 | zoom: 1;
14 | cursor: default;
15 | border: 1px solid rgb(0, 0, 0);
16 | position: absolute;
17 | background: rgb(255, 255, 255) no-repeat 0 0;
18 | pointer-events: none;
19 | }
20 | `]
21 | })
22 | export class ImageZoomLens {
23 | private visible: boolean;
24 | private lensWidth: number;
25 | private lensHeight: number;
26 | private top: number;
27 | private left: number;
28 | private borderSize: number;
29 |
30 | private el: HTMLElement;
31 |
32 | private parentImageContainer: ImageZoom;
33 |
34 | @HostListener('mousemove', ['$event'])
35 | private onMousemove(event: MouseEvent) {
36 | this.parentImageContainer.onMousemove(event);
37 | }
38 |
39 | @HostListener('mouseleave', ['$event'])
40 | public onMouseleave(event: MouseEvent) {
41 | let x: number = event.clientX;
42 | let y: number = event.clientY;
43 | if(x <= this.parentImageContainer.img.x || x >= (this.parentImageContainer.img.x + this.parentImageContainer.img.width)) {
44 | this.parentImageContainer.onMouseleave(event);
45 | } else if(y <= this.parentImageContainer.img.y || y >= (this.parentImageContainer.img.y + this.parentImageContainer.img.height)){
46 | this.parentImageContainer.onMouseleave(event);
47 | } else {
48 | this.parentImageContainer.onMousemove(event); // "mouseleave" event was just the mouse moving faster than the lens
49 | }
50 | }
51 |
52 | @HostListener('MozMousePixelScroll', ['$event'])
53 | @HostListener('DOMMouseScroll', ['$event'])
54 | @HostListener('mousewheel', ['$event'])
55 | public onMouseScroll(event: any) { // MouseWheelEvent is throwing undefined error in SystemJS
56 | this.parentImageContainer.onMouseScroll(event);
57 | }
58 |
59 | public setLensSize(width: number, height: number) {
60 | this.el.style.width = width + 'px';
61 | this.el.style.height = height + 'px';
62 | this.lensWidth = width;
63 | this.lensHeight = height;
64 | }
65 |
66 | setWindowPosition(left: number, top: number) {
67 | this.el.style.left = left + 'px';
68 | this.el.style.top = top + 'px';
69 | this.left = left;
70 | this.top = top;
71 | }
72 |
73 |
74 | public setOptions(borderSize: number) {
75 | this.el.style.border = `${borderSize}px solid rgb(136, 136, 136)`;
76 | this.borderSize = borderSize;
77 | }
78 |
79 | public setVisibility(visible: boolean) {
80 | this.el.style.display = visible ? 'block' : 'none';
81 | this.visible = visible;
82 | }
83 |
84 |
85 | public setParentImageContainer(parentImageContainer: ImageZoom) {
86 | this.parentImageContainer = parentImageContainer;
87 | }
88 |
89 | constructor(private _elementRef: ElementRef) {
90 | this.el = this._elementRef.nativeElement;
91 | this.setVisibility(false);
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/src/image-zoom.directive.metadata.json:
--------------------------------------------------------------------------------
1 | [{"__symbolic":"module","version":3,"metadata":{"ImageZoom":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Directive"},"arguments":[{"selector":"[imageZoom]"}]}],"members":{"imageZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"allowZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"clickToZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"scrollZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"windowPosition":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensStyle":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensWidth":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensHeight":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensBorder":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"delay":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"minZoomLevel":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"zoomLevel":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"maxZoomLevel":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"zoomLevelIncrement":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"},{"__symbolic":"reference","module":"@angular/core","name":"ComponentFactoryResolver"},{"__symbolic":"reference","module":"@angular/core","name":"ViewContainerRef"}]}],"imageChanged":[{"__symbolic":"method"}],"setImageZoomContainer":[{"__symbolic":"method"}],"setImageZoomContainerVisiblity":[{"__symbolic":"method"}],"setImageZoomLens":[{"__symbolic":"method"}],"setImageZoomLensVisibility":[{"__symbolic":"method"}],"setImageZoomLensPosition":[{"__symbolic":"method"}],"setImageZoomLensSize":[{"__symbolic":"method"}],"setImageBackgroundPosition":[{"__symbolic":"method"}],"setWindowPosition":[{"__symbolic":"method"}],"changeZoomLevel":[{"__symbolic":"method"}],"calculateOffsets":[{"__symbolic":"method"}],"calculateBoundaries":[{"__symbolic":"method"}],"allowZooming":[{"__symbolic":"method"}],"onMousemove":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousemove",["$event"]]}]}],"onMouseenter":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseenter",["$event"]]}]}],"onMouseleave":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseleave",["$event"]]}]}],"onMouseScroll":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["MozMousePixelScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["DOMMouseScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousewheel",["$event"]]}]}],"onClick":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["click",["$event"]]}]}],"ngOnChanges":[{"__symbolic":"method"}],"ngOnInit":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}},{"__symbolic":"module","version":1,"metadata":{"ImageZoom":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Directive"},"arguments":[{"selector":"[imageZoom]"}]}],"members":{"imageZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"allowZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"clickToZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"scrollZoom":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"windowPosition":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensStyle":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensWidth":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensHeight":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"lensBorder":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"delay":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"minZoomLevel":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"zoomLevel":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"maxZoomLevel":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"zoomLevelIncrement":[{"__symbolic":"property","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Input"}}]}],"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@angular/core","name":"ElementRef"},{"__symbolic":"reference","module":"@angular/core","name":"ComponentFactoryResolver"},{"__symbolic":"reference","module":"@angular/core","name":"ViewContainerRef"}]}],"imageChanged":[{"__symbolic":"method"}],"setImageZoomContainer":[{"__symbolic":"method"}],"setImageZoomContainerVisiblity":[{"__symbolic":"method"}],"setImageZoomLens":[{"__symbolic":"method"}],"setImageZoomLensVisibility":[{"__symbolic":"method"}],"setImageZoomLensPosition":[{"__symbolic":"method"}],"setImageZoomLensSize":[{"__symbolic":"method"}],"setImageBackgroundPosition":[{"__symbolic":"method"}],"setWindowPosition":[{"__symbolic":"method"}],"changeZoomLevel":[{"__symbolic":"method"}],"calculateOffsets":[{"__symbolic":"method"}],"calculateBoundaries":[{"__symbolic":"method"}],"allowZooming":[{"__symbolic":"method"}],"onMousemove":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousemove",["$event"]]}]}],"onMouseenter":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseenter",["$event"]]}]}],"onMouseleave":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mouseleave",["$event"]]}]}],"onMouseScroll":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["MozMousePixelScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["DOMMouseScroll",["$event"]]},{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["mousewheel",["$event"]]}]}],"onClick":[{"__symbolic":"method","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"HostListener"},"arguments":["click",["$event"]]}]}],"ngOnChanges":[{"__symbolic":"method"}],"ngOnInit":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}}]
--------------------------------------------------------------------------------
/src/image-zoom.directive.ts:
--------------------------------------------------------------------------------
1 | import {Directive, Input, HostListener, ComponentFactoryResolver, ComponentRef, ViewContainerRef, OnInit, OnDestroy, OnChanges, SimpleChanges, ElementRef, ViewChild} from '@angular/core';
2 | import {ImageZoomContainer} from './image-zoom-container.component';
3 | import {ImageZoomLens} from './image-zoom-lens.component';
4 |
5 | @Directive({
6 | selector : '[imageZoom]'
7 | })
8 | export class ImageZoom implements OnInit, OnDestroy, OnChanges {
9 | @Input() imageZoom: string;
10 | @Input() allowZoom: boolean = true;
11 | @Input() clickToZoom: boolean = false;
12 | @Input() scrollZoom: boolean = true;
13 | @Input() windowPosition: number = 1;
14 | @Input() lensStyle: string = 'WINDOW';
15 | @Input() lensWidth: number = 300;
16 | @Input() lensHeight: number = 300;
17 | @Input() lensBorder: number = 2;
18 | @Input() delay: number = 100;
19 | @Input() minZoomLevel: number = .2;
20 | @Input() zoomLevel: number;
21 |
22 | @Input()
23 | private set maxZoomLevel(maxZoomLevel: number) {
24 | this._maxZoomLevel = maxZoomLevel;
25 | this._autoCalculateZoom = false;
26 | }
27 |
28 | @Input() private zoomLevelIncrement: number = .1;
29 |
30 | public img: HTMLImageElement;
31 | public imageZoomContainer: ImageZoomContainer;
32 | public imageZoomLens: ImageZoomLens;
33 |
34 | public isZooming: boolean = false;
35 |
36 | private _elementPosX: number;
37 | private _elementPosY: number;
38 | private _elementOffsetX: number;
39 | private _elementOffsetY: number;
40 | private _containerOffsetX: number;
41 | private _containerOffsetY: number;
42 |
43 | private _zoomedImage: HTMLImageElement;
44 | private _zoomedImageLoaded: boolean = false;
45 | private _zoomedImageWidth: number;
46 | private _zoomedImageHeight: number;
47 | private _maxZoomLevel: number;
48 | private _autoCalculateZoom: boolean = true;
49 |
50 | private _widthRatio: number;
51 | private _heightRatio: number;
52 |
53 | private _currentX: number = 0;
54 | private _currentY: number = 0;
55 | private _mouseMoveDebounce: number = 0;
56 | private _mouseEnterDebounce: number = 0;
57 | private _lastEvent: MouseEvent;
58 | private _previousCursor: string;
59 |
60 | private _imageLoaded: boolean = false;
61 | private _componentsCreated: boolean = false;
62 |
63 | private _imageZoomContainerRef: ComponentRef;
64 | private _imageZoomLensRef: ComponentRef;
65 |
66 | constructor(private _elementRef: ElementRef, private _componentFactoryResolver: ComponentFactoryResolver, private _viewContainerRef: ViewContainerRef) {
67 | if(this._elementRef.nativeElement.nodeName !== 'IMG') {
68 | console.error('ImageZoom not placed on image element', this._elementRef.nativeElement);
69 | return;
70 | }
71 | this.img = this._elementRef.nativeElement;
72 | this.img.onload = () => {
73 | this._imageLoaded = true;
74 | if(this._componentsCreated) { // Possible race condition if the component resolver hasn't finished yet
75 | this.imageChanged();
76 | }
77 | };
78 |
79 | let imageZoomContainerFactory = this._componentFactoryResolver.resolveComponentFactory(ImageZoomContainer);
80 | let imageZoomLensFactory = this._componentFactoryResolver.resolveComponentFactory(ImageZoomLens);
81 |
82 | this._imageZoomContainerRef = this._viewContainerRef.createComponent(imageZoomContainerFactory);
83 | this.imageZoomContainer = this._imageZoomContainerRef.instance;
84 | this.imageZoomContainer.setParentImageContainer(this);
85 |
86 | this._imageZoomLensRef = this._viewContainerRef.createComponent(imageZoomLensFactory);
87 | this.imageZoomLens = this._imageZoomLensRef.instance;
88 | this.imageZoomLens.setParentImageContainer(this);
89 |
90 | this._componentsCreated = true;
91 | if(this._imageLoaded) {
92 | this.imageChanged();
93 | }
94 | }
95 |
96 | private imageChanged() {
97 | this._zoomedImageLoaded = false;
98 | this._zoomedImage = new Image();
99 | this._zoomedImage.onload = () => {
100 | this._zoomedImageWidth = this._zoomedImage.width;
101 | this._zoomedImageHeight = this._zoomedImage.height;
102 | if(this._zoomedImageWidth < this.img.width) {
103 | this.allowZoom = false;
104 | return;
105 | }
106 | this._zoomedImageLoaded = true;
107 | this.calculateOffsets();
108 | this.setImageZoomContainer();
109 | this.setImageZoomLens();
110 | this.setImageZoomLensPosition();
111 | this.setImageZoomLensSize();
112 | if(this._autoCalculateZoom) {
113 | if(this._zoomedImageWidth > this._zoomedImageHeight) {
114 | this._maxZoomLevel = this._zoomedImageHeight / this.lensHeight;
115 | } else {
116 | this._maxZoomLevel = this._zoomedImageWidth / this.lensWidth;
117 | }
118 | }
119 | if(!this.zoomLevel)
120 | {
121 | this.zoomLevel = (this._maxZoomLevel / 1.5);
122 | }
123 | this.changeZoomLevel();
124 | };
125 | this._zoomedImage.src = this.imageZoom ? this.imageZoom : this.img.src;
126 | }
127 |
128 | private setImageZoomContainer() {
129 | this.imageZoomContainer.setOptions(this.lensWidth, this.lensHeight, this.lensBorder, this._zoomedImage.src);
130 | }
131 |
132 | private setImageZoomContainerVisiblity(visible: boolean) {
133 | this.imageZoomContainer.setVisibility(visible);
134 | }
135 |
136 | private setImageZoomLens() {
137 | this.imageZoomLens.setOptions(this.lensBorder);
138 | }
139 |
140 | private setImageZoomLensVisibility(visible: boolean) {
141 | if(!visible || this.lensStyle.toUpperCase() === 'WINDOW') {
142 | this.imageZoomLens.setVisibility(visible);
143 | }
144 | }
145 |
146 | private setImageZoomLensPosition() {
147 | this.imageZoomLens.setWindowPosition(this._currentX - (this.lensWidth / this._widthRatio / 2) - this._containerOffsetX, this._currentY - (this.lensHeight / this._heightRatio / 2) - this._containerOffsetY);
148 | }
149 |
150 | private setImageZoomLensSize() {
151 | this.imageZoomLens.setLensSize(this.lensWidth / this._widthRatio, this.lensHeight / this._heightRatio);
152 | }
153 |
154 | private setImageBackgroundPosition() {
155 | let x: number = (((this._currentX - this._containerOffsetX) - this._elementOffsetX) * this._widthRatio - this.lensWidth / 2) * -1;
156 | let y: number = (((this._currentY - this._containerOffsetY) - this._elementOffsetY) * this._heightRatio - this.lensHeight / 2) * -1;
157 | this.imageZoomContainer.setBackgroundPostion(x, y);
158 | }
159 |
160 | private setWindowPosition() {
161 | if(this.lensStyle.toUpperCase() === 'LENS') {
162 | this.imageZoomContainer.setWindowPosition(this._currentX - (this.lensWidth / 2) - this.lensBorder - this._containerOffsetX, this._currentY - (this.lensHeight / 2) - this.lensBorder - this._containerOffsetY); // Account for lens border shifting image down and to the right
163 | } else if(this.lensStyle.toUpperCase() === 'WINDOW') {
164 | let windowX: number = this._elementPosX + this.img.width;
165 | let windowY: number = this._elementPosY;
166 | switch (this.windowPosition) { // Calculate x position
167 | case 1:
168 | case 2:
169 | case 3:
170 | case 4:
171 | case 16:
172 | break; // Default above
173 | case 5:
174 | case 15:
175 | windowX -= this.lensWidth;
176 | break;
177 | case 6:
178 | case 14:
179 | windowX -= ((this.lensWidth / 2) + (this.img.width / 2));
180 | break;
181 | case 7:
182 | case 13:
183 | windowX -= this.img.width;
184 | break;
185 | case 8:
186 | case 9:
187 | case 10:
188 | case 11:
189 | case 12:
190 | windowX -= (this.img.width + this.lensWidth);
191 | break;
192 | }
193 | switch (this.windowPosition) { // Calculate Y position
194 | case 1:
195 | case 11:
196 | break; // Default above
197 | case 2:
198 | case 10:
199 | windowY += ((this.img.height / 2) - (this.lensHeight / 2));
200 | break;
201 | case 3:
202 | case 9:
203 | windowY += (this.img.height - this.lensHeight);
204 | break;
205 | case 4:
206 | case 5:
207 | case 6:
208 | case 7:
209 | case 8:
210 | windowY += (this.img.height);
211 | break;
212 | case 12:
213 | case 13:
214 | case 14:
215 | case 15:
216 | case 16:
217 | windowY -= (this.lensHeight);
218 | break;
219 | }
220 | this.imageZoomContainer.setWindowPosition(windowX - this._containerOffsetX, windowY - this._containerOffsetY);
221 | }
222 | }
223 |
224 | private changeZoomLevel() {
225 | this._widthRatio = (this._zoomedImageWidth / this.zoomLevel) / this.img.width;
226 | this._heightRatio = (this._zoomedImageHeight / this.zoomLevel) / this.img.height;
227 | this.imageZoomContainer.setZoomSize(this._zoomedImageWidth / this.zoomLevel, this._zoomedImageHeight / this.zoomLevel);
228 | this.setImageBackgroundPosition();
229 | }
230 |
231 | private calculateOffsets() {
232 | this._elementPosX = this.img.getBoundingClientRect().left;
233 | this._elementPosY = this.img.getBoundingClientRect().top;
234 | if(this.lensHeight > this.img.height) {
235 | this.lensHeight = this.img.height;
236 | }
237 | let parent: HTMLElement = (this.img.offsetParent);
238 | let offsetX: number = 0;
239 | let offsetY: number = 0;
240 | while (parent !== undefined && parent !== null && parent.offsetParent !== undefined && parent.offsetParent !== null) {
241 | offsetX += parent.offsetLeft;
242 | offsetY += parent.offsetTop;
243 | parent = (parent.offsetParent);
244 | }
245 | this._containerOffsetX = offsetX;
246 | this._containerOffsetY = offsetY;
247 | this._elementOffsetX = this.img.offsetLeft;
248 | this._elementOffsetY = this.img.offsetTop;
249 | this.setImageZoomContainer();
250 | }
251 |
252 | private calculateBoundaries(clientX: number, clientY: number) {
253 | let xPos = clientX - this._elementOffsetX;
254 | let rightBoundary: number = (this.img.width - ((this.lensWidth / 2) / this._widthRatio)) + this._containerOffsetX;
255 | let leftBoundary: number = ((this.lensWidth / 2) / this._widthRatio) + this._containerOffsetX;
256 | if(xPos >= rightBoundary) {
257 | this._currentX = rightBoundary + this._elementOffsetX;
258 | } else if(xPos <= leftBoundary) {
259 | this._currentX = leftBoundary + this._elementOffsetX;
260 | } else {
261 | this._currentX = clientX;
262 | }
263 |
264 | let yPos = clientY - this._elementOffsetY;
265 | let topBoundary: number = ((this.lensHeight / 2) / this._heightRatio) + this._containerOffsetY;
266 | let bottomBoundary: number = (this.img.height - ((this.lensHeight / 2) / this._heightRatio)) + this._containerOffsetY;
267 | if(yPos >= bottomBoundary) {
268 | this._currentY = bottomBoundary + this._elementOffsetY;
269 | } else if(yPos <= topBoundary) {
270 | this._currentY = topBoundary + this._elementOffsetY;
271 | } else {
272 | this._currentY = clientY;
273 | }
274 | }
275 |
276 | public allowZooming(): boolean {
277 | return this.allowZoom && this._imageLoaded && this._zoomedImageLoaded;
278 | }
279 |
280 | @HostListener('mousemove', ['$event'])
281 | public onMousemove(event: MouseEvent) {
282 | if(this.allowZooming()) {
283 | this._lastEvent = event; // Make sure we end up at the right place, without calling too frequently
284 | if(this._mouseMoveDebounce !== 0) {
285 | return;
286 | }
287 | this._mouseMoveDebounce = window.setTimeout(() => {
288 | if(!this.isZooming && this._mouseEnterDebounce === 0) {
289 | this.onMouseenter(event);
290 | }
291 | this.calculateBoundaries(this._lastEvent.clientX, this._lastEvent.clientY);
292 | this.setImageBackgroundPosition();
293 | this.setImageZoomLensPosition();
294 | this.setWindowPosition();
295 | this._mouseMoveDebounce = 0;
296 | }, 10); // Wait 10ms to be more performant
297 | }
298 | }
299 |
300 | @HostListener('mouseenter', ['$event'])
301 | public onMouseenter(event: MouseEvent) {
302 | if(!this.isZooming) {
303 | if(this.allowZooming()) {
304 | this.calculateOffsets();
305 | if(this._mouseEnterDebounce !== 0) {
306 | clearTimeout(this._mouseEnterDebounce);
307 | }
308 | this._mouseEnterDebounce = window.setTimeout(() => {
309 | this.isZooming = true;
310 | clearTimeout(this._mouseEnterDebounce);
311 | this._previousCursor = this.img.style.cursor;
312 | this.img.style.cursor = 'pointer';
313 | this.setImageZoomContainerVisiblity(true);
314 | this.setImageZoomLensVisibility(true);
315 | }, this.delay);
316 | }
317 | }
318 | }
319 |
320 | @HostListener('mouseleave', ['$event'])
321 | public onMouseleave(event: MouseEvent) {
322 | let x: number = event.clientX;
323 | let y: number = event.clientY;
324 | if(y <= this.img.getBoundingClientRect().top || y >= (this.img.getBoundingClientRect().top + this.img.height) || x <= this.img.getBoundingClientRect().left || x >= (this.img.getBoundingClientRect().left + this.img.width)) {
325 | if(this._mouseEnterDebounce !== 0) {
326 | clearTimeout(this._mouseEnterDebounce);
327 | }
328 | if(this.isZooming) {
329 | this.img.style.cursor = this._previousCursor;
330 | this.setImageZoomContainerVisiblity(false);
331 | this.setImageZoomLensVisibility(false);
332 | this.isZooming = false;
333 | }
334 | } else {
335 | this.onMousemove(event); // "mouseleave" event was just the mouse focus going to the lens
336 | }
337 |
338 | }
339 |
340 | @HostListener('MozMousePixelScroll', ['$event'])
341 | @HostListener('DOMMouseScroll', ['$event'])
342 | @HostListener('mousewheel', ['$event'])
343 | public onMouseScroll(event: any) { // MouseWheelEvent is throwing undefined error in SystemJS
344 |
345 | if(this.scrollZoom && this.allowZooming()) {
346 | event.stopImmediatePropagation();
347 | event.stopPropagation();
348 | event.preventDefault();
349 |
350 | var pos = ((event.wheelDeltaY | event.detail * -1) != 0) ?
351 | (event.wheelDeltaY | event.detail * -1) :
352 | (event.deltaY | event.wheelDelta);
353 |
354 | if(pos > 0) { // Scroll up
355 | if(this.zoomLevel > (this.minZoomLevel + this.zoomLevelIncrement)) {
356 | this.zoomLevel -= this.zoomLevelIncrement;
357 | this.changeZoomLevel();
358 | }
359 | } else { // Scroll down
360 | if(this.zoomLevel < (this._maxZoomLevel - this.zoomLevelIncrement)) {
361 | this.zoomLevel += this.zoomLevelIncrement;
362 | this.changeZoomLevel();
363 | }
364 | }
365 | this.calculateBoundaries(this._lastEvent.clientX, this._lastEvent.clientY);
366 | this.setWindowPosition();
367 | this.setImageZoomLensSize();
368 | this.setImageZoomLensPosition();
369 | return false;
370 | } else {
371 | return true;
372 | }
373 | }
374 |
375 | @HostListener('click', ['$event'])
376 | public onClick(event: MouseEvent) {
377 | if(this.clickToZoom) {
378 | this.allowZoom = !this.allowZoom;
379 | if(this.allowZooming()) {
380 | this.onMousemove(event);
381 | } else {
382 | this.onMouseleave(event);
383 | }
384 | }
385 | }
386 |
387 | ngOnChanges(changeRecord: SimpleChanges) {
388 | for (let prop in changeRecord) {
389 | if(!changeRecord[prop].isFirstChange()) {
390 | if(prop === 'imageZoom') { // Image changed
391 | this.imageChanged();
392 | } else if(prop === 'lensWidth' || prop === 'lensHeight') {
393 | if(this.lensHeight > this.img.height) {
394 | this.lensHeight = this.img.height;
395 | }
396 | this.setImageZoomContainer();
397 | this.setImageBackgroundPosition();
398 | } else if(prop === 'zoomLevel') {
399 | this.changeZoomLevel();
400 | }
401 | }
402 | }
403 | }
404 |
405 | ngOnInit() {
406 | if(this.clickToZoom) {
407 | this.allowZoom = false;
408 | }
409 | }
410 |
411 | ngOnDestroy() {
412 | this._imageZoomContainerRef.destroy();
413 | this._imageZoomLensRef.destroy();
414 | }
415 |
416 | }
417 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "noImplicitAny": true,
4 | "module": "commonjs",
5 | "target": "ES5",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "sourceMap": true,
9 | "declaration": true,
10 | "lib": ["es2016", "dom"]
11 | },
12 | "angularCompilerOptions": {
13 | "genDir": "compiled"
14 | },
15 | "typeRoots": [
16 | "node_modules/@types"
17 | ],
18 | "files": [
19 | "index.ts"
20 | ],
21 | "exclude": [
22 | "node_modules"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "class-name": true,
7 | "comment-format": [
8 | true,
9 | "check-space"
10 | ],
11 | "curly": true,
12 | "eofline": true,
13 | "forin": true,
14 | "indent": [
15 | true,
16 | "spaces"
17 | ],
18 | "label-position": true,
19 | "label-undefined": true,
20 | "max-line-length": [
21 | true,
22 | 140
23 | ],
24 | "member-access": false,
25 | "member-ordering": [
26 | true,
27 | "static-before-instance",
28 | "variables-before-functions"
29 | ],
30 | "no-arg": true,
31 | "no-bitwise": true,
32 | "no-console": [
33 | true,
34 | "debug",
35 | "info",
36 | "time",
37 | "timeEnd",
38 | "trace"
39 | ],
40 | "no-construct": true,
41 | "no-debugger": true,
42 | "no-duplicate-key": true,
43 | "no-duplicate-variable": true,
44 | "no-empty": false,
45 | "no-eval": true,
46 | "no-inferrable-types": true,
47 | "no-shadowed-variable": true,
48 | "no-string-literal": false,
49 | "no-switch-case-fall-through": true,
50 | "no-trailing-whitespace": true,
51 | "no-unused-expression": true,
52 | "no-unused-variable": true,
53 | "no-unreachable": true,
54 | "no-use-before-declare": true,
55 | "no-var-keyword": true,
56 | "object-literal-sort-keys": false,
57 | "one-line": [
58 | true,
59 | "check-open-brace",
60 | "check-catch",
61 | "check-else",
62 | "check-whitespace"
63 | ],
64 | "quotemark": [
65 | true,
66 | "single"
67 | ],
68 | "radix": true,
69 | "semicolon": [
70 | "always"
71 | ],
72 | "triple-equals": [
73 | true,
74 | "allow-null-check"
75 | ],
76 | "typedef-whitespace": [
77 | true,
78 | {
79 | "call-signature": "nospace",
80 | "index-signature": "nospace",
81 | "parameter": "nospace",
82 | "property-declaration": "nospace",
83 | "variable-declaration": "nospace"
84 | }
85 | ],
86 | "variable-name": false,
87 | "whitespace": [
88 | true,
89 | "check-decl",
90 | "check-operator",
91 | "check-separator",
92 | "check-type"
93 | ],
94 |
95 | "directive-selector-name": [true, "camelCase"],
96 | "component-selector-name": [true, "kebab-case"],
97 | "directive-selector-type": [true, "attribute"],
98 | "component-selector-type": [true, "element"],
99 | "use-input-property-decorator": true,
100 | "use-output-property-decorator": true,
101 | "use-host-property-decorator": true,
102 | "no-input-rename": true,
103 | "no-output-rename": true,
104 | "use-life-cycle-interface": true,
105 | "use-pipe-transform-interface": true,
106 | "component-class-suffix": true,
107 | "directive-class-suffix": true
108 | }
109 | }
110 |
--------------------------------------------------------------------------------