-
Notifications
You must be signed in to change notification settings - Fork 3
/
Listener.ts
190 lines (164 loc) · 6.05 KB
/
Listener.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import { Overlay } from './Overlay';
import { Zoomable } from './Zoomable';
import { ZoomedElement } from './element/ZoomedElement';
import { ZoomedImageElement } from './element/ZoomedImageElement';
import { ZoomedVideoElement } from './element/ZoomedVideoElement';
import { FULL_SRC_KEY, ZOOM_FUNCTION_KEY, ZOOM_IN_VALUE, ZOOM_OUT_VALUE } from './util/Attributes';
import { Dimensions } from './util/Dimensions';
import './zoom.scss';
/**
* The key code for the Esc key.
*/
const ESCAPE_KEY_CODE: number = 27;
/**
* The amount of pixels required to scroll vertically with a scroll wheel or keyboard to dismiss a zoomed element.
*/
const SCROLL_Y_DELTA: number = 70;
/**
* The amount of pixels required to scroll vertically with a touch screen to dismiss a zoomed element.
*/
const TOUCH_Y_DELTA: number = 30;
/**
* Entry point to the library.
*/
export class Listener {
/**
* Executes a function when the DOM is fully loaded.
* @param fn The function to execute.
* @see http://youmightnotneedjquery.com/#ready
*/
private static ready(fn: Function): void {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
fn();
});
} else {
fn();
}
}
/**
* The {@link Overlay}.
*/
private _overlay: Overlay = new Overlay();
/**
* The current {@link ZoomedElement}.
*/
private _current: ZoomedElement;
/**
* The value calculated from {@link Dimensions.scrollY} when a {@link ZoomedElement} is first opened.
*/
private _initialScrollY: number;
/**
* The Y value of a touch event when a {@link ZoomedElement} is first opened.
*/
private _initialTouchY: number;
/**
* Listens for click events on {@link Zoomable} elements and adds the {@link _overlay} to the document.
*/
listen(): void {
Listener.ready(() => {
document.body.addEventListener('click', (event: MouseEvent) => {
let target: Zoomable = event.target as Zoomable;
let operation: string = target.getAttribute(ZOOM_FUNCTION_KEY);
if (operation === ZOOM_IN_VALUE) {
this.zoom(event);
} else if (operation === ZOOM_OUT_VALUE) {
this.close();
}
});
this._overlay.add();
});
}
/**
* Zooms in on an element.
* @param event The click event that occurred when interacting with the element.
*/
private zoom(event: MouseEvent): void {
event.stopPropagation();
if (this._overlay.state !== 'hidden') {
return;
}
let target: Zoomable = event.target as Zoomable;
if (event.metaKey || event.ctrlKey) {
let url: string = target.getAttribute(FULL_SRC_KEY) || target.currentSrc || target.src;
window.open(url, '_blank');
return;
}
if (target.width >= window.innerWidth) {
// target is already as big (or bigger), therefore we gain nothing from zooming in on it
return;
}
if (target.tagName === 'IMG' || target.tagName === 'PICTURE') {
this._current = new ZoomedImageElement(target, this._overlay);
} else { /* target.tagName === 'VIDEO */
this._current = new ZoomedVideoElement(target, this._overlay);
}
this._current.open(() => {
this.addCloseListeners();
this._initialScrollY = Dimensions.scrollY();
});
}
/**
* Closes {@link _current}.
*/
private close(): void {
if (this._current) {
this._current.close();
this.removeCloseListeners();
this._current = undefined;
}
}
/**
* Adds event listeners to the page to listen for element dismissal.
*/
private addCloseListeners(): void {
window.addEventListener('scroll', this.scrollListener);
document.addEventListener('keyup', this.keyboardListener);
document.addEventListener('touchstart', this.touchStartListener);
}
/**
* Removes the event listeners that were listening for element dismissal.
*/
private removeCloseListeners(): void {
window.removeEventListener('scroll', this.scrollListener);
document.removeEventListener('keyup', this.keyboardListener);
document.removeEventListener('touchstart', this.touchStartListener);
}
/**
* An event listener that calls {@link close} if the difference between the {@link _initialScrollY} and
* {@link Dimensions.scrollY} is more than {@link SCROLL_Y_DELTA}.
*/
private scrollListener: EventListener = () => {
if (Math.abs(this._initialScrollY - Dimensions.scrollY()) > SCROLL_Y_DELTA) {
this.close();
}
};
/**
* An event listener that calls {@link close} if the event's key code was the {@link ESCAPE_KEY_CODE}.
* @param event The keyboard event.
*/
private keyboardListener: EventListener = (event: KeyboardEvent) => {
if (event.keyCode === ESCAPE_KEY_CODE) {
this.close();
}
};
/**
* An event listener that records the event's {@link _initialTouchY} and then adds {@link touchMoveListener}.
* @param event The touch start event.
*/
private touchStartListener: EventListener = (event: TouchEvent) => {
this._initialTouchY = event.touches[0].pageY;
event.target.addEventListener('touchmove', this.touchMoveListener);
};
/**
* An event listener that calls {@link close} and removes itself if the difference between
* {@link _initialTouchY} and the current touch Y position is more than {@link TOUCH_Y_DELTA}.
* @param event The touch movement event.
*/
private touchMoveListener: EventListener = (event: TouchEvent) => {
if (Math.abs(event.touches[0].pageY - this._initialTouchY) > TOUCH_Y_DELTA) {
this.close();
event.target.removeEventListener('touchmove', this.touchMoveListener);
}
};
}