(function() { 'use strict'; var definePinchZoom = function($) { var PinchZoom = function(el, options, viewerContainer) { this.el = $(el); this.viewerContainer = viewerContainer; this.zoomFactor = 1; this.lastScale = 1; this.offset = { x: 0, y: 0 }; this.options = $.extend({}, this.defaults, options); this.setupMarkup(); this.bindEvents(); this.update(); // default enable. this.enable(); this.height = 0; this.load = false; this.direction = null; this.clientY = null; this.lastclientY = null; }, sum = function(a, b) { return a + b; }, isCloseTo = function(value, expected) { return value > expected - 0.01 && value < expected + 0.01; }; PinchZoom.prototype = { defaults: { tapZoomFactor: 2, zoomOutFactor: 1.2, animationDuration: 300, maxZoom: 4, minZoom: 0.8, lockDragAxis: false, use2d: true, zoomStartEventName: 'pz_zoomstart', zoomEndEventName: 'pz_zoomend', dragStartEventName: 'pz_dragstart', dragEndEventName: 'pz_dragend', doubleTapEventName: 'pz_doubletap' }, /** * Event handler for 'dragstart' * @param event */ handleDragStart: function(event) { this.el.trigger(this.options.dragStartEventName); this.stopAnimation(); this.lastDragPosition = false; this.hasInteraction = true; this.handleDrag(event); }, /** * Event handler for 'drag' * @param event */ handleDrag: function(event) { if (this.zoomFactor > 1.0) { var touch = this.getTouches(event)[0]; this.drag(touch, this.lastDragPosition, event); this.offset = this.sanitizeOffset(this.offset); this.lastDragPosition = touch; } }, handleDragEnd: function() { this.el.trigger(this.options.dragEndEventName); this.end(); }, /** * Event handler for 'zoomstart' * @param event */ handleZoomStart: function(event) { this.el.trigger(this.options.zoomStartEventName); this.stopAnimation(); this.lastScale = 1; this.nthZoom = 0; this.lastZoomCenter = false; this.hasInteraction = true; }, /** * Event handler for 'zoom' * @param event */ handleZoom: function(event, newScale) { // a relative scale factor is used var touchCenter = this.getTouchCenter(this.getTouches(event)), scale = newScale / this.lastScale; this.lastScale = newScale; // the first touch events are thrown away since they are not precise this.nthZoom += 1; if (this.nthZoom > 3) { this.scale(scale, touchCenter); this.drag(touchCenter, this.lastZoomCenter); } this.lastZoomCenter = touchCenter; }, handleZoomEnd: function() { this.el.trigger(this.options.zoomEndEventName); this.end(); }, /** * Event handler for 'doubletap' * @param event */ handleDoubleTap: function(event) { var center = this.getTouches(event)[0], zoomFactor = this.zoomFactor > 1 ? 1 : this.options.tapZoomFactor, startZoomFactor = this.zoomFactor, updateProgress = (function(progress) { this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); }).bind(this); if (this.hasInteraction) { return; } if (startZoomFactor > zoomFactor) { center = this.getCurrentZoomCenter(); } this.animate(this.options.animationDuration, updateProgress, this.swing); this.el.trigger(this.options.doubleTapEventName); }, /** * Max / min values for the offset * @param offset * @return {Object} the sanitized offset */ sanitizeOffset: function(offset) { var maxX = (this.zoomFactor - 1) * this.getContainerX(), maxY = (this.zoomFactor - 1) * this.getContainerY(), maxOffsetX = Math.max(maxX, 0), maxOffsetY = Math.max(maxY, 0), minOffsetX = Math.min(maxX, 0), minOffsetY = Math.min(maxY, 0); var x = Math.min(Math.max(offset.x, minOffsetX), maxOffsetX), y = Math.min(Math.max(offset.y, minOffsetY), maxOffsetY); return { x: x, y: y }; }, /** * Scale to a specific zoom factor (not relative) * @param zoomFactor * @param center */ scaleTo: function(zoomFactor, center) { this.scale(zoomFactor / this.zoomFactor, center); }, /** * Scales the element from specified center * @param scale * @param center */ scale: function(scale, center) { scale = this.scaleZoomFactor(scale); this.addOffset({ x: (scale - 1) * (center.x + this.offset.x), y: (scale - 1) * (center.y + this.offset.y) }); }, /** * Scales the zoom factor relative to current state * @param scale * @return the actual scale (can differ because of max min zoom factor) */ scaleZoomFactor: function(scale) { var originalZoomFactor = this.zoomFactor; this.zoomFactor *= scale; this.zoomFactor = Math.min(this.options.maxZoom, Math.max(this.zoomFactor, this.options.minZoom)); return this.zoomFactor / originalZoomFactor; }, /** * Drags the element * @param center * @param lastCenter */ drag: function(center, lastCenter, event) { if (lastCenter) { if (this.options.lockDragAxis) { // lock scroll to position that was changed the most if (Math.abs(center.x - lastCenter.x) > Math.abs(center.y - lastCenter.y)) { this.addOffset({ x: -(center.x - lastCenter.x), y: 0 }); } else { this.addOffset({ y: -(center.y - lastCenter.y), x: 0 }); } } else { if (center.y - lastCenter.y < 0) { this.direction = "down"; } else if (center.y - lastCenter.y > 10) { this.direction = "up"; } this.addOffset({ y: -(center.y - lastCenter.y), x: -(center.x - lastCenter.x) }); } } }, /** * Calculates the touch center of multiple touches * @param touches * @return {Object} */ getTouchCenter: function(touches) { return this.getVectorAvg(touches); }, /** * Calculates the average of multiple vectors (x, y values) */ getVectorAvg: function(vectors) { return { x: vectors.map(function(v) { return v.x; }).reduce(sum) / vectors.length, y: vectors.map(function(v) { return v.y; }).reduce(sum) / vectors.length }; }, /** * Adds an offset * @param offset the offset to add * @return return true when the offset change was accepted */ addOffset: function(offset) { this.offset = { x: this.offset.x + offset.x, y: this.offset.y + offset.y }; }, sanitize: function() { if (this.zoomFactor < this.options.zoomOutFactor) { this.zoomOutAnimation(); } else if (this.isInsaneOffset(this.offset)) { this.sanitizeOffsetAnimation(); } }, /** * Checks if the offset is ok with the current zoom factor * @param offset * @return {Boolean} */ isInsaneOffset: function(offset) { var sanitizedOffset = this.sanitizeOffset(offset); return sanitizedOffset.x !== offset.x || sanitizedOffset.y !== offset.y; }, /** * Creates an animation moving to a sane offset */ sanitizeOffsetAnimation: function() { var targetOffset = this.sanitizeOffset(this.offset), startOffset = { x: this.offset.x, y: this.offset.y }, updateProgress = (function(progress) { this.offset.x = startOffset.x + progress * (targetOffset.x - startOffset.x); this.offset.y = startOffset.y + progress * (targetOffset.y - startOffset.y); this.update(); }).bind(this); this.animate( this.options.animationDuration, updateProgress, this.swing ); }, /** * Zooms back to the original position, * (no offset and zoom factor 1) */ zoomOutAnimation: function() { var startZoomFactor = this.zoomFactor, zoomFactor = 1, center = this.getCurrentZoomCenter(), updateProgress = (function(progress) { this.scaleTo(startZoomFactor + progress * (zoomFactor - startZoomFactor), center); }).bind(this); this.animate( this.options.animationDuration, updateProgress, this.swing ); }, /** * Updates the aspect ratio */ updateAspectRatio: function() { this.setContainerY(this.getContainerX() / this.getAspectRatio()); }, /** * Calculates the initial zoom factor (for the element to fit into the container) * @return the initial zoom factor */ getInitialZoomFactor: function() { // use .offsetWidth instead of width() // because jQuery-width() return the original width but Zepto-width() will calculate width with transform. // the same as .height() if (this.container[0] && this.el[0]) { return this.container[0].offsetWidth / this.el[0].offsetWidth; } else { return 0 } }, /** * Calculates the aspect ratio of the element * @return the aspect ratio */ getAspectRatio: function() { if (this.el[0]) { var offsetHeight = this.el[0].offsetHeight; return this.container[0].offsetWidth / offsetHeight; } else { return 0 } }, /** * Calculates the virtual zoom center for the current offset and zoom factor * (used for reverse zoom) * @return {Object} the current zoom center */ getCurrentZoomCenter: function() { // uses following formula to calculate the zoom center x value // offset_left / offset_right = zoomcenter_x / (container_x - zoomcenter_x) var length = this.container[0].offsetWidth * this.zoomFactor, offsetLeft = this.offset.x, offsetRight = length - offsetLeft - this.container[0].offsetWidth, widthOffsetRatio = offsetLeft / offsetRight, centerX = widthOffsetRatio * this.container[0].offsetWidth / (widthOffsetRatio + 1), // the same for the zoomcenter y height = this.container[0].offsetHeight * this.zoomFactor, offsetTop = this.offset.y, offsetBottom = height - offsetTop - this.container[0].offsetHeight, heightOffsetRatio = offsetTop / offsetBottom, centerY = heightOffsetRatio * this.container[0].offsetHeight / (heightOffsetRatio + 1); // prevents division by zero if (offsetRight === 0) { centerX = this.container[0].offsetWidth; } if (offsetBottom === 0) { centerY = this.container[0].offsetHeight; } return { x: centerX, y: centerY }; }, canDrag: function() { return !isCloseTo(this.zoomFactor, 1); }, /** * Returns the touches of an event relative to the container offset * @param event * @return array touches */ getTouches: function(event) { var position = this.container.offset(); return Array.prototype.slice.call(event.touches).map(function(touch) { return { x: touch.pageX - position.left, y: touch.pageY - position.top }; }); }, /** * Animation loop * does not support simultaneous animations * @param duration * @param framefn * @param timefn * @param callback */ animate: function(duration, framefn, timefn, callback) { var startTime = new Date().getTime(), renderFrame = (function() { if (!this.inAnimation) { return; } var frameTime = new Date().getTime() - startTime, progress = frameTime / duration; if (frameTime >= duration) { framefn(1); if (callback) { callback(); } this.update(); this.stopAnimation(); this.update(); } else { if (timefn) { progress = timefn(progress); } framefn(progress); this.update(); requestAnimationFrame(renderFrame); } }).bind(this); this.inAnimation = true; requestAnimationFrame(renderFrame); }, /** * Stops the animation */ stopAnimation: function() { this.inAnimation = false; }, /** * Swing timing function for animations * @param p * @return {Number} */ swing: function(p) { return -Math.cos(p * Math.PI) / 2 + 0.5; }, getContainerX: function() { if (this.el[0]) { return this.el[0].offsetWidth; } else { return 0; } }, getContainerY: function() { return this.el[0].offsetHeight; }, setContainerY: function(y) { y = y.toFixed(2); return this.container.height(y); }, /** * Creates the expected html structure */ setupMarkup: function() { this.container = $('
'); this.el.before(this.container); this.container.append(this.el); this.container.css({ 'position': 'relative', // 'width':'auto', // 'height':'auto' }); // Zepto doesn't recognize `webkitTransform..` style this.el.css({ '-webkit-transform-origin': '0% 0%', '-moz-transform-origin': '0% 0%', '-ms-transform-origin': '0% 0%', '-o-transform-origin': '0% 0%', 'transform-origin': '0% 0%', 'position': 'relative' }); }, end: function() { this.hasInteraction = false; this.sanitize(); this.update(); }, /** * Binds all required event listeners */ bindEvents: function() { detectGestures(this.container.eq(0), this, this.viewerContainer); // Zepto and jQuery both know about `on` $(window).on('resize', this.update.bind(this)); $(this.el).find('img').on('load', this.update.bind(this)); }, /** * Updates the css values according to the current zoom factor and offset */ update: function() { if (this.updatePlaned) { return; } this.updatePlaned = true; setTimeout((function() { this.updatePlaned = false; this.updateAspectRatio(); var zoomFactor = this.getInitialZoomFactor() * this.zoomFactor, offsetX = (-this.offset.x / zoomFactor).toFixed(3), offsetY = (-this.offset.y / zoomFactor).toFixed(3); this.lastclientY = offsetY; var transform3d = 'scale3d(' + zoomFactor + ', ' + zoomFactor + ',1) ' + 'translate3d(' + offsetX + 'px,' + offsetY + 'px,0px)', transform2d = 'scale(' + zoomFactor + ', ' + zoomFactor + ') ' + 'translate(' + offsetX + 'px,' + offsetY + 'px)', removeClone = (function() { if (this.clone) { this.clone.remove(); delete this.clone; } }).bind(this); // Scale 3d and translate3d are faster (at least on ios) // but they also reduce the quality. // PinchZoom uses the 3d transformations during interactions // after interactions it falls back to 2d transformations if (!this.options.use2d || this.hasInteraction || this.inAnimation) { this.is3d = true; // removeClone(); this.el.css({ '-webkit-transform': transform3d, '-o-transform': transform2d, '-ms-transform': transform2d, '-moz-transform': transform2d, 'transform': transform3d }); } else { // When changing from 3d to 2d transform webkit has some glitches. // To avoid this, a copy of the 3d transformed element is displayed in the // foreground while the element is converted from 3d to 2d transform if (this.is3d) { // this.clone = this.el.clone(); // this.clone.css('pointer-events', 'none'); // this.clone.appendTo(this.container); // setTimeout(removeClone, 200); } this.el.css({ '-webkit-transform': transform2d, '-o-transform': transform2d, '-ms-transform': transform2d, '-moz-transform': transform2d, 'transform': transform2d }); this.is3d = false; } this.done && this.done.call(this, zoomFactor) }).bind(this), 0); }, /** * Enables event handling for gestures */ enable: function() { this.enabled = true; }, /** * Disables event handling for gestures */ disable: function() { this.enabled = false; }, //销毁还原 destroy: function() { var dom = this.el.clone(); var p = this.container.parent(); this.container.remove(); dom.removeAttr('style'); p.append(dom); } }; var detectGestures = function(el, target, viewerContainer) { var interaction = null, fingers = 0, lastTouchStart = null, startTouches = null, lastTouchY = null, clientY = null, lastclientY = 0, lastTop = 0, setInteraction = function(newInteraction, event) { if (interaction !== newInteraction) { if (interaction && !newInteraction) { switch (interaction) { case "zoom": target.handleZoomEnd(event); break; case 'drag': target.handleDragEnd(event); break; } } switch (newInteraction) { case 'zoom': target.handleZoomStart(event); break; case 'drag': target.handleDragStart(event); break; } } interaction = newInteraction; }, updateInteraction = function(event) { if (fingers === 2) { setInteraction('zoom'); } else if (fingers === 1 && target.canDrag()) { setInteraction('drag', event); } else { setInteraction(null, event); } }, targetTouches = function(touches) { return Array.prototype.slice.call(touches).map(function(touch) { return { x: touch.pageX, y: touch.pageY }; }); }, getDistance = function(a, b) { var x, y; x = a.x - b.x; y = a.y - b.y; return Math.sqrt(x * x + y * y); }, calculateScale = function(startTouches, endTouches) { var startDistance = getDistance(startTouches[0], startTouches[1]), endDistance = getDistance(endTouches[0], endTouches[1]); return endDistance / startDistance; }, cancelEvent = function(event) { event.stopPropagation(); event.preventDefault(); }, detectDoubleTap = function(event) { var time = (new Date()).getTime(); var pageY = event.changedTouches[0].pageY; var top = parentNode.scrollTop || 0; if (fingers > 1) { lastTouchStart = null; lastTouchY = null; cancelEvent(event); } if (time - lastTouchStart < 300 && Math.abs(pageY - lastTouchY) < 10 && Math.abs(lastTop - top) < 10) { cancelEvent(event); target.handleDoubleTap(event); switch (interaction) { case "zoom": target.handleZoomEnd(event); break; case 'drag': target.handleDragEnd(event); break; } } if (fingers === 1) { lastTouchStart = time; lastTouchY = pageY; lastTop = top; } }, firstMove = true; if (viewerContainer) { var parentNode = viewerContainer[0]; } if (parentNode) { parentNode.addEventListener('touchstart', function(event) { if (target.enabled) { firstMove = true; fingers = event.touches.length; detectDoubleTap(event); clientY = event.changedTouches[0].clientY; if (fingers > 1) { cancelEvent(event); } } }); parentNode.addEventListener('touchmove', function(event) { if (target.enabled) { lastclientY = event.changedTouches[0].clientY; if (firstMove) { updateInteraction(event); if (interaction) { // cancelEvent(event); } startTouches = targetTouches(event.touches); } else { switch (interaction) { case 'zoom': target.handleZoom(event, calculateScale(startTouches, targetTouches(event.touches))); break; case 'drag': target.handleDrag(event); break; } if (interaction) { // cancelEvent(event); target.update(lastclientY); } } if (fingers > 1) { cancelEvent(event); } firstMove = false; } }); parentNode.addEventListener('touchend', function(event) { if (target.enabled) { fingers = event.touches.length; if (fingers > 1) { cancelEvent(event); } updateInteraction(event); } }); } }; return PinchZoom; }; var PinchZoom = definePinchZoom($); var Pdfh5 = function(dom, options) { this.container = $(dom); this.currentNum = 1; //当前页数从1开始 this.thePDF = null; this.pdfRender = null; this.totalNum = null; this.pdfLoaded = false; this.pages = null; this.initTime = 0; this.startTime = 0; this.endTime = 0; this.renderTime = 0; this.timer = null; this.loadWidth = 1; this.docWidth = document.documentElement.clientWidth; this.eventType = {}; this.init(options); }; Pdfh5.prototype = { init: function(options) { var self = this; if (self.pdfLoaded) { return; } this.initTime = new Date().getTime(); setTimeout(function() { self.eventType["start"] && self.eventType["start"].call(self, self.initTime); self.start && self.start(self.initTime) }, 0) options = options ? options : {}; options.pdfurl = options.pdfurl ? options.pdfurl : null; options.data = options.data ? options.data : null; if (options.scrollEnable == 'undefined' || options.scrollEnable == undefined) { options.scrollEnable = true } if (options.scrollEnable == 'true' || options.scrollEnable == true) { options.scrollEnable = true } if (options.scrollEnable == 'null' || options.scrollEnable == null) { options.scrollEnable = true } if (options.scrollEnable == 'false') { options.scrollEnable = false } var html = '