Skip to content

Commit d42a1cb

Browse files
committed
first commit
0 parents  commit d42a1cb

13 files changed

+474
-0
lines changed

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Image viewer
2+
3+
Ionic 2 plugin providing a Twitter inspired experience to visualize pictures.
4+
5+
![Plugin preview](https://raw.githubusercontent.com/username/projectname/branch/path/to/img.png)
6+
7+
## Demo
8+
9+
[link to plunkr]
10+
11+
## Installation
12+
13+
Make sure you have Ionic and Angular installed.
14+
15+
```
16+
npm install TODO
17+
```
18+
19+
## Usage
20+
21+
Import the image viewer directive in your component, and add the `imageViewer` property to the pictures :
22+
23+
```typescript
24+
import {ImageViewerDirective} from '../image-viewer/image-viewer.directive';
25+
26+
27+
@Component({
28+
template: `<img [src]="url" imageViewer />`,
29+
directives: [ImageViewerDirective]
30+
})
31+
class MyComponent {
32+
33+
}
34+
```
35+
36+
# Contributing
37+
38+
Please read contributing guidelines here.

ionic-img-viewer.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {ImageViewerDirective} from './src/image-viewer.directive';

package.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "ionic-img-viewer",
3+
"version": "1.0.0",
4+
"description": "Ionic 2 component providing a Twitter inspired experience to visualize pictures.",
5+
"main": "ionic-img-viewer.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"prepublish": "tsc"
9+
},
10+
"typings": "./ionic-img-viewer.d.ts",
11+
"repository": {
12+
"type": "git",
13+
"url": "TODO"
14+
},
15+
"keywords": [
16+
"ionic2",
17+
"image",
18+
"angular2",
19+
"component"
20+
],
21+
"author": "Orion Charlier",
22+
"license": "MIT",
23+
"dependencies": {
24+
"@angular/common": "^2.0.0-rc.4",
25+
"@angular/compiler": "^2.0.0-rc.4",
26+
"@angular/core": "^2.0.0-rc.4",
27+
"@angular/forms": "^0.2.0",
28+
"@angular/http": "^2.0.0-rc.4",
29+
"@angular/platform-browser": "^2.0.0-rc.4",
30+
"@angular/platform-browser-dynamic": "^2.0.0-rc.4",
31+
"ionic-angular": "^2.0.0-beta.11",
32+
"rxjs": "^5.0.0-beta.6",
33+
"zone.js": "^0.6.12"
34+
},
35+
"devDependencies": {
36+
"typescript": "^1.8.10",
37+
"typings": "^1.3.2"
38+
}
39+
}

src/image-viewer-gesture.ts

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {ImageViewerComponent} from './image-viewer.component';
2+
import {PanGesture} from 'ionic-angular/gestures/drag-gesture';
3+
import {GesturePriority} from 'ionic-angular/gestures/gesture-controller';
4+
import {CSS, nativeRaf, pointerCoord} from 'ionic-angular/util/dom';
5+
import {Animation} from 'ionic-angular';
6+
7+
const HAMMER_THRESHOLD = 10;
8+
const MAX_ATTACK_ANGLE = 45;
9+
const DRAG_THRESHOLD = 70;
10+
11+
export class ImageViewerGesture extends PanGesture {
12+
13+
private translationY: number;
14+
private opacity: number;
15+
private startY: number;
16+
private imageContainer: HTMLElement;
17+
private backdrop: HTMLElement;
18+
19+
constructor(private component: ImageViewerComponent, private cb: Function) {
20+
super(component.getNativeElement(), {
21+
maxAngle: MAX_ATTACK_ANGLE,
22+
threshold: HAMMER_THRESHOLD,
23+
gesture: component._gestureCtrl.create('image-viewer', { priority: GesturePriority.Normal }),
24+
direction: 'y'
25+
});
26+
27+
this.translationY = 0;
28+
this.imageContainer = <HTMLElement>component.getNativeElement().querySelector('.image');
29+
this.backdrop = <HTMLElement>component.getNativeElement().querySelector('ion-backdrop');
30+
31+
this.listen();
32+
}
33+
34+
onDragStart(ev: any): boolean {
35+
let coord = pointerCoord(ev);
36+
this.startY = coord.y;
37+
return true;
38+
}
39+
40+
canStart(): boolean {
41+
return !this.component.isZoomed;
42+
}
43+
44+
onDragMove(ev: any): boolean {
45+
let coord = pointerCoord(ev);
46+
this.translationY = coord.y - this.startY;
47+
this.opacity = Math.max(1 - Math.abs(this.translationY) / (10 * DRAG_THRESHOLD), .5);
48+
49+
nativeRaf(() => {
50+
this.imageContainer.style[<any>CSS.transform] = `translateY(${this.translationY}px)`;
51+
this.backdrop.style['opacity'] = this.opacity.toString();
52+
});
53+
54+
return true;
55+
}
56+
57+
onDragEnd(ev: any): boolean {
58+
59+
if (Math.abs(this.translationY) > DRAG_THRESHOLD) {
60+
this.cb();
61+
} else {
62+
let imageContainerAnimation = new Animation(this.imageContainer);
63+
let backdropAnimation = new Animation(this.backdrop);
64+
65+
backdropAnimation.fromTo('opacity', this.opacity, '1');
66+
imageContainerAnimation.fromTo('translateY', `${this.translationY}px`, '0px');
67+
68+
new Animation()
69+
.easing('ease-in')
70+
.duration(150)
71+
.add(backdropAnimation)
72+
.add(imageContainerAnimation)
73+
.play();
74+
}
75+
76+
return true;
77+
}
78+
}

