jack vor 8 Jahren
Ursprung
Commit
9d9bc42267
3 geänderte Dateien mit 879 neuen und 0 gelöschten Zeilen
  1. 175 0
      app/css/previewImage.css
  2. 382 0
      app/js/public/mui.previewimage.js
  3. 322 0
      app/js/public/mui.zoom.js

+ 175 - 0
app/css/previewImage.css

@ -0,0 +1,175 @@
1
.mui-preview-image.mui-fullscreen {
2
	position: fixed;
3
	z-index: 20;
4
	background-color: #000;
5
}
6
7
.mui-preview-header,
8
.mui-preview-footer {
9
	position: absolute;
10
	width: 100%;
11
	left: 0;
12
	z-index: 10;
13
}
14
15
.mui-preview-header {
16
	height: 44px;
17
	top: 0;
18
}
19
20
.mui-preview-footer {
21
	height: 50px;
22
	bottom: 0px;
23
}
24
25
.mui-preview-header .mui-preview-indicator {
26
	display: block;
27
	line-height: 25px;
28
	color: #fff;
29
	text-align: center;
30
	margin: 15px auto 4;
31
	width: 70px;
32
	background-color: rgba(0, 0, 0, 0.4);
33
	border-radius: 12px;
34
	font-size: 16px;
35
	display:none;
36
}
37
38
.mui-preview-image {
39
	display: none;
40
	-webkit-animation-duration: 0.5s;
41
	animation-duration: 0.5s;
42
	-webkit-animation-fill-mode: both;
43
	animation-fill-mode: both;
44
}
45
46
.mui-preview-image.mui-preview-in {
47
	-webkit-animation-name: fadeIn;
48
	animation-name: fadeIn;
49
}
50
51
.mui-preview-image.mui-preview-out {
52
	background: none;
53
	-webkit-animation-name: fadeOut;
54
	animation-name: fadeOut;
55
}
56
57
.mui-preview-image.mui-preview-out .mui-preview-header,
58
.mui-preview-image.mui-preview-out .mui-preview-footer {
59
	display: none;
60
}
61
62
.mui-zoom-scroller {
63
	position: absolute;
64
	display: -webkit-box;
65
	display: -webkit-flex;
66
	display: flex;
67
	-webkit-box-align: center;
68
	-webkit-align-items: center;
69
	align-items: center;
70
	-webkit-box-pack: center;
71
	-webkit-justify-content: center;
72
	justify-content: center;
73
	left: 0;
74
	right: 0;
75
	bottom: 0;
76
	top: 0;
77
	width: 100%;
78
	height: 100%;
79
	margin: 0;
80
	-webkit-backface-visibility: hidden;
81
}
82
83
.mui-zoom {
84
	-webkit-transform-style: preserve-3d;
85
	transform-style: preserve-3d;
86
}
87
88
.mui-slider .mui-slider-group .mui-slider-item img {
89
	width: auto;
90
	height: auto;
91
	max-width: 100%;
92
	max-height: 100%;
93
}
94
95
.mui-android-4-1 .mui-slider .mui-slider-group .mui-slider-item img {
96
	width: 100%;
97
}
98
99
.mui-android-4-1 .mui-slider.mui-preview-image .mui-slider-group .mui-slider-item {
100
	display: inline-table;
101
}
102
103
.mui-android-4-1 .mui-slider.mui-preview-image .mui-zoom-scroller img {
104
	display: table-cell;
105
	vertical-align: middle;
106
}
107
108
.mui-preview-loading {
109
	position: absolute;
110
	width: 100%;
111
	height: 100%;
112
	top: 0;
113
	left: 0;
114
	display: none;
115
}
116
117
.mui-preview-loading.mui-active {
118
	display: block;
119
}
120
121
.mui-preview-loading .mui-spinner-white {
122
	position: absolute;
123
	top: 50%;
124
	left: 50%;
125
	margin-left: -25px;
126
	margin-top: -25px;
127
	height: 50px;
128
	width: 50px;
129
}
130
131
.mui-preview-image img.mui-transitioning {
132
	-webkit-transition: -webkit-transform 0.5s ease, opacity 0.5s ease;
133
	transition: transform 0.5s ease, opacity 0.5s ease;
134
}
135
136
@-webkit-keyframes fadeIn {
137
	0% {
138
		opacity: 0;
139
	}
140
	100% {
141
		opacity: 1;
142
	}
143
}
144
145
@keyframes fadeIn {
146
	0% {
147
		opacity: 0;
148
	}
149
	100% {
150
		opacity: 1;
151
	}
152
}
153
154
@-webkit-keyframes fadeOut {
155
	0% {
156
		opacity: 1;
157
	}
158
	100% {
159
		opacity: 0;
160
	}
161
}
162
163
@keyframes fadeOut {
164
	0% {
165
		opacity: 1;
166
	}
167
	100% {
168
		opacity: 0;
169
	}
170
}
171
172
p img {
173
	max-width: 100%;
174
	height: auto;
175
}

