Skip to content

Instantly share code, notes, and snippets.

@daysv
Last active July 20, 2020 06:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daysv/d67bfc2adb8979662c75e0ab34e5dbc5 to your computer and use it in GitHub Desktop.
Save daysv/d67bfc2adb8979662c75e0ab34e5dbc5 to your computer and use it in GitHub Desktop.
zoom image
/**
* @author sangzhe
*/
import AlloyFinger from 'alloyfinger'
const time = 300
const zoomId = 'zoom-opened'
const Zoom = function (selector) {
this.currentImage = null
this._completed = true
this._openedImage = false
this.finger = null
this.wrapper = null
this._originStyle = {}
this.init(selector)
}
Zoom.prototype.init = function (selector) {
const images = typeof selector === 'string' ? document.querySelectorAll(selector) : selector
if (NodeList.prototype.isPrototypeOf(images)) {
images.forEach(image => {
this.addInitEvent(image)
})
} else if (HTMLElement.prototype.isPrototypeOf(images)) {
this.addInitEvent(images)
} else {
console.error('not image found')
}
}
Zoom.prototype.addInitEvent = function (image) {
image.addEventListener('click', () => {
if (!this._completed || this._openedImage) return
history.pushState({
zoom: true
}, '')
window.addEventListener('popstate', this.close.bind(this, true))
this._completed = false
const cloneImage = this.cloneTarget(image)
cloneImage.setAttribute('id', zoomId)
image.style.opacity = 0
this.wrapper = this.createWrapper()
this.wrapper.addEventListener('touchmove', function (e) {
e.preventDefault()
e.stopPropagation()
})
this.wrapper.addEventListener('click', this.close.bind(this, false))
document.body.append(this.wrapper)
document.body.append(cloneImage)
this._originStyle = {
height: cloneImage.style.height,
width: cloneImage.style.width,
top: cloneImage.style.top,
left: cloneImage.style.left,
marginTop: 0,
transform: ''
}
requestAnimationFrame(() => {
setTimeout(() => {
this.animate(this.wrapper, {backgroundColor: 'rgba(0,0,0,0.7)'})
const height = window.screen.availWidth / cloneImage.style.width.replace('px', '') * cloneImage.style.height.replace('px', '')
this.animate(cloneImage, {
transform: 'scale(1)',
left: '0',
width: `${window.screen.availWidth}px`,
height: `${height}px`,
marginTop: `${-height / 2}px`,
top: '50%'
}, () => {
this.currentImage = image
this._completed = true
this._openedImage = cloneImage
this.initFinger()
})
})
})
})
}
Zoom.prototype.close = function (noBack) {
if (!this.currentImage || !this._completed) return
this.finger.destroy()
this._completed = false
const zoomImage = this._openedImage
this.animate(this.wrapper, {backgroundColor: 'rgba(0,0,0,0)'}, () => {
document.body.removeChild(this.wrapper)
})
this.animate(zoomImage, this._originStyle, () => {
document.body.removeChild(zoomImage)
this.currentImage.style.opacity = 1
this.currentImage = null
this._openedImage = false
this._completed = true
window.removeEventListener('popstate', this.close.bind(this, true))
console.log('noBack', noBack)
if (!noBack) {
history.back()
}
})
}
Zoom.prototype.initFinger = function () {
const element = this._openedImage
let _x = 0
let _y = 0
let _scale = 1
let _isMove = false
let _initScale = 1
let _pressMove = false
let _lastX = 0
let _lastY = 0
const easeMove = (element, scale, x, y, ratio = 1) => {
if (!_isMove) return
const scaleRatio = 1 / scale
ratio = ratio * 0.96
x = x * ratio * scaleRatio
y = y * ratio * scaleRatio
_x = _x + x
_y = _y + y
element.style.transform = `scale(${scale}) translate3d(${_x}px, ${_y}px, 0)`
if (Math.abs(x) >= 1 || Math.abs(y) >= 1) {
requestAnimationFrame(() => {
easeMove(element, scale, x, y, ratio)
})
}
}
this.finger = new AlloyFinger(element, {
touchStart: function () {
_pressMove = false
_isMove = false
},
touchMove: function () {
_isMove = true
},
touchEnd: function () {
_initScale = _scale
if (_pressMove) {
easeMove(element, _initScale, _lastX, _lastY)
}
},
touchCancel: function () {
},
multipointStart: function () {
},
multipointEnd: function () {
},
tap: () => {
},
doubleTap: () => {
if (!/scale\(1\)/i.test(element.style.transform)) {
_scale = 1
} else {
_scale = 2
}
this.animate(element, {transform: `scale(${_scale}) translate3d(${_x}px, ${_y}px, 0)`})
},
longTap: function () {
_isMove = true
},
singleTap: () => {
if (!_isMove) {
this.close()
}
},
rotate: function (evt) {
evt.preventDefault()
},
pinch: function (evt) {
evt.preventDefault()
_isMove = true
const scale = evt.zoom * _initScale
if (scale >= 1) {
element.style.transform = `scale(${scale}) translate3d(${_x}px, ${_y}px, 0)`
_scale = scale
}
},
pressMove: function (evt) {
evt.preventDefault()
_pressMove = true
_x = _x + evt.deltaX / _scale
_y = _y + evt.deltaY / _scale
element.style.transform = `scale(${_scale}) translate3d(${_x}px, ${_y}px, 0)`
_lastX = evt.deltaX
_lastY = evt.deltaY
},
swipe: function (evt) {
evt.preventDefault()
_isMove = true
}
})
}
Zoom.prototype.cloneTarget = template => {
const {top, left, width, height} = template.getBoundingClientRect()
const clone = template.cloneNode()
clone.removeAttribute('id')
clone.style.position = 'fixed'
clone.style.top = `${top}px`
clone.style.left = `${left}px`
clone.style.width = `${width}px`
clone.style.height = `${height}px`
clone.style.zIndex = 10000
return clone
}
Zoom.prototype.animate = function (element, params, callback) {
element.style.willChange = 'all'
element.style.transition = `all ${time}ms ease`
Object.keys(params).forEach(key => {
element.style[key] = params[key]
})
setTimeout(() => {
element.style.transition = ''
element.style.willChange = ''
callback && callback()
}, time)
}
Zoom.prototype.createWrapper = function () {
const div = document.createElement('div')
div.setAttribute('id', 'zoom-wrapper')
div.style.position = 'fixed'
div.style.left = '0'
div.style.right = '0'
div.style.top = '0'
div.style.bottom = '0'
div.style.zIndex = 1000
return div
}
export default Zoom
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment