UNPKG

view-bigimg

Version:
428 lines (377 loc) 13.7 kB
import TouchEvent from './event' import isPassive from './isPassive' const ZOOM_CONSTANT = 15 // increase or decrease value for zoom on mouse wheel const MOUSE_WHEEL_COUNT = 5 //A mouse delta after which it should stop preventing default behaviour of mouse wheel const preventDefaultCb = e => e.preventDefault() /** * ease out method * @param {Number} t current time * @param {Number} b intial value * @param {Number} c changed value * @param {Number} d duration */ function easeOutQuart(t, b, c, d) { t /= d t-- return -c * (t * t * t * t - 1) + b } // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating // requestAnimationFrame polyfill by Erik Möller // fixes from Paul Irish and Tino Zijdel // 各种兼容 requestAnimationFrame cancelAnimationFrame (function () { var lastTime = 0 if (window.requestAnimationFrame) { return } var vendors = ['ms', 'moz', 'webkit', 'o'] for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'] } if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback) { var currTime = new Date().getTime() var timeToCall = Math.max(0, 16 - (currTime - lastTime)) var id = window.setTimeout(function () { callback(currTime + timeToCall) }, timeToCall) lastTime = currTime + timeToCall return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) { clearTimeout(id); } }()) /** * function to check if image is loaded * @param {Node} img en image node */ function imageLoaded(img) { return img.compvare && (typeof img.naturalWidth === 'undefined' || img.naturalWidth !== 0); } function ImageViewer(container, options) { this.container = container this.options = Object.assign({}, ImageViewer.defaults, options) this.zoomValue = 100 container.classList.add('iv-container'); this.imageWrap = container.querySelector('.iv-image-wrap') this.closeBtn = container.querySelector('.iv-close') } ImageViewer.prototype = { constructor: ImageViewer, _init () { var viewer = this var options = viewer.options var zooming = false // 是否正在通过手势进行缩放 var container = viewer.container var imageWrap = viewer.imageWrap // 鼠标滚轮事件控制缩放失误次数 var changedDelta = 0 //handle double tap for zoom in and zoom out var touchtime = 0, point viewer._imageSlider = new TouchEvent(imageWrap, { onStart () { if (!viewer.loaded) return false if (zooming) return var self = this // 这里的 this 是 _imageSlider self.imgWidth = viewer.imageDim.w * viewer.zoomValue / 100 self.imgHeight = viewer.imageDim.h * viewer.zoomValue / 100 self.curImgLeft = parseFloat(viewer.currentImg.style.left) self.curImgTop = parseFloat(viewer.currentImg.style.top) }, onMove (e, position) { if (zooming) return // 每次移动都把 position 设置到 currentPos this.currentPos = position var newLeft = this.curImgLeft + position.dx var newTop = this.curImgTop + position.dy var baseLeft = Math.max((viewer.containerDim.w - this.imgWidth) / 2, 0), baseTop = Math.max((viewer.containerDim.h - this.imgHeight) / 2, 0), baseRight = viewer.containerDim.w - baseLeft, baseBottom = viewer.containerDim.h - baseTop // 修正新的 left top 的边界 newLeft = Math.min(newLeft, baseLeft) newTop = Math.min(newTop, baseTop) // //fix for right and bottom if ((newLeft + this.imgWidth) < baseRight) { newLeft = baseRight - this.imgWidth //newLeft - (newLeft + imgWidth - baseRight) } if ((newTop + this.imgHeight) < baseBottom) { newTop = baseBottom - this.imgHeight //newTop + (newTop + imgHeight - baseBottom) } viewer.currentImg.style.left = newLeft + 'px' viewer.currentImg.style.top = newTop + 'px' }, onEnd () { if (zooming) return }, onMouseWheel (e) { if (!options.zoomOnMouseWheel || !viewer.loaded) { return } // cross-browser wheel delta 这里限制了 delta 不是 1 就是 -1, zoomValue 100 - 500 var delta = Math.max(-1, Math.min(1, (e.wheelDelta))), zoomValue = viewer.zoomValue * (100 + delta * ZOOM_CONSTANT) / 100 if (!(zoomValue >= 100 && zoomValue <= options.maxZoom)) { changedDelta += Math.abs(delta) // 这里 Math.abs(delta) 为 1,即 changedDelta ++ } else { changedDelta = 0 } if (changedDelta > MOUSE_WHEEL_COUNT) return; // 保护措施,在5次 zoomValue 不合法时,不做任何处理 e.preventDefault(); var x = e.pageX, y = e.pageY viewer.zoom(zoomValue, { x: x, y: y }) }, // dbclick onClick (e) { if (touchtime == 0) { touchtime = Date.now() point = { x: e.pageX, y: e.pageY } } else { if ((Date.now() - touchtime) < 500 && Math.abs(e.pageX - point.x) < 50 && Math.abs(e.pageY - point.y) < 50) { if (viewer.zoomValue == options.zoomValue) { viewer.zoom(200) } else { viewer.resetZoom() } touchtime = 0 } else { touchtime = 0 } } }, // pinch onPinch (estart) { if (!viewer.loaded) return var touch0 = estart.touches[0], touch1 = estart.touches[1] if (!(touch0 && touch1)) { return } zooming = true var startdist = Math.sqrt(Math.pow(touch1.pageX - touch0.pageX, 2) + Math.pow(touch1.pageY - touch0.pageY, 2)), startZoom = viewer.zoomValue, center = { x: ((touch1.pageX + touch0.pageX) / 2), y: ((touch1.pageY + touch0.pageY) / 2) } var moveListener = function (emove) { emove.preventDefault() var touch0 = emove.touches[0], touch1 = emove.touches[1], newDist = Math.sqrt(Math.pow(touch1.pageX - touch0.pageX, 2) + Math.pow(touch1.pageY - touch0.pageY, 2)), zoomValue = startZoom + (newDist - startdist) / 2 viewer.zoom(zoomValue, center) } var endListener = function () { document.removeEventListener('touchmove', moveListener) document.removeEventListener('touchend', endListener) zooming = false } document.addEventListener('touchmove', moveListener, isPassive() ? { capture: false, passive: false } : false) document.addEventListener('touchend', endListener) } }).init() if (options.refreshOnResize) { this._resizeHandler = this.refresh.bind(this) window.addEventListener('resize', this._resizeHandler) } //prevent scrolling the backside if container if fixed positioned container.addEventListener('touchmove', preventDefaultCb, isPassive() ? { capture: false, passive: false } : false) container.addEventListener('mousewheel', preventDefaultCb) //assign event on close button this._close = this.hide.bind(this) this.closeBtn.addEventListener('click', this._close) }, // 核心方法,处理缩放移动等位置计算 zoom (perc, point) { var self = this, maxZoom = this.options.maxZoom, curPerc = this.zoomValue, curImg = this.currentImg, containerDim = this.containerDim, imageDim = this.imageDim, curLeft = parseFloat(curImg.style.left), curTop = parseFloat(curImg.style.top) // 约束 perc 的范围 100-500 perc = Math.round(Math.max(100, perc)) perc = Math.min(maxZoom, perc) // 从哪缩放的坐标,默认从 container 中心缩放 point = point || { x: containerDim.w / 2, y: containerDim.h / 2 } self._clearFrames(); var step = 0 //calculate base top,left,bottom,right 这里可以考虑放到 self 对象上 // var baseLeft = (containerDim.w - imageDim.w) / 2, // baseTop = (containerDim.h - imageDim.h) / 2, // baseRight = containerDim.w - baseLeft, // baseBottom = containerDim.h - baseTop function _zoom() { step++ if (step < 20) { // 20 帧完成动画,不到 20 则启动计时器 self._zoomFrame = requestAnimationFrame(_zoom) } // 计算新的缩放值 var tickZoom = easeOutQuart(step, curPerc, perc - curPerc, 20) // 计算新的宽高 left top var ratio = tickZoom / curPerc, imgWidth = imageDim.w * tickZoom / 100, imgHeight = imageDim.h * tickZoom / 100, newLeft = -((point.x - curLeft) * ratio - point.x), newTop = -((point.y - curTop) * ratio - point.y) var baseLeft = Math.max((containerDim.w - imgWidth) / 2, 0), baseTop = Math.max((containerDim.h - imgHeight) / 2, 0), baseRight = containerDim.w - baseLeft, baseBottom = containerDim.h - baseTop // 修正新的 left top 的边界 newLeft = Math.min(newLeft, baseLeft) newTop = Math.min(newTop, baseTop) //fix for right and bottom if ((newLeft + imgWidth) < baseRight) { newLeft = baseRight - imgWidth //newLeft - (newLeft + imgWidth - baseRight) } if ((newTop + imgHeight) < baseBottom) { newTop = baseBottom - imgHeight //newTop + (newTop + imgHeight - baseBottom) } curImg.style.width = imgWidth + 'px' curImg.style.height = imgHeight + 'px' curImg.style.left = newLeft + 'px' curImg.style.top = newTop + 'px' self.zoomValue = tickZoom } _zoom() }, _clearFrames () { cancelAnimationFrame(this._zoomFrame) }, resetZoom () { this.zoom(this.options.zoomValue) }, //calculate dimensions of image, container and reset the image _calculateDimensions () { //calculate content width of image and snap image var self = this var curImg = self.currentImg var container = self.container var imageWidth = curImg.getBoundingClientRect().width var imageHeight = curImg.getBoundingClientRect().height var contWidth = container.getBoundingClientRect().width var contHeight = container.getBoundingClientRect().height // set the container dimension self.containerDim = { w: contWidth, h: contHeight } //set the image dimension var imgWidth, imgHeight var ratio = imageWidth / imageHeight imgWidth = (imageWidth > imageHeight && contHeight >= contWidth) || ratio * contHeight > contWidth ? contWidth : ratio * contHeight imgHeight = imgWidth / ratio self.imageDim = { w: imgWidth, h: imgHeight } // //reset image position and zoom curImg.style.width = imgWidth + 'px' curImg.style.height = imgHeight + 'px' curImg.style.left = (contWidth - imgWidth) / 2 + 'px' curImg.style.top = (contHeight - imgHeight) / 2 + 'px' curImg.style.maxWidth = 'none' curImg.style.maxHeight = 'none' }, refresh () { if (!this.loaded) return this._calculateDimensions() this.resetZoom() }, show (image) { this.container.style.display = 'block' if (image) this.load(image) }, hide () { this.container.style.display = 'none' }, destroy () { window.removeEventListener('resize', this._resizeHandler) this._imageSlider.destroy() this.closeBtn.removeEventListener('click', this._close) this.container.parentNode.removeChild(this.container) this.closeBtn = null this.container = null this.imageWrap = null this.options = null this._close = null this._imageSlider = null this._resizeHandler = null }, load (image) { var self = this var container = this.container var imageWrap = this.imageWrap var beforeImg = imageWrap.querySelector('.iv-large-image') if (beforeImg) { imageWrap.removeChild(beforeImg) } var img = document.createElement('img') img.classList.add('iv-large-image') img.src = image this.currentImg = img this.imageWrap.appendChild(img) this.loaded = false container.querySelector('.iv-loader').style.display = 'block' img.style.display = 'none' function refreshView() { self.loaded = true self.zoomValue = 100 //reset zoom of images img.style.display = 'block' self.refresh() //hide loader container.querySelector('.iv-loader').style.display = 'none' } if (imageLoaded(img)) { refreshView() } else { img.onload = function () { refreshView() } } } } ImageViewer.defaults = { zoomValue: 100, maxZoom: 500, refreshOnResize: true, zoomOnMouseWheel: true } function ViewBigimg (options) { var imageViewHtml = '<div class="iv-loader"></div><div class="iv-image-view"><div class="iv-image-wrap"></div><div class="iv-close"></div></div>' var container = document.createElement('div') container.id = 'iv-container' container.innerHTML = imageViewHtml document.body.appendChild(container) var viewer = new ImageViewer(container, options) viewer._init() return viewer } export default ViewBigimg