+ 382 - 0
app/js/public/mui.previewimage.js

@ -0,0 +1,382 @@
1
(function($, window) {
2
3
	var template = '<div id="{{id}}" class="mui-slider mui-preview-image mui-fullscreen"><div class="mui-preview-header">{{header}}</div><div class="mui-slider-group"></div><div class="mui-preview-footer mui-hidden">{{footer}}</div><div class="mui-preview-loading"><span class="mui-spinner mui-spinner-white"></span></div></div>';
4
	var itemTemplate = '<div class="mui-slider-item mui-zoom-wrapper {{className}}"><div class="mui-zoom-scroller"><img src="{{src}}" data-preview-lazyload="{{lazyload}}" style="{{style}}" class="mui-zoom"></div></div>';
5
	var defaultGroupName = '__DEFAULT';
6
	var div = document.createElement('div');
7
	var imgId = 0;
8
	var PreviewImage = function(options) {
9
		this.options = $.extend(true, {
10
			id: '__MUI_PREVIEWIMAGE',
11
			zoom: true,
12
			header: '<span class="mui-preview-indicator"></span>',
13
			footer: ''
14
		}, options || {});
15
		this.init();
16
		this.initEvent();
17
	};
18
	var proto = PreviewImage.prototype;
19
	proto.init = function() {
20
		var options = this.options;
21
		var el = document.getElementById(this.options.id);
22
		if (!el) {
23
			div.innerHTML = template.replace(/\{\{id\}\}/g, this.options.id).replace('{{header}}', options.header).replace('{{footer}}', options.footer);
24
			document.body.appendChild(div.firstElementChild);
25
			el = document.getElementById(this.options.id);
26
		}
27
28
		this.element = el;
29
		this.scroller = this.element.querySelector($.classSelector('.slider-group'));
30
		this.indicator = this.element.querySelector($.classSelector('.preview-indicator'));
31
		this.loader = this.element.querySelector($.classSelector('.preview-loading'));
32
		if (options.footer) {
33
			this.element.querySelector($.classSelector('.preview-footer')).classList.remove($.className('hidden'));
34
		}
35
		this.addImages();
36
	};
37
	proto.initEvent = function() {
38
		var self = this;
39
		$(document.body).on('tap', 'img[data-preview-src]', function() {
40
			self.open(this);
41
			return false;
42
		});
43
		var laterClose = null;
44
		var laterCloseEvent = function() {
45
			!laterClose && (laterClose = $.later(function() {
46
				self.loader.removeEventListener('tap', laterCloseEvent);
47
				self.scroller.removeEventListener('tap', laterCloseEvent);
48
				self.close();
49
			}, 300));
50
		};
51
		this.scroller.addEventListener('doubletap', function() {
52
			if (laterClose) {
53
				laterClose.cancel();
54
				laterClose = null;
55
			}
56
		});
57
		this.element.addEventListener('webkitAnimationEnd', function() {
58
			if (self.element.classList.contains($.className('preview-out'))) { //close
59
				self.element.style.display = 'none';
60
				self.element.classList.remove($.className('preview-out'));
61
				self.element.classList.remove($.className('preview-in'));
62
				laterClose = null;
63
			} else { //open
64
				self.loader.addEventListener('tap', laterCloseEvent);
65
				self.scroller.addEventListener('tap', laterCloseEvent);
66
			}
67
		});
68
		this.element.addEventListener('slide', function(e) {
69
			if (self.options.zoom) {
70
				var lastZoomerEl = self.element.querySelector('.mui-zoom-wrapper:nth-child(' + (self.lastIndex + 1) + ')');
71
				if (lastZoomerEl) {
72
					$(lastZoomerEl).zoom().setZoom(1);
73
				}
74
			}
75
			var slideNumber = e.detail.slideNumber;
76
			self.lastIndex = slideNumber;
77
			self.indicator && (self.indicator.innerText = (slideNumber + 1) + '/' + self.currentGroup.length);
78
			self._loadItem(slideNumber);
79
80
		});
81
	};
82
	proto.addImages = function(group, index) {
83
		this.groups = {};
84
		var imgs = [];
85
		if (group) {
86
			if (group === defaultGroupName) {
87
				imgs = document.querySelectorAll("img[data-preview-src]:not([data-preview-group])");
88
			} else {
89
				imgs = document.querySelectorAll("img[data-preview-src][data-preview-group='" + group + "']");
90
			}
91
		} else {
92
			imgs = document.querySelectorAll("img[data-preview-src]");
93
		}
94
		if (imgs.length) {
95
			for (var i = 0, len = imgs.length; i < len; i++) {
96
				this.addImage(imgs[i]);
97
			}
98
		}
99
	};
100
	proto.addImage = function(img) {
101
		var group = img.getAttribute('data-preview-group');
102
		group = group || defaultGroupName;
103
		if (!this.groups[group]) {
104
			this.groups[group] = [];
105
		}
106
		var src = img.getAttribute('src');
107
		if (img.__mui_img_data && img.__mui_img_data.src === src) { //已缓存且图片未变化
108
			this.groups[group].push(img.__mui_img_data);
109
		} else {
110
			var lazyload = img.getAttribute('data-preview-src');
111
			if (!lazyload) {
112
				lazyload = src;
113
			}
114
			var imgObj = {
115
				src: src,
116
				lazyload: src === lazyload ? '' : lazyload,
117
				loaded: src === lazyload ? true : false,
118
				sWidth: 0,
119
				sHeight: 0,
120
				sTop: 0,
121
				sLeft: 0,
122
				sScale: 1,
123
				el: img
124
			};
125
			this.groups[group].push(imgObj);
126
			img.__mui_img_data = imgObj;
127
		}
128
	};
129
130
131
	proto.empty = function() {
132
		this.scroller.innerHTML = '';
133
	};
134
	proto._initImgData = function(itemData, imgEl) {
135
		if (!itemData.sWidth) {
136
			var img = itemData.el;
137
			itemData.sWidth = img.offsetWidth;
138
			itemData.sHeight = img.offsetHeight;
139
			var offset = $.offset(img);
140
			itemData.sTop = offset.top;
141
			itemData.sLeft = offset.left;
142
			itemData.sScale = Math.max(itemData.sWidth / window.innerWidth, itemData.sHeight / window.innerHeight);
143
		}
144
		imgEl.style.webkitTransform = 'translate3d(0,0,0) scale(' + itemData.sScale + ')';
145
	};
146
147
	proto._getScale = function(from, to) {
148
		var scaleX = from.width / to.width;
149
		var scaleY = from.height / to.height;
150
		var scale = 1;
151
		if (scaleX <= scaleY) {
152
			scale = from.height / (to.height * scaleX);
153
		} else {
154
			scale = from.width / (to.width * scaleY);
155
		}
156
		return scale;
157
	};
158
	proto._imgTransitionEnd = function(e) {
159
		var img = e.target;
160
		img.classList.remove($.className('transitioning'));
161
		img.removeEventListener('webkitTransitionEnd', this._imgTransitionEnd.bind(this));
162
	};
163
	proto._loadItem = function(index, isOpening) { //TODO 暂时仅支持img
164
		var itemEl = this.scroller.querySelector($.classSelector('.slider-item:nth-child(' + (index + 1) + ')'));
165
		var itemData = this.currentGroup[index];
166
		var imgEl = itemEl.querySelector('img');
167
		this._initImgData(itemData, imgEl);
168
		if (isOpening) {
169
			var posi = this._getPosition(itemData);
170
			imgEl.style.webkitTransitionDuration = '0ms';
171
			imgEl.style.webkitTransform = 'translate3d(' + posi.x + 'px,' + posi.y + 'px,0) scale(' + itemData.sScale + ')';
172
			imgEl.offsetHeight;
173
		}
174
		if (!itemData.loaded && imgEl.getAttribute('data-preview-lazyload')) {
175
			var self = this;
176
			self.loader.classList.add($.className('active'));
177
			//移动位置动画
178
			imgEl.style.webkitTransitionDuration = '0.5s';
179
			imgEl.addEventListener('webkitTransitionEnd', self._imgTransitionEnd.bind(self));
180
			imgEl.style.webkitTransform = 'translate3d(0,0,0) scale(' + itemData.sScale + ')';
181
			this.loadImage(imgEl, function() {
182
				itemData.loaded = true;
183
				imgEl.src = itemData.lazyload;
184
				self._initZoom(itemEl, this.width, this.height);
185
				imgEl.classList.add($.className('transitioning'));
186
				imgEl.addEventListener('webkitTransitionEnd', self._imgTransitionEnd.bind(self));
187
				imgEl.setAttribute('style', '');
188
				imgEl.offsetHeight;
189
				self.loader.classList.remove($.className('active'));
190
			});
191
		} else {
192
			itemData.lazyload && (imgEl.src = itemData.lazyload);
193
			this._initZoom(itemEl, imgEl.width, imgEl.height);
194
			imgEl.classList.add($.className('transitioning'));
195
			imgEl.addEventListener('webkitTransitionEnd', this._imgTransitionEnd.bind(this));
196
			imgEl.setAttribute('style', '');
197
			imgEl.offsetHeight;
198
		}
199
		this._preloadItem(index + 1);
200
		this._preloadItem(index - 1);
201
	};
202
	proto._preloadItem = function(index) {
203
		var itemEl = this.scroller.querySelector($.classSelector('.slider-item:nth-child(' + (index + 1) + ')'));
204
		if (itemEl) {
205
			var itemData = this.currentGroup[index];
206
			if (!itemData.sWidth) {
207
				var imgEl = itemEl.querySelector('img');
208
				this._initImgData(itemData, imgEl);
209
			}
210
		}
211
	};
212
	proto._initZoom = function(zoomWrapperEl, zoomerWidth, zoomerHeight) {
213
		if (!this.options.zoom) {
214
			return;
215
		}
216
		if (zoomWrapperEl.getAttribute('data-zoomer')) {
217
			return;
218
		}
219
		var zoomEl = zoomWrapperEl.querySelector($.classSelector('.zoom'));
220
		if (zoomEl.tagName === 'IMG') {
221
			var self = this;
222
			var maxZoom = self._getScale({
223
				width: zoomWrapperEl.offsetWidth,
224
				height: zoomWrapperEl.offsetHeight
225
			}, {
226
				width: zoomerWidth,
227
				height: zoomerHeight
228
			});
229
			$(zoomWrapperEl).zoom({
230
				maxZoom: Math.max(maxZoom, 1)
231
			});
232
		} else {
233
			$(zoomWrapperEl).zoom();
234
		}
235
	};
236
	proto.loadImage = function(imgEl, callback) {
237
		var onReady = function() {
238
			callback && callback.call(this);
239
		};
240
		var img = new Image();
241
		img.onload = onReady;
242
		img.onerror = onReady;
243
		img.src = imgEl.getAttribute('data-preview-lazyload');
244
	};
245
	proto.getRangeByIndex = function(index, length) {
246
		return {
247
			from: 0,
248
			to: length - 1
249
		};
250
		//		var from = Math.max(index - 1, 0);
251
		//		var to = Math.min(index + 1, length);
252
		//		if (index === length - 1) {
253
		//			from = Math.max(length - 3, 0);
254
		//			to = length - 1;
255
		//		}
256
		//		if (index === 0) {
257
		//			from = 0;
258
		//			to = Math.min(2, length - 1);
259
		//		}
260
		//		return {
261
		//			from: from,
262
		//			to: to
263
		//		};
264
	};
265
266
	proto._getPosition = function(itemData) {
267
		var sLeft = itemData.sLeft - window.pageXOffset;
268
		var sTop = itemData.sTop - window.pageYOffset;
269
		var left = (window.innerWidth - itemData.sWidth) / 2;
270
		var top = (window.innerHeight - itemData.sHeight) / 2;
271
		return {
272
			left: sLeft,
273
			top: sTop,
274
			x: sLeft - left,
275
			y: sTop - top
276
		};
277
	};
278
	proto.refresh = function(index, groupArray) {
279
		this.currentGroup = groupArray;
280
		//重新生成slider
281
		var length = groupArray.length;
282
		var itemHtml = [];
283
		var currentRange = this.getRangeByIndex(index, length);
284
		var from = currentRange.from;
285
		var to = currentRange.to + 1;
286
		var currentIndex = index;
287
		var className = '';
288
		var itemStr = '';
289
		var wWidth = window.innerWidth;
290
		var wHeight = window.innerHeight;
291
		for (var i = 0; from < to; from++, i++) {
292
			var itemData = groupArray[from];
293
			var style = '';
294
			if (itemData.sWidth) {
295
				style = '-webkit-transform:translate3d(0,0,0) scale(' + itemData.sScale + ');transform:translate3d(0,0,0) scale(' + itemData.sScale + ')';
296
			}
297
			itemStr = itemTemplate.replace('{{src}}', itemData.src).replace('{{lazyload}}', itemData.lazyload).replace('{{style}}', style);
298
			if (from === index) {
299
				currentIndex = i;
300
				className = $.className('active');
301
			} else {
302
				className = '';
303
			}
304
			itemHtml.push(itemStr.replace('{{className}}', className));
305
		}
306
		this.scroller.innerHTML = itemHtml.join('');
307
		this.element.style.display = 'block';
308
		this.element.classList.add($.className('preview-in'));
309
		this.lastIndex = currentIndex;
310
		this.element.offsetHeight;
311
		$(this.element).slider().gotoItem(currentIndex, 0);
312
		this.indicator && (this.indicator.innerText = (currentIndex + 1) + '/' + this.currentGroup.length);
313
		this._loadItem(currentIndex, true);
314
	};
315
	proto.openByGroup = function(index, group) {
316
		index = Math.min(Math.max(0, index), this.groups[group].length - 1);
317
		this.refresh(index, this.groups[group]);
318
	};
319
	proto.open = function(index, group) {
320
		if (this.isShown()) {
321
			return;
322
		}
323
		if (typeof index === "number") {
324
			group = group || defaultGroupName;
325
			this.addImages(group, index); //刷新当前group
326
			this.openByGroup(index, group);
327
		} else {
328
			group = index.getAttribute('data-preview-group');
329
			group = group || defaultGroupName;
330
			this.addImages(group, index); //刷新当前group
331
			this.openByGroup(this.groups[group].indexOf(index.__mui_img_data), group);
332
		}
333
	};
334
	proto.close = function(index, group) {
335
		if (!this.isShown()) {
336
			return;
337
		}
338
		this.element.classList.remove($.className('preview-in'));
339
		this.element.classList.add($.className('preview-out'));
340
		var itemEl = this.scroller.querySelector($.classSelector('.slider-item:nth-child(' + (this.lastIndex + 1) + ')'));
341
		var imgEl = itemEl.querySelector('img');
342
		if (imgEl) {
343
			imgEl.classList.add($.className('transitioning'));
344
			var itemData = this.currentGroup[this.lastIndex];
345
			var posi = this._getPosition(itemData);
346
			var sLeft = posi.left;
347
			var sTop = posi.top;
348
			if (sTop > window.innerHeight || sLeft > window.innerWidth || sTop < 0 || sLeft < 0) { //out viewport
349
				imgEl.style.opacity = 0;
350
				imgEl.style.webkitTransitionDuration = '0.5s';
351
				imgEl.style.webkitTransform = 'scale(' + itemData.sScale + ')';
352
			} else {
353
				if (this.options.zoom) {
354
					$(imgEl.parentNode.parentNode).zoom().toggleZoom(0);
355
				}
356
				imgEl.style.webkitTransitionDuration = '0.5s';
357
				imgEl.style.webkitTransform = 'translate3d(' + posi.x + 'px,' + posi.y + 'px,0) scale(' + itemData.sScale + ')';
358
			}
359
		}
360
		var zoomers = this.element.querySelectorAll($.classSelector('.zoom-wrapper'));
361
		for (var i = 0, len = zoomers.length; i < len; i++) {
362
			$(zoomers[i]).zoom().destroy();
363
		}
364
		$(this.element).slider().destroy();
365
//		this.empty();
366
	};
367
	proto.isShown = function() {
368
		return this.element.classList.contains($.className('preview-in'));
369
	};
370
371
	var previewImageApi = null;
372
	$.previewImage = function(options) {
373
		if (!previewImageApi) {
374
			previewImageApi = new PreviewImage(options);
375
		}
376
		return previewImageApi;
377
	};
378
	$.getPreviewImage = function() {
379
		return previewImageApi;
380
	}
381
382
})(mui, window);

