Skip to content

Commit 92359c9

Browse files
committed
WIP - nearly ready, to test
1 parent 2d08883 commit 92359c9

File tree

7 files changed

+162
-117
lines changed

7 files changed

+162
-117
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# 1.2.2
2+
3+
### Features
4+
5+
Support for pinch to zoom
6+
7+
### Bug Fixes
8+
9+
**CSS:** In iOS the status bar does not overlapps the back button anymore
10+
111
# 1.1.5
212

313
- Update for AOT support (related to PR #9)

README.md

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,6 @@ import { IonicImageViewerModule } from 'ionic-img-viewer';
3636
export class AppModule {}
3737
```
3838

39-
**For Ionic 2 beta version:**
40-
41-
Import the image viewer directive in your component.
42-
43-
```typescript
44-
import { ImageViewerDirective } from 'ionic-img-viewer';
45-
46-
47-
@Component({
48-
template: `<img [src]="url" imageViewer />`,
49-
directives: [ImageViewerDirective]
50-
})
51-
class MyComponent {
52-
53-
}
54-
```
55-
5639
## Usage
5740

5841
Add the `imageViewer` property to the pictures.
@@ -61,6 +44,15 @@ Add the `imageViewer` property to the pictures.
6144
<img src="IMAGE_URL" imageViewer />
6245
```
6346

47+
If you use thumbnails and want to display bigger images, you can use it like so :
48+
49+
```html
50+
<img src="IMAGE_URL" imageViewer="OTHER_IMAGE_URL" />
51+
```
52+
53+
However, if `OTHER_IMAGE_URL` is not preloaded, the animation might suffer. Indeed there will be no ready image to make the transition (it might blink and you'll not get the smooth transition effet while opening).
54+
So try to cache your image before the call if you use it that way.
55+
6456
# Contributing
6557

6658
See [CONTRIBUTING.md](CONTRIBUTING.md).
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Renderer } from '@angular/core';
12
import { ImageViewerComponent } from './image-viewer.component';
23
import { PanGesture } from 'ionic-angular/gestures/drag-gesture';
34
import { pointerCoord } from 'ionic-angular/util/dom';
@@ -8,15 +9,15 @@ const HAMMER_THRESHOLD = 10;
89
const MAX_ATTACK_ANGLE = 45;
910
const DRAG_THRESHOLD = 70;
1011

11-
export class ImageViewerGesture extends PanGesture {
12+
export class ImageViewerTransitionGesture extends PanGesture {
1213

1314
private translationY: number;
1415
private opacity: number;
1516
private startY: number;
1617
private imageContainer: HTMLElement;
1718
private backdrop: HTMLElement;
1819

19-
constructor(platform: Platform, private component: ImageViewerComponent, domCtrl: DomController, private cb: Function) {
20+
constructor(platform: Platform, private component: ImageViewerComponent, domCtrl: DomController, private renderer: Renderer, private cb: Function) {
2021
super(platform, component.getNativeElement(), {
2122
maxAngle: MAX_ATTACK_ANGLE,
2223
threshold: HAMMER_THRESHOLD,
@@ -61,8 +62,8 @@ export class ImageViewerGesture extends PanGesture {
6162
this.opacity = Math.max(1 - Math.abs(this.translationY) / (10 * DRAG_THRESHOLD), .5);
6263

6364
this.plt.raf(() => {
64-
this.imageContainer.style[this.plt.Css.transform] = `translateY(${this.translationY}px)`;
65-
this.backdrop.style['opacity'] = this.opacity.toString();
65+
this.renderer.setElementStyle(this.imageContainer, this.plt.Css.transform, `translateY(${this.translationY}px)`);
66+
this.renderer.setElementStyle(this.backdrop, 'opacity', this.opacity.toString());
6667
});
6768

6869
return true;

src/image-viewer-zoom-gesture.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Renderer } from '@angular/core';
2+
import { Animation, DomController, Gesture, Platform } from 'ionic-angular';
3+
import { DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from 'ionic-angular/gestures/hammer';
4+
5+
import { ImageViewerComponent } from './image-viewer.component';
6+
7+
const MAX_SCALE = 2;
8+
9+
export class ImageViewerZoomGesture extends Gesture {
10+
private adjustScale = 1;
11+
private adjustDeltaX = 0;
12+
private adjustDeltaY = 0;
13+
14+
private currentScale = 1;
15+
private currentDeltaX = 0;
16+
private currentDeltaY = 0;
17+
18+
constructor(private component: ImageViewerComponent, element: any, private platform: Platform, private renderer: Renderer) {
19+
super(element.nativeElement);
20+
21+
// Force both directions after super to avoid override allowing only one direction
22+
this.options({ direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL });
23+
24+
this.listen();
25+
this.on('pinch', (e) => this.onPinch(e));
26+
this.on('pinchend', (e) => this.onPinchEnd(e));
27+
this.on('pan', (e) => this.onPan(e));
28+
this.on('panend', (e) => this.onPanEnd(e));
29+
this.on('doubletap', (e) => this.onDoubleTap(e));
30+
}
31+
32+
onPinch(event) {
33+
this.component.dragGesture.abort(event);
34+
35+
this.currentScale = Math.max(1, Math.min(MAX_SCALE, this.adjustScale * event.scale));
36+
37+
this.currentDeltaX = this.adjustDeltaX + (event.deltaX / this.currentScale);
38+
this.currentDeltaY = this.adjustDeltaY + (event.deltaY / this.currentScale);
39+
40+
this.setImageContainerTransform();
41+
}
42+
43+
onPinchEnd(event) {
44+
this.component.isZoomed = (this.currentScale !== 1);
45+
46+
if (!this.component.isZoomed) {
47+
48+
new Animation(this.platform, this.element)
49+
.fromTo('translateX', `${this.currentDeltaX}px`, '0')
50+
.fromTo('translateY', `${this.currentDeltaY}px`, '0')
51+
.easing('ease-in')
52+
.duration(50)
53+
.play();
54+
55+
this.currentDeltaX = 0;
56+
this.currentDeltaY = 0;
57+
}
58+
59+
// Saving the final transforms for adjustment next time the user interacts.
60+
this.adjustScale = this.currentScale;
61+
this.adjustDeltaX = this.currentDeltaX;
62+
this.adjustDeltaY = this.currentDeltaY;
63+
}
64+
65+
onPan(event) {
66+
if (!this.component.isZoomed) {
67+
return;
68+
}
69+
70+
this.currentDeltaX = Math.floor(this.adjustDeltaX + event.deltaX);
71+
this.currentDeltaY = Math.floor(this.adjustDeltaY + event.deltaY);
72+
73+
this.setImageContainerTransform();
74+
}
75+
76+
onPanEnd(event) {
77+
if (!this.component.isZoomed) {
78+
return;
79+
}
80+
81+
const imageWidth = this.element.offsetWidth;
82+
const imageHeight = this.element.offsetHeight;
83+
84+
const isOverOnX = (this.currentDeltaX > imageWidth) || (this.currentDeltaX < -imageWidth);
85+
const isOverOnY = (this.currentDeltaY < -imageHeight) || (this.currentDeltaY > imageHeight);
86+
87+
if (isOverOnX || isOverOnY) {
88+
const correctedX = Math.min(imageWidth, Math.max(this.currentDeltaX, -imageWidth));
89+
const correctedY = Math.min(imageHeight, Math.min(this.currentDeltaX, -imageHeight));
90+
91+
new Animation(this.platform, this.element)
92+
.fromTo('translateX', `${this.currentDeltaX}px`, `${correctedX}px`)
93+
.fromTo('translateY', `${this.currentDeltaY}px`, `${correctedY}px`)
94+
.fromTo('scale', this.currentScale, this.currentScale)
95+
.easing('ease-in')
96+
.duration(50)
97+
.play();
98+
99+
this.currentDeltaX = correctedX;
100+
this.currentDeltaY = correctedY;
101+
}
102+
103+
this.adjustDeltaX = this.currentDeltaX;
104+
this.adjustDeltaY = this.currentDeltaY;
105+
}
106+
107+
onDoubleTap(event) {
108+
this.component.isZoomed = !this.component.isZoomed;
109+
if (this.component.isZoomed) {
110+
this.currentScale = MAX_SCALE;
111+
} else {
112+
this.currentScale = 1;
113+
114+
this.adjustDeltaX = this.currentDeltaX = 0;
115+
this.adjustDeltaY = this.currentDeltaY = 0;
116+
}
117+
118+
this.adjustScale = this.currentScale;
119+
this.setImageContainerTransform();
120+
}
121+
122+
setImageContainerTransform() {
123+
const transforms = [];
124+
transforms.push(`scale(${this.currentScale})`);
125+
transforms.push(`translate(${this.currentDeltaX}px, ${this.currentDeltaY}px)`);
126+
127+
this.renderer.setElementStyle(this.element, this.platform.Css.transform, transforms.join(' '));
128+
}
129+
}

src/image-viewer.component.ts

Lines changed: 7 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ import { DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from 'ionic-angular/gestures
1414
import { ElementRef, Renderer, Component, OnInit, OnDestroy, AfterViewInit, NgZone, ViewChild } from '@angular/core';
1515
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
1616

17-
import { ImageViewerGesture } from './image-viewer-gesture';
17+
import { ImageViewerTransitionGesture } from './image-viewer-transition-gesture';
18+
import { ImageViewerZoomGesture } from './image-viewer-zoom-gesture';
1819
import { ImageViewerEnter, ImageViewerLeave } from './image-viewer-transitions';
1920

20-
const DOUBLE_TAP_INTERVAL = 300;
21-
const MAX_SCALE = 2;
22-
2321
@Component({
2422
selector: 'image-viewer',
2523
template: `
@@ -32,27 +30,19 @@ const MAX_SCALE = 2;
3230
3331
<div class="image-wrapper">
3432
<div class="image" #imageContainer>
35-
<img [src]="imageUrl" (click)="onImageClick()" tappable />
33+
<img [src]="imageUrl" tappable />
3634
</div>
3735
</div>
3836
`
3937
})
4038
export class ImageViewerComponent extends Ion implements OnInit, OnDestroy, AfterViewInit {
4139
public imageUrl: SafeUrl;
4240

43-
private dragGesture: PanGesture;
41+
public dragGesture: ImageViewerTransitionGesture;
4442

4543
@ViewChild('imageContainer') imageContainer;
46-
private pinchGesture: Gesture;
47-
private adjustScale = 1;
48-
private adjustDeltaX = 0;
49-
private adjustDeltaY = 0;
50-
51-
private currentScale: number = 1;
52-
private currentDeltaX = null;
53-
private currentDeltaY = null;
44+
private pinchGesture: ImageViewerZoomGesture;
5445

55-
private dblClickInAction: boolean;
5646
public isZoomed: boolean;
5747

5848
constructor(
@@ -75,86 +65,16 @@ export class ImageViewerComponent extends Ion implements OnInit, OnDestroy, Afte
7565

7666
ngOnInit() {
7767
let gestureCallBack = () => this._nav.pop();
78-
this._zone.runOutsideAngular(() => this.dragGesture = new ImageViewerGesture(this.platform, this, this.domCtrl, gestureCallBack));
68+
this._zone.runOutsideAngular(() => this.dragGesture = new ImageViewerTransitionGesture(this.platform, this, this.domCtrl, this.renderer, gestureCallBack));
7969
}
8070

8171
ngAfterViewInit() {
8272
// imageContainer is set after the view has been initialized
83-
this._zone.runOutsideAngular(() => {
84-
this.pinchGesture = new Gesture(this.imageContainer.nativeElement, {direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL});
85-
this.pinchGesture.listen();
86-
this.pinchGesture.on('pinch', (e) => this.onPinch(e));
87-
this.pinchGesture.on('pinchend', (e) => this.onPinchEnd(e));
88-
this.pinchGesture.on('pan', (e) => this.onPan(e));
89-
});
73+
this._zone.runOutsideAngular(() => this.pinchGesture = new ImageViewerZoomGesture(this, this.imageContainer, this.platform, this.renderer));
9074
}
9175

9276
ngOnDestroy() {
9377
this.dragGesture && this.dragGesture.destroy();
9478
this.pinchGesture && this.pinchGesture.destroy();
9579
}
96-
97-
onImageClick() {
98-
if (this.dblClickInAction) {
99-
this.onImageDblClick();
100-
} else {
101-
this.dblClickInAction = true;
102-
setTimeout(() => this.dblClickInAction = false, DOUBLE_TAP_INTERVAL);
103-
}
104-
}
105-
106-
onImageDblClick() {
107-
// Clear eventual transforms caused by a previous pinch
108-
this.imageContainer.nativeElement.style.transform = '';
109-
110-
this.isZoomed = !this.isZoomed;
111-
112-
if (this.isZoomed) {
113-
this.currentScale === 2;
114-
}
115-
116-
this.renderer.setElementClass(this.imageContainer.nativeElement, 'zoom', this.isZoomed);
117-
}
118-
119-
onPinch(event) {
120-
this.dragGesture.abort(event);
121-
122-
this.currentScale = Math.max(1, Math.min(MAX_SCALE, this.adjustScale * event.scale));
123-
124-
if (this.currentScale === 1) {
125-
this.currentDeltaX = 0;
126-
this.currentDeltaY = 0;
127-
} else {
128-
this.currentDeltaX = this.adjustDeltaX + (event.deltaX / this.currentScale);
129-
this.currentDeltaY = this.adjustDeltaY + (event.deltaY / this.currentScale);
130-
}
131-
132-
this.setImageContainerTransform();
133-
}
134-
135-
onPan(event) {
136-
if (this.isZoomed) {
137-
this.currentDeltaX = this.adjustDeltaX + event.deltaX;
138-
this.currentDeltaY = this.adjustDeltaY + event.deltaY;
139-
140-
this.setImageContainerTransform();
141-
}
142-
}
143-
144-
onPinchEnd(event) {
145-
this.isZoomed = (this.currentScale !== 1);
146-
147-
// Saving the final transforms for adjustment next time the user interacts.
148-
this.adjustScale = this.currentScale;
149-
this.adjustDeltaX = this.currentDeltaX;
150-
this.adjustDeltaY = this.currentDeltaY;
151-
}
152-
153-
setImageContainerTransform() {
154-
const transforms = [];
155-
transforms.push(`scale(${this.currentScale})`);
156-
transforms.push(`translate(${this.currentDeltaX}px, ${this.currentDeltaY}px)`);
157-
158-
this.renderer.setElementStyle(this.imageContainer.nativeElement, 'transform', transforms.join(' '));
159-
}
16080
}

src/image-viewer.scss

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,6 @@ image-viewer.ion-page {
4646

4747
.image {
4848
will-change: transform;
49-
transform-origin: 0 0;
50-
51-
&.zoom {
52-
transform: scale(2);
53-
}
5449
}
5550

5651
img {
@@ -59,8 +54,5 @@ image-viewer.ion-page {
5954
max-width: 100%;
6055
max-height: 100%;
6156
margin: 0 auto;
62-
63-
transition: transform .2s ease-in-out;
64-
transform-origin: 0 0;
6557
}
6658
}

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"src/image-viewer.component.ts",
2828
"src/image-viewer.directive.ts",
2929
"src/image-viewer.ts",
30-
"src/image-viewer-gesture.ts",
30+
"src/image-viewer-transition-gesture.ts",
31+
"src/image-viewer-zoom-gesture.ts",
3132
"src/image-viewer-transitions.ts",
3233
"src/module.ts"
3334
],

0 commit comments

Comments
 (0)