src/image-viewer-transitions.ts

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {Transition, TransitionOptions, Animation, ViewController} from 'ionic-angular';
2+
import {CSS} from 'ionic-angular/util/dom';
3+
4+
export class ImageViewerEnter extends Transition {
5+
constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
6+
super(enteringView, leavingView, opts);
7+
8+
let ele = enteringView.pageRef().nativeElement;
9+
10+
let fromPosition = enteringView.data.position;
11+
let toPosition = ele.querySelector('img').getBoundingClientRect();
12+
let flipS = fromPosition.width / toPosition.width;
13+
let flipY = fromPosition.top - toPosition.top;
14+
let flipX = fromPosition.left - toPosition.left;
15+
16+
let backdrop = new Animation(ele.querySelector('ion-backdrop'));
17+
let image = new Animation(ele.querySelector('.image'));
18+
19+
image.fromTo('translateY', `${flipY}px`, '0px')
20+
.fromTo('translateX', `${flipX}px`, '0px')
21+
.fromTo('scale', flipS, '1');
22+
23+
backdrop.fromTo('opacity', '0.01', '1');
24+
25+
this.easing('ease-in')
26+
.duration(150)
27+
.add(backdrop)
28+
.add(image);
29+
30+
let enteringNavBar = new Animation(enteringView.navbarRef());
31+
enteringNavBar.before.addClass('show-navbar');
32+
this.add(enteringNavBar);
33+
34+
let enteringBackButton = new Animation(enteringView.backBtnRef());
35+
this.add(enteringBackButton);
36+
enteringBackButton.before.addClass('show-back-button');
37+
}
38+
}
39+
40+
export class ImageViewerLeave extends Transition {
41+
constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) {
42+
super(enteringView, leavingView, opts);
43+
44+
let ele = leavingView.pageRef().nativeElement;
45+
46+
let toPosition = leavingView.data.position;
47+
let fromPosition = ele.querySelector('img').getBoundingClientRect();
48+
49+
let offsetY = 0;
50+
let imageYOffset = ele.querySelector('.image').style[CSS.transform];
51+
if (imageYOffset) {
52+
let regexResult = imageYOffset.match(/translateY\((-?\d+)px\)/);
53+
offsetY = regexResult ? parseFloat(regexResult[1]) : offsetY;
54+
}
55+
56+
let flipS = toPosition.width / fromPosition.width;
57+
let flipY = toPosition.top - fromPosition.top + offsetY;
58+
let flipX = toPosition.left - fromPosition.left;
59+
60+
let backdropOpacity = ele.querySelector('ion-backdrop').style['opacity'];
61+
62+
let backdrop = new Animation(ele.querySelector('ion-backdrop'));
63+
let image = new Animation(ele.querySelector('.image'));
64+
65+
image.fromTo('translateY', `${offsetY}px`, `${flipY}px`)
66+
.fromTo('translateX', `0px`, `${flipX}px`)
67+
.fromTo('scale', '1', flipS);
68+
69+
backdrop.fromTo('opacity', backdropOpacity, '0');
70+
71+
this.easing('ease-in-out')
72+
.duration(150)
73+
.add(backdrop)
74+
.add(image);
75+
}
76+
}