+ 322 - 0
app/js/public/mui.zoom.js

@ -0,0 +1,322 @@
1
(function($, window) {
2
	var CLASS_ZOOM = $.className('zoom');
3
	var CLASS_ZOOM_SCROLLER = $.className('zoom-scroller');
4
5
	var SELECTOR_ZOOM = '.' + CLASS_ZOOM;
6
	var SELECTOR_ZOOM_SCROLLER = '.' + CLASS_ZOOM_SCROLLER;
7
8
	var EVENT_PINCH_START = 'pinchstart';
9
	var EVENT_PINCH = 'pinch';
10
	var EVENT_PINCH_END = 'pinchend';
11
	if ('ongesturestart' in window) {
12
		EVENT_PINCH_START = 'gesturestart';
13
		EVENT_PINCH = 'gesturechange';
14
		EVENT_PINCH_END = 'gestureend';
15
	}
16
	$.Zoom = function(element, options) {
17
		var zoom = this;
18
19
		zoom.options = $.extend($.Zoom.defaults, options);
20
21
		zoom.wrapper = zoom.element = element;
22
		zoom.scroller = element.querySelector(SELECTOR_ZOOM_SCROLLER);
23
		zoom.scrollerStyle = zoom.scroller && zoom.scroller.style;
24
25
		zoom.zoomer = element.querySelector(SELECTOR_ZOOM);
26
		zoom.zoomerStyle = zoom.zoomer && zoom.zoomer.style;
27
28
		zoom.init = function() {
29
			//自动启用
30
			$.options.gestureConfig.pinch = true;
31
			$.options.gestureConfig.doubletap = true;
32
			zoom.initEvents();
33
		};
34
35
		zoom.initEvents = function(detach) {
36
			var action = detach ? 'removeEventListener' : 'addEventListener';
37
			var target = zoom.scroller;
38
39
			target[action](EVENT_PINCH_START, zoom.onPinchstart);
40
			target[action](EVENT_PINCH, zoom.onPinch);
41
			target[action](EVENT_PINCH_END, zoom.onPinchend);
42
43
			target[action]($.EVENT_START, zoom.onTouchstart);
44
			target[action]($.EVENT_MOVE, zoom.onTouchMove);
45
			target[action]($.EVENT_CANCEL, zoom.onTouchEnd);
46
			target[action]($.EVENT_END, zoom.onTouchEnd);
47
48
			target[action]('drag', zoom.dragEvent);
49
			target[action]('doubletap', zoom.doubleTapEvent);
50
		};
51
		zoom.dragEvent = function(e) {
52
			if (imageIsMoved || isGesturing) {
53
				e.stopPropagation();
54
			}
55
		};
56
		zoom.doubleTapEvent = function(e) {
57
			zoom.toggleZoom(e.detail.center);
58
		};
59
		zoom.transition = function(style, time) {
60
			time = time || 0;
61
			style['webkitTransitionDuration'] = time + 'ms';
62
			return zoom;
63
		};
64
		zoom.translate = function(style, x, y) {
65
			x = x || 0;
66
			y = y || 0;
67
			style['webkitTransform'] = 'translate3d(' + x + 'px,' + y + 'px,0px)';
68
			return zoom;
69
		};
70
		zoom.scale = function(style, scale) {
71
			scale = scale || 1;
72
			style['webkitTransform'] = 'translate3d(0,0,0) scale(' + scale + ')';
73
			return zoom;
74
		};
75
		zoom.scrollerTransition = function(time) {
76
			return zoom.transition(zoom.scrollerStyle, time);
77
		};
78
		zoom.scrollerTransform = function(x, y) {
79
			return zoom.translate(zoom.scrollerStyle, x, y);
80
		};
81
		zoom.zoomerTransition = function(time) {
82
			return zoom.transition(zoom.zoomerStyle, time);
83
		};
84
		zoom.zoomerTransform = function(scale) {
85
			return zoom.scale(zoom.zoomerStyle, scale);
86
		};
87
88
		// Gestures
89
		var scale = 1,
90
			currentScale = 1,
91
			isScaling = false,
92
			isGesturing = false;
93
		zoom.onPinchstart = function(e) {
94
			isGesturing = true;
95
		};
96
		zoom.onPinch = function(e) {
97
			if (!isScaling) {
98
				zoom.zoomerTransition(0);
99
				isScaling = true;
100
			}
101
			scale = (e.detail ? e.detail.scale : e.scale) * currentScale;
102
			if (scale > zoom.options.maxZoom) {
103
				scale = zoom.options.maxZoom - 1 + Math.pow((scale - zoom.options.maxZoom + 1), 0.5);
104
			}
105
			if (scale < zoom.options.minZoom) {
106
				scale = zoom.options.minZoom + 1 - Math.pow((zoom.options.minZoom - scale + 1), 0.5);
107
			}
108
			zoom.zoomerTransform(scale);
109
		};
110
		zoom.onPinchend = function(e) {
111
			scale = Math.max(Math.min(scale, zoom.options.maxZoom), zoom.options.minZoom);
112
			zoom.zoomerTransition(zoom.options.speed).zoomerTransform(scale);
113
			currentScale = scale;
114
			isScaling = false;
115
		};
116
		zoom.setZoom = function(newScale) {
117
			scale = currentScale = newScale;
118
			zoom.scrollerTransition(zoom.options.speed).scrollerTransform(0, 0);
119
			zoom.zoomerTransition(zoom.options.speed).zoomerTransform(scale);
120
		};
121
		zoom.toggleZoom = function(position, speed) {
122
			if (typeof position === 'number') {
123
				speed = position;
124
				position = undefined;
125
			}
126
			speed = typeof speed === 'undefined' ? zoom.options.speed : speed;
127
			if (scale && scale !== 1) {
128
				scale = currentScale = 1;
129
				zoom.scrollerTransition(speed).scrollerTransform(0, 0);
130
			} else {
131
				scale = currentScale = zoom.options.maxZoom;
132
				if (position) {
133
					var offset = $.offset(zoom.zoomer);
134
					var top = offset.top;
135
					var left = offset.left;
136
					var offsetX = (position.x - left) * scale;
137
					var offsetY = (position.y - top) * scale;
138
					this._cal();
139
					if (offsetX >= imageMaxX && offsetX <= (imageMaxX + wrapperWidth)) { //center
140
						offsetX = imageMaxX - offsetX + wrapperWidth / 2;
141
					} else if (offsetX < imageMaxX) { //left
142
						offsetX = imageMaxX - offsetX + wrapperWidth / 2;
143
					} else if (offsetX > (imageMaxX + wrapperWidth)) { //right
144
						offsetX = imageMaxX + wrapperWidth - offsetX - wrapperWidth / 2;
145
					}
146
					if (offsetY >= imageMaxY && offsetY <= (imageMaxY + wrapperHeight)) { //middle
147
						offsetY = imageMaxY - offsetY + wrapperHeight / 2;
148
					} else if (offsetY < imageMaxY) { //top
149
						offsetY = imageMaxY - offsetY + wrapperHeight / 2;
150
					} else if (offsetY > (imageMaxY + wrapperHeight)) { //bottom
151
						offsetY = imageMaxY + wrapperHeight - offsetY - wrapperHeight / 2;
152
					}
153
					offsetX = Math.min(Math.max(offsetX, imageMinX), imageMaxX);
154
					offsetY = Math.min(Math.max(offsetY, imageMinY), imageMaxY);
155
					zoom.scrollerTransition(speed).scrollerTransform(offsetX, offsetY);
156
				} else {
157
					zoom.scrollerTransition(speed).scrollerTransform(0, 0);
158
				}
159
			}
160
			zoom.zoomerTransition(speed).zoomerTransform(scale);
161
		};
162
163
		zoom._cal = function() {
164
			wrapperWidth = zoom.wrapper.offsetWidth;
165
			wrapperHeight = zoom.wrapper.offsetHeight;
166
			imageWidth = zoom.zoomer.offsetWidth;
167
			imageHeight = zoom.zoomer.offsetHeight;
168
			var scaledWidth = imageWidth * scale;
169
			var scaledHeight = imageHeight * scale;
170
			imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0);
171
			imageMaxX = -imageMinX;
172
			imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0);
173
			imageMaxY = -imageMinY;
174
		};
175
176
		var wrapperWidth, wrapperHeight, imageIsTouched, imageIsMoved, imageCurrentX, imageCurrentY, imageMinX, imageMinY, imageMaxX, imageMaxY, imageWidth, imageHeight, imageTouchesStart = {},
177
			imageTouchesCurrent = {},
178
			imageStartX, imageStartY, velocityPrevPositionX, velocityPrevTime, velocityX, velocityPrevPositionY, velocityY;
179
180
		zoom.onTouchstart = function(e) {
181
			e.preventDefault();
182
			imageIsTouched = true;
183
			imageTouchesStart.x = e.type === $.EVENT_START ? e.targetTouches[0].pageX : e.pageX;
184
			imageTouchesStart.y = e.type === $.EVENT_START ? e.targetTouches[0].pageY : e.pageY;
185
		};
186
		zoom.onTouchMove = function(e) {
187
			e.preventDefault();
188
			if (!imageIsTouched) return;
189
			if (!imageIsMoved) {
190
				wrapperWidth = zoom.wrapper.offsetWidth;
191
				wrapperHeight = zoom.wrapper.offsetHeight;
192
				imageWidth = zoom.zoomer.offsetWidth;
193
				imageHeight = zoom.zoomer.offsetHeight;
194
				var translate = $.parseTranslateMatrix($.getStyles(zoom.scroller, 'webkitTransform'));
195
				imageStartX = translate.x || 0;
196
				imageStartY = translate.y || 0;
197
				zoom.scrollerTransition(0);
198
			}
199
			var scaledWidth = imageWidth * scale;
200
			var scaledHeight = imageHeight * scale;
201
202
			if (scaledWidth < wrapperWidth && scaledHeight < wrapperHeight) return;
203
204
			imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0);
205
			imageMaxX = -imageMinX;
206
			imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0);
207
			imageMaxY = -imageMinY;
208
209
			imageTouchesCurrent.x = e.type === $.EVENT_MOVE ? e.targetTouches[0].pageX : e.pageX;
210
			imageTouchesCurrent.y = e.type === $.EVENT_MOVE ? e.targetTouches[0].pageY : e.pageY;
211
212
			if (!imageIsMoved && !isScaling) {
213
				//				if (Math.abs(imageTouchesCurrent.y - imageTouchesStart.y) < Math.abs(imageTouchesCurrent.x - imageTouchesStart.x)) {
214
				//TODO 此处需要优化,当遇到长图,需要上下滚动时,下列判断会导致滚动不流畅
215
				if (
216
					(Math.floor(imageMinX) === Math.floor(imageStartX) && imageTouchesCurrent.x < imageTouchesStart.x) ||
217
					(Math.floor(imageMaxX) === Math.floor(imageStartX) && imageTouchesCurrent.x > imageTouchesStart.x)
218
				) {
219
					imageIsTouched = false;
220
					return;
221
				}
222
				//				}
223
			}
224
			imageIsMoved = true;
225
			imageCurrentX = imageTouchesCurrent.x - imageTouchesStart.x + imageStartX;
226
			imageCurrentY = imageTouchesCurrent.y - imageTouchesStart.y + imageStartY;
