zx-image-viewer
Version:
594 lines (546 loc) • 15.4 kB
JavaScript
/**
* Create by capricorncd
* 2018/5/16 0016.
* https://github.com/capricorncd
*/
import '../style/image-preview.styl'
import util from './util'
import dom from './dom'
import ic from './img-controls'
import keyboard from './keyboard'
import broadcast from './broadcast'
import {
mouseWheel,
filterOptions,
fmtImageArray,
appendIconfontToHead,
createToolbarButtons
} from './fn'
// window.util = util
// 默认配置参数
const __DEFAULT = {
// 背景遮罩透明度[0-1]
// backgroundOpacity: .6,
// 分页mouseover切换图片
paginationable: true,
// 显示关闭按钮
showClose: true,
// 显示上一张/下一张箭头
showSwitchArrow: true,
// 显示工具栏
showToolbar: false,
// 显示分页导航栏
showPagination: true,
// 缩放
scalable: true,
// 旋转
rotatable: true,
// 移动
movable: true,
// 按键配置
keyboard: {
prev: 'left',
next: 'right',
// 滚动鼠标[放大,缩小]
scale: 'mousewheel',
// [Clockwise 顺时针, anticlockwise 逆时针]
rotate: ['up', 'down'],
close: 'escape'
},
// 图标字体
iconfont: '//at.alicdn.com/t/font_613889_qd2ugx65fxadzpvi.css',
// 工具栏按钮数量及顺序配置
toolbarButtons: ['prev', 'enlarge', 'rotate', 'reduce', 'next'],
// mask background, default: 'rgba(0, 0, 0, .6)'
maskBackground: null
}
// const log = console.log
const Z_INDEX = 10
class ZxImageViewer {
constructor (opts, arr) {
if (typeof arr === 'undefined' && util.isArray(opts)) {
arr = opts
opts = undefined
}
// 初始化参数
let options = util.isObject(opts) ? util.assign(__DEFAULT, opts) : __DEFAULT
// broadcast
this.broadcast = broadcast.broadcast
// 判断键名配置是否有重复的
this.opts = filterOptions(options)
// 图片数组
this.images = []
this._init()
if (util.isArray(arr)) {
this.init(arr, 0)
}
}
// 内部初始化
_init () {
const opts = this.opts
// log(opts)
// 预览容器dom结构对象
const vnode = {
tag: 'div',
attrs: {
class: 'zx-image-preview-wrapper',
style: 'display:none;' + (opts.maskBackground ? `background:${opts.maskBackground}` : '')
},
child: [
// 预览图片
{
tag: 'img',
attrs: {
class: 'zip-picture v-transition'
}
}
]
}
// 显示左右箭头
if (opts.showSwitchArrow) {
// 左右方向箭头
vnode.child.push({tag: 'div', attrs: {class: 'zip-arrow _prev-arrow'}})
vnode.child.push({tag: 'div', attrs: {class: 'zip-arrow _next-arrow'}})
}
// 显示关闭按钮
if(opts.showClose) {
vnode.child.push({tag: 'div', attrs: {class: 'zip-close _cur'}})
}
// 工具栏
if (opts.showToolbar) {
vnode.child.push({
tag: 'div',
attrs: {
class: 'zip-tool-wrapper'
}
// child: createToolbarButtons(opts.toolbarButtons)
})
}
// 分页栏
if (opts.showPagination) {
vnode.child.push({tag: 'div', attrs: {class: 'zip-pagination-wrapper'}})
}
// 创建dom结构
// 预览容器
this.$container = dom.create(vnode)
// 关闭按钮
this.$close = dom.query('.zip-close', this.$container)
// 预览图片
this.$img = dom.query('.zip-picture', this.$container)
// 工具栏
this.$tool = dom.query('.zip-tool-wrapper', this.$container)
// 分页栏
this.$pagination = dom.query('.zip-pagination-wrapper', this.$container)
// 背景透明度
// if (typeof opts.backgroundOpacity !== 'undefined') {
// const bo = util.toNumber(opts.backgroundOpacity)
// this.$container.style.background = `rgba(0, 0, 0, ${bo})`
// }
// preview是否显示
this.isPreview = false
// 是否添加到body
this.isAppendToBody = dom.appendToBody(this.$container)
this.index = 0
// 事件处理器
this._eventHandler()
// 添加图标字体样式至head
appendIconfontToHead(dom, opts.iconfont)
}
// 初始化
init (images, index) {
if (!this.isAppendToBody) {
this.isAppendToBody = dom.appendToBody(this.$container)
}
// 初始化数据
let imgArray = fmtImageArray(images)
if (imgArray) {
this.images = imgArray
}
if (typeof index !== 'undefined') {
this.index = index >= this.images.length ? 0 : util.int(index)
}
this._updateToolbar()
this._resetPaginationInnerHtml()
this._resetCurrent$img()
}
/**
* 更新当前图片数组
* @param images
*/
update (images) {
this.init(images, 0)
}
// 重置分页html结构
_resetPaginationInnerHtml () {
if (!this.$pagination) return
let html = ''
let len = this.images.length
this.images.forEach((item, index) => {
html += `<i style="width:${Math.floor(1 / len * 100)}%" data-index="${index}" class="_item${this.index === index ? ' _item-active' : ''}"></i>`
})
this.$pagination.innerHTML = html
this._checkArrowPrevNext()
}
/**
* 查看图片
* @param index 当前图片在数组中的索引
* @param angle 旋转角度
* @param images 图片数组
*/
view (index = 0, angle, images) {
// 参数验证
if (typeof angle !== 'undefined') {
if (util.isArray(angle) && typeof images === 'undefined') {
images = angle
angle = null
}
}
let imgArray = fmtImageArray(images)
if (imgArray) {
this.images = imgArray
this._resetPaginationInnerHtml()
this._updateToolbar()
}
// 图片数组是否有元素判断
if (this.images.length === 0) {
throw new Error(`图片数组images参数为空或格式不正确!`)
}
if (index < this.images.length) {
this.index = util.int(index)
}
this._resetCurrent$img(angle)
this.show()
// 修改Pagination样式
this._changePaginationClass()
}
// 销毁对象
destroy () {
try {
this.$container.parentNode.removeChild(this.$container)
this.$container = null
} catch (e) {}
}
/**
* 重置当前被预览的图片
* @param _angle 设置旋转角度
* @private
*/
_resetCurrent$img (_angle) {
let item = this.images[this.index]
const _this = this
const $img = this.$img
$img.src = item.url
// 获取设置的图片旋转角度
let angle = util.int(_angle || item.angle)
// 根据缩略图设置的旋转角度,重置预览图片的旋转角度
dom.attr($img, 'rotate-angle', angle)
$img.onload = function () {
// 旋转和计算$img位置
ic.rotate($img, angle)
// 移除on-error样式
dom.rmClass(_this.$container, 'on-error')
}
$img.onerror = function () {
// 添加on-error样式
dom.addClass(_this.$container, 'on-error')
}
}
/**
* 事件处理
* @private
*/
_eventHandler () {
// 禁止图片被选中
this.$container.addEventListener('selectstart', e => {
e.preventDefault()
})
// document.onselectstart = new Function('event.returnValue=false')
// 关闭
this.$close && this.$close.addEventListener('click', e => {
e.stopPropagation()
this.hide()
})
// 点击图片
this.$img.addEventListener('click', e => {
e.stopPropagation()
})
// 拖动图片
if (this.opts.movable) {
ic.move(this.$img)
} else {
this.$img.style.cursor = 'auto'
}
// 点击preview容器
this.$container.addEventListener('click', e => {
const $el = e.target
if (dom.hasClass($el, '_prev-arrow')) {
this.prev()
} else if (dom.hasClass($el, '_next-arrow')) {
this.next()
} else {
this.hide()
}
})
// 工具栏点击事件
this.$tool && this.$tool.addEventListener('click', e => {
e.stopPropagation()
const $el = e.target
// console.log($el.className)
// let isToolItem = dom.hasClass($el, 'zx')
// if (!isToolItem) return
// 上一张
if (dom.hasClass($el, 'zx-prev')) {
this.prev()
}
// 下一张
else if (dom.hasClass($el, 'zx-next')) {
this.next()
}
// 放大
else if (dom.hasClass($el, 'zx-enlarge')) {
this._scale(1)
}
// 缩小
else if (dom.hasClass($el, 'zx-reduce')) {
this._scale(-1)
}
// 旋转
else if (dom.hasClass($el, 'zx-rotate')) {
this._rotate()
}
}, true)
// 点击统计栏
this.$pagination && this.opts.paginationable && this.$pagination.addEventListener('mouseover', e => {
// 处理事件
this._handleChangePage(e)
})
// 点击统计栏,阻止事件冒泡
this.$pagination && this.$pagination.addEventListener('click', e => {
e.stopPropagation()
})
const keys = this.opts.keyboard || {}
// 键盘事件
window.addEventListener('keyup', e => {
if (!this.isPreview) return
let keyCode = e.keyCode
// log(keyCode, e.key, e.code, e.which)
// 阻止方向键移动元素或滚动条
e.preventDefault()
// 上一张
if (keyboard.code(keys.prev) === keyCode) {
this.prev()
}
// 下一张
if (keyboard.code(keys.next) === keyCode) {
this.next()
}
// 旋转
if (util.isArray(keys.rotate)) {
// 顺时针
if (keyboard.code(keys.rotate[0]) === keyCode) {
this._rotate()
}
// 逆时针
if (keyboard.code(keys.rotate[1]) === keyCode) {
this._rotate(true)
}
} else if (typeof keys.rotate === 'string' && keyboard.code(keys.rotate) === keyCode) {
// 顺时针
this._rotate()
}
// 缩放
if (util.isArray(keys.scale)) {
// 放大
if (keyboard.code(keys.scale[0]) === keyCode) {
this._scale(1)
}
// 缩小
if (keyboard.code(keys.scale[1]) === keyCode) {
this._scale(-1)
}
} else if (typeof keys.scale === 'string' && keyboard.code(keys.scale) === keyCode) {
this._scale(1)
}
// 关闭
if (keyboard.code(keys.close) === keyCode) {
this.hide()
}
})
// log(this.opts)
// 滚动鼠标前进后退
if (util.toLower(keys.prev) === 'mousewheel') {
mouseWheel(switchWheelHandler)
}
// 滚动鼠标缩放
if (util.toLower(keys.scale) === 'mousewheel') {
mouseWheel(scaleWheelHandler)
}
// 滚动鼠标旋转
if (util.toLower(keys.rotate) === 'mousewheel') {
mouseWheel(rotateWheelHandler)
}
const _this = this
// 滚动鼠标缩放处理
function scaleWheelHandler (e) {
if (!_this.isPreview) return
// log(e)
const $el = e.target
if ($el !== _this.$img) return
// 浏览器兼容处理
// 鼠标滚动方向
let wheelDelta = e.wheelDelta || -e.detail
_this._scale(wheelDelta)
}
// 滚动鼠标前后切换处理
function switchWheelHandler (e) {
if (!_this.isPreview) return
let wheelDelta = e.wheelDelta || -e.detail
wheelDelta > 0 ? _this.prev() : _this.next()
}
// 滚动鼠标旋转处理
function rotateWheelHandler (e) {
if (!_this.isPreview) return
let wheelDelta = e.wheelDelta || -e.detail
_this._rotate(wheelDelta > 0)
}
}
// 点击或鼠标滑过统计栏处理
_handleChangePage (e) {
if (this.images.length <= 1) return
// e.stopPropagation()
const $el = e.target
let isToolItem = dom.hasClass($el, '_item')
if (!isToolItem) return
let index = dom.attr($el, 'data-index') >>> 0
// 当前点击index和this.index相同
if (this.index === index) return
this.index = index
this._changePaginationClass($el)
this._resetCurrent$img()
}
// 修改统计栏item样式
_changePaginationClass ($el) {
if (!this.$pagination) return
$el = $el || this.$pagination.querySelectorAll('._item')[this.index]
const $active = dom.query('._item-active', this.$pagination)
dom.rmClass($active, '_item-active')
dom.addClass($el, '_item-active')
}
// 隐藏图片预览
hide () {
if (this.$container) {
broadcast.emit('close')
this.$container.style.display = 'none'
this.isPreview = false
}
}
// 显示图片预览
show () {
if (this.$container) {
let zIndex = util.getMaxZindex()
if (zIndex > Z_INDEX) {
this.$container.style.zIndex = zIndex
}
broadcast.emit('show')
this.$container.style.display = 'block'
this.isPreview = true
}
}
// 上一张
prev () {
this._switchImage('prev')
}
// 下一张
next () {
this._switchImage('next')
}
/**
* 旋转
* @param isAnticlockwise 是否逆时针
*/
_rotate (isAnticlockwise) {
// 禁止旋转
if (!this.opts.rotatable) return
let deg = isAnticlockwise ? -90 : 90
const angle = util.int(dom.attr(this.$img, 'rotate-angle')) + deg
dom.attr(this.$img, 'rotate-angle', angle)
ic.rotate(this.$img, angle)
}
/**
* 缩放
* @private
*/
_scale (wheelDelta) {
// 禁止缩放
if (!this.opts.scalable) return
ic.scale(this.$img, wheelDelta)
}
// 切换
_switchImage (type) {
let maxIndex = this.images.length - 1
if (maxIndex <= 0) return
switch (type) {
case 'prev':
if (+this.index === 0) {
this.index = maxIndex
} else {
this.index--
}
break
case 'next':
if (+this.index >= maxIndex) {
this.index = 0
} else {
this.index++
}
break
}
let item = this.images[this.index]
this.$img.src = item.url
const angle = util.int(item.angle)
// 根据缩略图设置的旋转角度,重置预览图片的旋转角度
dom.attr(this.$img, 'rotate-angle', angle)
ic.rotate(this.$img, angle)
this._changePaginationClass()
}
// 验证图片切换键是否显示
_checkArrowPrevNext () {
// 不显示左右箭头
if (!this.opts.showSwitchArrow) return
// 图片数组只有一张或为空
if (this.images.length <= 1) {
this.togglePrev('hide')
this.toggleNext('hide')
}
}
togglePrev (type) {
const $el = dom.query('._prev-arrow', this.$container)
if ($el) $el.style.display = type === 'show' ? 'block' : 'none'
}
toggleNext (type) {
const $el = dom.query('._next-arrow', this.$container)
if ($el) $el.style.display = type === 'show' ? 'block' : 'none'
}
/**
* 更新工具栏按钮
* 当一张图片时,隐藏左右按钮
* 2019-06-21
* @private
*/
_updateToolbar () {
if (!this.$tool) return
let buttons = this.images.length <= 1
? this.opts.toolbarButtons.filter(item => item !== 'prev' && item !== 'next')
: this.opts.toolbarButtons
let vnode = {
tag: 'div',
child: createToolbarButtons(buttons)
}
this.$tool.innerHTML = dom.getInnerHtml(vnode)
}
}
ZxImageViewer.prototype.on = broadcast.on
ZxImageViewer.prototype.off = broadcast.off
ZxImageViewer.prototype.emit = broadcast.emit
export { ZxImageViewer }
export default ZxImageViewer