src/image-viewer.component.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import {NavController, NavParams, Transition} from 'ionic-angular';
2+
import {Ion} from 'ionic-angular/components/ion';
3+
import {PanGesture} from 'ionic-angular/gestures/drag-gesture';
4+
import {GestureController} from 'ionic-angular/gestures/gesture-controller';
5+
import {ElementRef, Renderer, Component, OnInit, OnDestroy, NgZone} from '@angular/core';
6+
7+
import {ImageViewerGesture} from './image-viewer-gesture';
8+
import {ImageViewerEnter, ImageViewerLeave} from './image-viewer-transitions';
9+
10+
const DOUBLE_TAP_INTERVAL = 300;
11+
12+
@Component({
13+
selector: 'image-viewer',
14+
template: `
15+
<ion-header>
16+
<ion-navbar>
17+
</ion-navbar>
18+
</ion-header>
19+
20+
<ion-backdrop></ion-backdrop>
21+
22+
<div class="image-wrapper">
23+
<div class="image">
24+
<img [src]="d.image" (click)="onImageClick()" />
25+
</div>
26+
</div>
27+
`
28+
})
29+
export class ImageViewerComponent extends Ion implements OnInit, OnDestroy {
30+
private d: {cssClass: string};
31+
private created: number;
32+
33+
private computedHeight: number;
34+
private computedWidth: number;
35+
36+
private dragGesture: PanGesture;
37+
private dblClickInAction: boolean;
38+
public isZoomed: boolean;
39+
40+
constructor(
41+
public _gestureCtrl: GestureController,
42+
private _nav: NavController,
43+
private _elementRef: ElementRef,
44+
private _zone: NgZone,
45+
private renderer: Renderer,
46+
params: NavParams
47+
) {
48+
super(_elementRef);
49+
50+
this.d = params.data;
51+
this.created = Date.now();
52+
53+
if (this.d.cssClass) {
54+
this.d.cssClass.split(' ').forEach(cssClass => {
55+
renderer.setElementClass(this.getNativeElement(), cssClass, true);
56+
});
57+
}
58+
}
59+
60+
ngOnInit() {
61+
let gestureCallBack = () => this._nav.pop();
62+
this._zone.runOutsideAngular(() => this.dragGesture = new ImageViewerGesture(this, gestureCallBack));
63+
}
64+
65+
ngOnDestroy() {
66+
this.dragGesture && this.dragGesture.destroy();
67+
}
68+
69+
onImageClick() {
70+
if (this.dblClickInAction) {
71+
this.isZoomed = !this.isZoomed;
72+
this.renderer.setElementClass(this.getNativeElement(), 'zoom', this.isZoomed);
73+
} else {
74+
this.dblClickInAction = true;
75+
setTimeout(() => this.dblClickInAction = false, DOUBLE_TAP_INTERVAL);
76+
}
77+
}
78+
}
79+
80+
Transition.register('image-viewer-enter', ImageViewerEnter);
81+
Transition.register('image-viewer-leave', ImageViewerLeave);

src/image-viewer.directive.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {App} from 'ionic-angular';
2+
import {ElementRef, HostListener, Directive} from '@angular/core';
3+
4+
import {ImageViewer} from './image-viewer';
5+
6+
@Directive({
7+
selector: '[imageViewer]'
8+
})
9+
export class ImageViewerDirective {
10+
11+
constructor(
12+
private _app: App,
13+
private _el: ElementRef
14+
) { }
15+
16+
@HostListener('click', ['$event.target'])
17+
onClick(): void {
18+
let position = this._el.nativeElement.getBoundingClientRect();
19+
20+
let imageViewer = ImageViewer.create({image: this._el.nativeElement.src, position: position});
21+
this._app.present(imageViewer);
22+
}
23+
}

0 commit comments

Comments
 (0)