227
228
			if (imageCurrentX < imageMinX) {
229
				imageCurrentX = imageMinX + 1 - Math.pow((imageMinX - imageCurrentX + 1), 0.8);
230
			}
231
			if (imageCurrentX > imageMaxX) {
232
				imageCurrentX = imageMaxX - 1 + Math.pow((imageCurrentX - imageMaxX + 1), 0.8);
233
			}
234
235
			if (imageCurrentY < imageMinY) {
236
				imageCurrentY = imageMinY + 1 - Math.pow((imageMinY - imageCurrentY + 1), 0.8);
237
			}
238
			if (imageCurrentY > imageMaxY) {
239
				imageCurrentY = imageMaxY - 1 + Math.pow((imageCurrentY - imageMaxY + 1), 0.8);
240
			}
241
242
			//Velocity
243
			if (!velocityPrevPositionX) velocityPrevPositionX = imageTouchesCurrent.x;
244
			if (!velocityPrevPositionY) velocityPrevPositionY = imageTouchesCurrent.y;
245
			if (!velocityPrevTime) velocityPrevTime = $.now();
246
			velocityX = (imageTouchesCurrent.x - velocityPrevPositionX) / ($.now() - velocityPrevTime) / 2;
247
			velocityY = (imageTouchesCurrent.y - velocityPrevPositionY) / ($.now() - velocityPrevTime) / 2;
248
			if (Math.abs(imageTouchesCurrent.x - velocityPrevPositionX) < 2) velocityX = 0;
249
			if (Math.abs(imageTouchesCurrent.y - velocityPrevPositionY) < 2) velocityY = 0;
250
			velocityPrevPositionX = imageTouchesCurrent.x;
251
			velocityPrevPositionY = imageTouchesCurrent.y;
252
			velocityPrevTime = $.now();
253
254
			zoom.scrollerTransform(imageCurrentX, imageCurrentY);
255
		};
256
		zoom.onTouchEnd = function(e) {
257
			if (!e.touches.length) {
258
				isGesturing = false;
259
			}
260
			if (!imageIsTouched || !imageIsMoved) {
261
				imageIsTouched = false;
262
				imageIsMoved = false;
263
				return;
264
			}
265
			imageIsTouched = false;
266
			imageIsMoved = false;
267
			var momentumDurationX = 300;
268
			var momentumDurationY = 300;
269
			var momentumDistanceX = velocityX * momentumDurationX;
270
			var newPositionX = imageCurrentX + momentumDistanceX;
271
			var momentumDistanceY = velocityY * momentumDurationY;
272
			var newPositionY = imageCurrentY + momentumDistanceY;
273
274
			if (velocityX !== 0) momentumDurationX = Math.abs((newPositionX - imageCurrentX) / velocityX);
275
			if (velocityY !== 0) momentumDurationY = Math.abs((newPositionY - imageCurrentY) / velocityY);
276
			var momentumDuration = Math.max(momentumDurationX, momentumDurationY);
277
278
			imageCurrentX = newPositionX;
279
			imageCurrentY = newPositionY;
280
281
			var scaledWidth = imageWidth * scale;
282
			var scaledHeight = imageHeight * scale;
283
			imageMinX = Math.min((wrapperWidth / 2 - scaledWidth / 2), 0);
284
			imageMaxX = -imageMinX;
285
			imageMinY = Math.min((wrapperHeight / 2 - scaledHeight / 2), 0);
286
			imageMaxY = -imageMinY;
287
			imageCurrentX = Math.max(Math.min(imageCurrentX, imageMaxX), imageMinX);
288
			imageCurrentY = Math.max(Math.min(imageCurrentY, imageMaxY), imageMinY);
289
290
			zoom.scrollerTransition(momentumDuration).scrollerTransform(imageCurrentX, imageCurrentY);
291
		};
292
		zoom.destroy = function() {
293
			zoom.initEvents(true); //detach
294
			delete $.data[zoom.wrapper.getAttribute('data-zoomer')];
295
			zoom.wrapper.setAttribute('data-zoomer', '');
296
		}
297
		zoom.init();
298
		return zoom;
299
	};
300
	$.Zoom.defaults = {
301
		speed: 300,
302
		maxZoom: 3,
303
		minZoom: 1,
304
	};
305
	$.fn.zoom = function(options) {
306
		var zoomApis = [];
307
		this.each(function() {
308
			var zoomApi = null;
309
			var self = this;
310
			var id = self.getAttribute('data-zoomer');
311
			if (!id) {
312
				id = ++$.uuid;
313
				$.data[id] = zoomApi = new $.Zoom(self, options);
314
				self.setAttribute('data-zoomer', id);
315
			} else {
316
				zoomApi = $.data[id];
317
			}
318
			zoomApis.push(zoomApi);
319
		});
320
		return zoomApis.length === 1 ? zoomApis[0] : zoomApis;
321
	};
322
})(mui, window);