├── .gitignore
├── src
├── images
│ ├── demo.gif
│ ├── liya.jpg
│ ├── pinch.png
│ ├── rain.jpg
│ ├── swipe.png
│ ├── favicon.ico
│ ├── kalaqiu.jpg
│ ├── qrcode.png
│ ├── spread.png
│ ├── vertical.png
│ ├── baiyuekui.jpg
│ ├── cyberpunk.jpg
│ ├── trajectory.png
│ └── jinglingwangzuo.jpg
├── css
│ └── noname-gallery.css
└── js
│ └── noname-gallery.js
├── .editorconfig
├── guide.txt
├── README.md
├── index.html
└── dist
├── noname-gallery.min.js
└── noname-gallery.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | .idea/
--------------------------------------------------------------------------------
/src/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/demo.gif
--------------------------------------------------------------------------------
/src/images/liya.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/liya.jpg
--------------------------------------------------------------------------------
/src/images/pinch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/pinch.png
--------------------------------------------------------------------------------
/src/images/rain.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/rain.jpg
--------------------------------------------------------------------------------
/src/images/swipe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/swipe.png
--------------------------------------------------------------------------------
/src/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/favicon.ico
--------------------------------------------------------------------------------
/src/images/kalaqiu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/kalaqiu.jpg
--------------------------------------------------------------------------------
/src/images/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/qrcode.png
--------------------------------------------------------------------------------
/src/images/spread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/spread.png
--------------------------------------------------------------------------------
/src/images/vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/vertical.png
--------------------------------------------------------------------------------
/src/images/baiyuekui.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/baiyuekui.jpg
--------------------------------------------------------------------------------
/src/images/cyberpunk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/cyberpunk.jpg
--------------------------------------------------------------------------------
/src/images/trajectory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/trajectory.png
--------------------------------------------------------------------------------
/src/images/jinglingwangzuo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/18223781723/noname-gallery/HEAD/src/images/jinglingwangzuo.jpg
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/src/css/noname-gallery.css:
--------------------------------------------------------------------------------
1 | /* 容器 */
2 | .noname-gallery-container {
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | bottom: 0;
8 | z-index: 1000;
9 | overflow: hidden;
10 | user-select: none;
11 | touch-action: none;
12 | }
13 |
14 | /* 背景 */
15 | .noname-gallery-bg {
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | right: 0;
20 | bottom: 0;
21 | z-index: -1;
22 | background: #000;
23 | }
24 |
25 | /* 计数器 */
26 | .noname-gallery-counter {
27 | position: absolute;
28 | top: 15px;
29 | left: 15px;
30 | z-index: 1;
31 | font-size: 14px;
32 | color: #F9F9F9;
33 | pointer-events: none;
34 | }
35 |
36 | .noname-gallery-wrap {
37 | display: flex;
38 | margin: 0;
39 | padding: 0;
40 | list-style: none;
41 | }
42 |
43 | .noname-gallery-wrap li {
44 | width: 100%;
45 | height: 100vh;
46 | overflow: hidden;
47 | }
48 |
49 | .noname-gallery-img {
50 | display: block;
51 | transform-origin: left top;
52 | object-fit: cover;
53 | /* 使用-webkit-user-drag: none;会导致华为浏览器长按不显示拖动缩略图 */
54 | }
--------------------------------------------------------------------------------
/guide.txt:
--------------------------------------------------------------------------------
1 | 单击(移动距离小于10)
2 | PC端单击图片区域进行缩放
3 | PC端单击非图片区域关闭画廊
4 | 移动端单击关闭画廊
5 |
6 | 双击(两次距离小于30)
7 | PC端不响应双击事件
8 | 移动端双击缩放图片
9 |
10 | 区分拖拽目标(dragTarget)
11 | 如果图片未进行缩放操作,且拖拽为水平方向,dragTarget = wrap
12 | 如果图片已进行缩放操作,但宽高未超过屏幕宽高,且拖拽为水平方向,dragTarget = wrap
13 | 如果图片已进行缩放操作,且宽高超过屏幕宽高,且拖拽为水平方向(向左拖拽),且图片处在右边界,dragTarget = wrap
14 | 如果图片已进行缩放操作,且宽高超过屏幕宽高,且拖拽为水平方向(向右拖拽),且图片处在左边界,dragTarget = wrap
15 | 如果图片未进行缩放操作,且拖拽未垂直方向,dragTarget = img
16 | 如果图片已放大,且宽高超过屏幕宽高,且图片不处在左右边界,dragTarget = img
17 |
18 | 拖拽(移动距离大于10)
19 | 当拖拽目标为wrap时,即dragTarget = wrap,水平方向拖动距离大于屏幕宽度10%,即可切换下一张图片
20 | 当拖拽目标为img时,即dragTarget = img,垂直方向拖动距离大于屏幕高度10%,且图片未进行缩放操作,即可关闭画廊
21 |
22 | 双指
23 | 当拖拽目标为wrap时,即dragTarget = wrap,可双指交替滑动,滑动边界为上一张左边界,当前,下一张右边界
24 | 当拖拽目标为img时,即dragTarget = img,双指可以缩放图片
25 | 当拖拽目标为img时,即dragTarget = img,且拖拽为垂直方向,双指可交替拖拽
26 |
27 | 动画
28 | 实现方式分为requestAnimationFrame和transition
29 | 由于实现方式的不同,动画分为可打断的动画和不可打断的动画。可打断的动画即动画过程中,可以响应用户的操作。反之则为不可打断的动画。(transition实现的动画也是可以打断的,只不过需要写个函数计算当前动画的值)
30 | 其实完全可以使用requestAnimationFrame实现动画,从而让所有动画都可以打断,但是由于transition在移动端具有更好的兼容性,动画更流畅。所以为了统一交互,只能做出取舍。
31 |
32 | 可打断的动画如下
33 | wrap滑动切换图片动画(用户可快速滑动)
34 | 图片缩放后,宽高有一个超过屏幕宽高,用户滑动执行惯性滚动动画,用户点击屏幕或键盘切换图片时会停止惯性滚动
35 |
36 | 不可打断的动画如下
37 | 开始动画
38 | 单击、双击缩放动画
39 | 双指缩小、垂直滑动未关闭的恢复动画
40 | wrap滑动切换图片时,上一张图片如果放大过的恢复动画
41 | 关闭动画
42 |
43 | 遇到的问题
44 | 使用transition同时缩放宽高和transform: translate3d()时,移动端微信浏览器的动画存在抖动和轨迹偏移的问题。如果用top,left代替,则不会出现此问题。但性能不如transform: translate。demo:http://jsdemo.codeman.top/html/transition.html
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # noname-gallery
2 | JavaScript image gallery, easy to use, no dependencies.
3 |
4 | 
5 |
6 | # Demo
7 | Demo: http://nonamegallery.codeman.top
8 |
9 | 
10 |
11 | # Getting Started
12 | In a browser:
13 | ```javascript
14 |
15 |
16 |
17 | ```
18 |
19 | # Example
20 | ```javascript
21 |
22 |
![]()
23 |
![]()
24 |
![]()
25 |
![]()
26 |
![]()
27 |
![]()
28 |
29 |
30 |
47 | ```
48 |
49 | # Options
50 | | Params | Type | Defaults | Description |
51 | | :---- | :---- | :---- | :---- |
52 | | options | object | | 配置项 |
53 | | options.list | array | HTMLImageElement[] | 图片列表,必填参数 |
54 | | options.index | number | 0 | 索引 |
55 | | options.fadeInOut | boolean | true | 动画淡入淡出,当缩略图和预览尺寸不匹配时,建议开启 |
56 | | options.useTransform | boolean | true | 使用transform或宽高缩放 |
57 | | options.verticalZoom | boolean | true | 垂直滑动时缩小图片 |
58 | | options.openKeyboard | boolean | false | 开启键盘控制,esc关闭画廊,方向键切换图片 |
59 | | options.zoomToScreenCenter | boolean | false | 将放大区域移动至屏幕中心显示 |
60 | | options.duration | number | 300 | 动画持续时间,单位ms |
61 | | options.minScale | number | 1.5 | 最小放大倍数 |
62 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 | NonameGallery
10 |
11 |
12 |
83 |
84 |
85 |
86 |
87 |
NonameGallery v1.0.0
88 |
基于JavaScript开发的图片预览插件,支持PC端和移动端,兼容主流浏览器,简单易用,零依赖。
89 |
90 |
91 |

92 |

93 |

94 |

95 |

96 |

97 |
98 |
99 |
121 |
122 |
手势支持
123 |
支持所有基本手势,包括单击关闭画廊,双击缩放图片,双指缩放图片,左右滑动切换图片。
124 |
125 |

126 |
127 |
双指放大图片
128 |
当用户两根手指分别向外扩展时,图片则会相应放大。最大放大尺寸会在图片实际宽高和图片预览宽高*1.5(用户可配置)两者中取较大值。
129 |
130 |
131 |
132 |
133 |

134 |
135 |
双指缩小图片
136 |
当用户两根手指分别向内收缩时,图片则会相应缩小。最小尺寸为图片预览宽高*0.7(系统默认值)。
137 |
138 |
139 |
140 |
141 |

142 |
143 |
水平滑动切换图片
144 |
当用户单个手指水平滑动距离超过屏幕宽度*0.1时,则会切换图片。如果图片为放大状态且宽度大于屏幕宽度,则需要先滑动到图片边界。
145 |
146 |
147 |
148 |
149 |

150 |
151 |
垂直滑动关闭画廊
152 |
当用户单个手指垂直滑动距离超过屏幕高度*0.1时,会关闭画廊,如果图片为放大状态,则会响应拖动查看图片事件。
153 |
154 |
155 |
156 |
PC端如何操作
157 |
PC端操作基本类似,支持单击关闭画廊,点击图片缩放,增加键盘控制,ESC关闭画廊,方向键切换图片。
158 |
159 |
160 |
161 |
162 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/dist/noname-gallery.min.js:
--------------------------------------------------------------------------------
1 | !function(){function t(t){if(0===t.list.length)throw new Error("options.list can not be empty array");this.options=Object.assign({},this.defaults,t)}t.prototype.defaults={list:[],index:0,fadeInOut:!0,useTransform:!0,verticalZoom:!0,openKeyboard:!1,zoomToScreenCenter:!1,duration:300,minScale:1.5},t.prototype.init=function(){this.setProperties(),this.setWindowSize(),this.setPreviewList(),this.setWrap(),this.render(),this.getElement(),this.bindEventListener(),this.open()},t.prototype.setProperties=function(){this.container=null,this.bg=null,this.counter=null,this.wrap=null,this.imgList=null,this.bgOpacity=1,this.windowWidth=0,this.windowHeight=0,this.index=this.options.index,this.wrapWidth=0,this.wrapX=0,this.previewList=[],this.currentImg={x:0,y:0,width:0,height:0,scale:1,opacity:1,status:""},this.pointers=[],this.point1={x:0,y:0},this.point2={x:0,y:0},this.diff={x:0,y:0},this.distance={x:0,y:0},this.lastDistance={x:0,y:0},this.lastPoint1={x:0,y:0},this.lastPoint2={x:0,y:0},this.lastMove={x:0,y:0},this.lastCenter={x:0,y:0},this.tapCount=0,this.dragDirection="",this.dragTarget="",this.status="",this.isPointerdown=!1,this.isAnimating=!0,this.isWrapAnimating=!1,this.tapTimeout=null,this.pointerdownTime=null,this.pointermoveTime=null,this.pinchTime=null,this.inertiaRafId=null,this.wrapRafId=null},t.prototype.setWindowSize=function(){this.windowWidth=window.innerWidth,this.windowHeight=window.innerHeight},t.prototype.setPreviewList=function(){for(const t of this.options.list){const i=t.getBoundingClientRect(),e=this.getImgSize(t.naturalWidth,t.naturalHeight,this.windowWidth,this.windowHeight),s=Math.max(this.decimal(t.naturalWidth/e.width,5),this.options.minScale);this.previewList.push({x:this.decimal((this.windowWidth-e.width)/2,2),y:this.decimal((this.windowHeight-e.height)/2,2),width:this.decimal(e.width,2),height:this.decimal(e.height,2),maxWidth:this.decimal(e.width*s,2),maxHeight:this.decimal(e.height*s,2),maxScale:s,thumbnail:{x:this.decimal(i.left,2),y:this.decimal(i.top,2),width:this.decimal(i.width,2),height:this.decimal(i.height,2),scaleX:this.decimal(i.width/e.width,5),scaleY:this.decimal(i.height/e.height,5)}})}},t.prototype.setCurrentImg=function(t,i,e,s,n,h,r){this.currentImg={x:t,y:i,width:e,height:s,scale:n,opacity:h,status:r}},t.prototype.setWrap=function(){this.wrapWidth=this.previewList.length*this.windowWidth,this.wrapX=this.index*this.windowWidth*-1},t.prototype.render=function(){let t="opacity: 0;";this.options.useTransform&&(t+="transition: opacity "+this.options.duration+"ms ease-out;");let i=''+(this.options.index+1)+" / "+this.options.list.length+'
';for(let e=0,s=this.options.list.length;e
'}i+="
",document.body.insertAdjacentHTML("beforeend",i)},t.prototype.getElement=function(){this.container=document.querySelector(".noname-gallery-container"),this.bg=document.querySelector(".noname-gallery-bg"),this.counter=document.querySelector(".noname-gallery-counter"),this.wrap=document.querySelector(".noname-gallery-wrap"),this.imgList=document.querySelectorAll(".noname-gallery-img");for(let t=0,i=this.imgList.length;t1&&(Math.abs(this.point1.x-this.lastPoint1.x)>30||Math.abs(this.point1.y-this.lastPoint1.y)>30)&&(this.tapCount=1),clearTimeout(this.tapTimeout),window.cancelAnimationFrame(this.inertiaRafId),window.cancelAnimationFrame(this.wrapRafId)):2===this.pointers.length&&(this.tapCount=0,this.point2={x:this.pointers[1].clientX,y:this.pointers[1].clientY},this.lastCenter=this.getCenter(this.point1,this.point2),this.lastDistance={x:this.distance.x,y:this.distance.y},this.lastPoint2={x:this.pointers[1].clientX,y:this.pointers[1].clientY},""===this.dragTarget&&(this.dragTarget="img")),this.lastPoint1={x:this.pointers[0].clientX,y:this.pointers[0].clientY})},t.prototype.handlePointermove=function(t){if(!this.isPointerdown)return;this.handlePointers(t,"update");const i={x:this.pointers[0].clientX,y:this.pointers[0].clientY};if(1===this.pointers.length)this.diff={x:i.x-this.lastMove.x,y:i.y-this.lastMove.y},this.distance={x:i.x-this.point1.x+this.lastDistance.x,y:i.y-this.point1.y+this.lastDistance.y},this.lastMove={x:i.x,y:i.y},this.pointermoveTime=Date.now(),(Math.abs(this.distance.x)>10||Math.abs(this.distance.y)>10)&&(this.tapCount=0,""===this.dragDirection&&""===this.dragTarget&&(this.getDragDirection(),this.getDragTarget())),"wrap"===this.dragTarget?this.handleWrapPointermove():"img"===this.dragTarget&&this.handleImgPointermove();else if(2===this.pointers.length){const t={x:this.pointers[1].clientX,y:this.pointers[1].clientY};"img"===this.dragTarget&&"verticalToClose"!==this.currentImg.status&&this.handlePinch(i,t),this.lastPoint1={x:i.x,y:i.y},this.lastPoint2={x:t.x,y:t.y}}t.preventDefault()},t.prototype.handlePointerup=function(t){this.isPointerdown&&(this.handlePointers(t,"delete"),0===this.pointers.length?(this.isPointerdown=!1,0===this.tapCount?"wrap"===this.dragTarget?this.handleWrapPointerup():"img"===this.dragTarget&&this.handleImgPointerup():1===this.tapCount?"mouse"===t.pointerType?t.clientX>=this.currentImg.x&&t.clientX<=this.currentImg.x+this.currentImg.width&&t.clientY>=this.currentImg.y&&t.clientY<=this.currentImg.y+this.currentImg.height?this.handleZoom({x:t.clientX,y:t.clientY}):this.close():Date.now()-this.pointerdownTime<500?this.tapTimeout=setTimeout((()=>{this.close()}),250):this.tapCount=0:this.tapCount>1&&this.handleZoom({x:t.clientX,y:t.clientY})):1===this.pointers.length&&(this.point1={x:this.pointers[0].clientX,y:this.pointers[0].clientY},this.lastMove={x:this.pointers[0].clientX,y:this.pointers[0].clientY}))},t.prototype.handlePointercancel=function(t){this.tapCount=0,this.isPointerdown=!1,this.pointers.length=0,this.isWrapAnimating&&this.handleWrapPointerup()},t.prototype.handlePointers=function(t,i){for(let e=0;e0?this.index--:["ArrowRight","ArrowDown"].includes(t.key)&&i1){if(this.options.useTransform)i.element.style.transition="transform "+this.options.duration+"ms ease-out, opacity "+this.options.duration+"ms ease-out",i.element.style.transform="translate3d("+i.x+"px,"+i.y+"px, 0) scale(1)";else{const t={img:{width:{from:this.currentImg.width,to:i.width},height:{from:this.currentImg.height,to:i.height},x:{from:this.currentImg.x,to:i.x},y:{from:this.currentImg.y,to:i.y},index:this.index}};this.raf(t)}i.element.style.cursor="zoom-in",this.setCurrentImg(i.x,i.y,i.width,i.height,1,1,"")}else{const e=this.windowWidth/2,s=this.windowHeight/2,n=this.decimal((t.x-i.x)*i.maxScale,2),h=this.decimal((t.y-i.y)*i.maxScale,2);let r,o;if(i.maxWidth>this.windowWidth?(r=this.options.zoomToScreenCenter?e-n:t.x-n,r>0?r=0:rthis.windowHeight?(o=this.options.zoomToScreenCenter?s-h:t.y-h,o>0?o=0:oh.maxScale?(s=h.maxScale/this.currentImg.scale,this.currentImg.scale=h.maxScale,this.currentImg.width=h.maxWidth,this.currentImg.height=h.maxHeight):nthis.windowWidth?this.currentImg.x>0?this.currentImg.x=0:this.currentImg.xthis.windowHeight?this.currentImg.y>0?this.currentImg.y=0:this.currentImg.y=e/s?t>e?(n=e,h=e/t*i):(n=t,h=i):i>s?(n=s/i*t,h=s):(n=t,h=i),{width:n,height:h}},t.prototype.getDragDirection=function(){Math.abs(this.distance.x)>Math.abs(this.distance.y)?this.dragDirection="h":this.dragDirection="v"},t.prototype.getDragTarget=function(){let t=!1,i=!1;this.currentImg.width>this.windowWidth?(this.diff.x>0&&0===this.currentImg.x||this.diff.x<0&&this.currentImg.x===this.windowWidth-this.currentImg.width)&&(t=!0):this.currentImg.width>=this.previewList[this.index].width&&(i=!0),"h"===this.dragDirection&&(t||i)?this.dragTarget="wrap":this.dragTarget="img"},t.prototype.getDistance=function(t,i){const e=t.x-i.x,s=t.y-i.y;return Math.hypot(e,s)},t.prototype.handleWrapPointermove=function(){if(this.wrapX>0||this.wrapX<(this.previewList.length-1)*this.windowWidth*-1)this.wrapX+=.3*this.diff.x;else{this.wrapX+=this.diff.x;const t=(this.index-1)*this.windowWidth*-1,i=(this.index+1)*this.windowWidth*-1;this.wrapX>t?this.wrapX=t:this.wrapXthis.windowWidth||this.currentImg.height>this.windowHeight?(this.currentImg.x+=this.diff.x,this.currentImg.y+=this.diff.y,this.handleBoundary(),this.currentImg.status="inertia",this.options.useTransform?(t.element.style.transition="none",t.element.style.transform="translate3d("+this.currentImg.x+"px, "+this.currentImg.y+"px, 0) scale("+this.currentImg.scale+")"):t.element.style.transform="translate3d("+this.currentImg.x+"px, "+this.currentImg.y+"px, 0)"):"v"===this.dragDirection&&this.currentImg.width<=t.width&&this.currentImg.height<=t.height&&(this.currentImg.status="verticalToClose",this.bgOpacity=this.decimal(1-Math.abs(this.distance.y)/(this.windowHeight/1.2),5),this.bgOpacity<0&&(this.bgOpacity=0),this.options.verticalZoom?(this.currentImg.scale=this.bgOpacity,this.currentImg.width=this.decimal(t.width*this.currentImg.scale,2),this.currentImg.height=this.decimal(t.height*this.currentImg.scale,2),this.currentImg.x=t.x+this.distance.x+(t.width-this.currentImg.width)/2,this.currentImg.y=t.y+this.distance.y+(t.height-this.currentImg.height)/2):(this.currentImg.x=t.x,this.currentImg.y=t.y+this.distance.y,this.currentImg.scale=1),this.bg.style.opacity=this.bgOpacity,this.options.useTransform?(this.bg.style.transition="none",t.element.style.transition="none",t.element.style.transform="translate3d("+this.currentImg.x+"px, "+this.currentImg.y+"px , 0) scale("+this.currentImg.scale+")"):(t.element.style.width=this.currentImg.width+"px",t.element.style.height=this.currentImg.height+"px",t.element.style.transform="translate3d("+this.currentImg.x+"px, "+this.currentImg.y+"px , 0)"))},t.prototype.handleWrapPointerup=function(){const t=Math.round(.1*this.windowWidth),i=this.index;Math.abs(this.distance.x)>t&&(this.distance.x>0&&i>0?this.index--:this.distance.x<0&&i1e3)this.handleInertia();else if("verticalToClose"===this.currentImg.status&&Math.abs(this.distance.y)>=t)this.close();else if("shrink"===this.currentImg.status||"verticalToClose"==this.currentImg.status&&Math.abs(this.distance.y)1||Math.abs(i.y)>1)&&(e.inertiaRafId=window.requestAnimationFrame(s))}))},t.prototype.handleWrapSwipe=function(){this.isWrapAnimating=!0;const t={wrap:{x:{from:this.wrapX,to:this.windowWidth*this.index*-1}}};this.wrapRaf(t),this.counter.innerHTML=this.index+1+" / "+this.previewList.length},t.prototype.handleLastImg=function(t){if(this.index!==t){if(this.currentImg.scale>1){const i=this.previewList[t];if(this.options.useTransform)i.element.style.transition="transform "+this.options.duration+"ms ease-out, opacity "+this.options.duration+"ms ease-out",i.element.style.transform="translate3d("+i.x+"px, "+i.y+"px, 0) scale(1)";else{const e={img:{width:{from:this.currentImg.width,to:i.width},height:{from:this.currentImg.height,to:i.height},x:{from:this.currentImg.x,to:i.x},y:{from:this.currentImg.y,to:i.y},index:t}};this.raf(e)}i.element.style.cursor="zoom-in"}const i=this.previewList[this.index];this.setCurrentImg(i.x,i.y,i.width,i.height,1,1,"")}},t.prototype.handleTransitionEnd=function(t){"IMG"===t.target.tagName&&(t.target===this.previewList[this.index].element&&(this.isAnimating=!1,this.dragTarget="",this.dragDirection=""),"close"===this.status&&(this.unbindEventListener(),this.container.remove()))},t.prototype.decimal=function(t,i){const e=Math.pow(10,i);return Math.round(t*e)/e},t.prototype.getCenter=function(t,i){return{x:(t.x+i.x)/2,y:(t.y+i.y)/2}},t.prototype.easeOut=function(t,i,e,s){const n=e/s;return-(i-t)*n*(n-2)+t},t.prototype.raf=function(t){const i=this;let e,s=0;const n=this.options.duration,h=this.previewList[t.img.index];window.requestAnimationFrame((function r(o){void 0===e&&(e=o);let a=o-e;if(a>n&&(a=n,s++),t.bg){const e=i.decimal(i.easeOut(t.bg.opacity.from,t.bg.opacity.to,a,n),5);i.bg.style.opacity=e}if(t.img.opacity){const e=i.decimal(i.easeOut(t.img.opacity.from,t.img.opacity.to,a,n),5);h.element.style.opacity=e}const d=i.decimal(i.easeOut(t.img.width.from,t.img.width.to,a,n),2),l=i.decimal(i.easeOut(t.img.height.from,t.img.height.to,a,n),2),m=i.decimal(i.easeOut(t.img.x.from,t.img.x.to,a,n),2),c=i.decimal(i.easeOut(t.img.y.from,t.img.y.to,a,n),2);h.element.style.width=d+"px",h.element.style.height=l+"px",h.element.style.transform="translate3d("+m+"px, "+c+"px, 0)",s<=1?window.requestAnimationFrame(r):(t.img.index===i.index&&(i.isAnimating=!1,i.dragTarget="",i.dragDirection=""),"close"===i.status&&(i.unbindEventListener(),i.container.remove()))}))},t.prototype.wrapRaf=function(t){const i=this;let e,s=0;const n=this.options.duration;this.wrapRafId=window.requestAnimationFrame((function h(r){void 0===e&&(e=r);let o=r-e;o>n&&(o=n,s++),i.wrapX=i.decimal(i.easeOut(t.wrap.x.from,t.wrap.x.to,o,n),2),i.wrap.style.transform="translate3d("+i.wrapX+"px, 0, 0)",s<=1?i.wrapRafId=window.requestAnimationFrame(h):(i.isWrapAnimating=!1,i.dragTarget="",i.dragDirection="")}))},"function"==typeof define&&define.amd?define((function(){return t})):"object"==typeof module&&"object"==typeof exports?module.exports=t:window.NonameGallery=t}();
--------------------------------------------------------------------------------
/src/js/noname-gallery.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 使用PointerEvent实现的图片预览插件
3 | * @param {object} options 配置项
4 | */
5 | function NonameGallery(options) {
6 | // 抛出错误
7 | if (options.list.length === 0) {
8 | throw new Error('options.list can not be empty array');
9 | }
10 | this.options = Object.assign({}, this.defaults, options);
11 | }
12 | /**
13 | * 默认配置项
14 | */
15 | NonameGallery.prototype.defaults = {
16 | list: [], // HTMLImageElement[]
17 | index: 0, // 索引
18 | fadeInOut: true, // 淡入淡出
19 | useTransform: true, // 宽高缩放只能使用requestAnimationFrame,transition同时过渡宽高和transform时会发生抖动以及动画轨迹偏移的问题,手机端微信浏览器尤其严重
20 | verticalZoom: true, // 垂直滑动缩放图片
21 | openKeyboard: false, // 开启键盘 esc关闭,方向键切换图片
22 | zoomToScreenCenter: false, // 将放大区域移动到屏幕中心显示
23 | duration: 300, // 动画持续时间
24 | minScale: 1.5 // 最小放大倍数
25 | }
26 | /**
27 | * 初始化
28 | */
29 | NonameGallery.prototype.init = function () {
30 | // 设置属性值
31 | this.setProperties();
32 | // 设置视口大小
33 | this.setWindowSize();
34 | // 设置previewList
35 | this.setPreviewList();
36 | // 设置wrap宽度和x轴偏移量
37 | this.setWrap();
38 | // 渲染
39 | this.render();
40 | // 获取元素
41 | this.getElement();
42 | // 绑定事件
43 | this.bindEventListener();
44 | // 打开画廊
45 | this.open();
46 | }
47 | /**
48 | * 设置属性值(防止全局实例,多次调用出现问题)
49 | */
50 | NonameGallery.prototype.setProperties = function () {
51 | this.container = null; // .noname-gallery-container
52 | this.bg = null; // .noname-gallery-bg
53 | this.counter = null; // .noname-gallery-counter
54 | this.wrap = null; // .noname-gallery-wrap
55 | this.imgList = null; // .noname-gallery-img
56 | this.bgOpacity = 1; // .noname-gallery-bg 透明度
57 | this.windowWidth = 0; // 视口宽度
58 | this.windowHeight = 0; // 视口高度
59 | this.index = this.options.index; // 预览图片索引
60 | this.wrapWidth = 0; // .noname-gallery-wrap 宽度
61 | this.wrapX = 0; // .noname-gallery-wrap x轴偏移量
62 | this.previewList = []; // 预览图片列表
63 | this.currentImg = {
64 | x: 0, // 当前图片旋转中心(已设置为左上角)相对屏幕左上角偏移值
65 | y: 0, // 当前图片旋转中心(已设置为左上角)相对屏幕左上角偏移值
66 | width: 0, // 当前图片宽度
67 | height: 0, // 当前图片高度
68 | scale: 1, // 当前图片缩放倍数
69 | opacity: 1, // 当前图片透明度 开启淡入淡出时会使用到
70 | status: '' // 当前图片状态 shrink(scale < 1) verticalToClose inertia
71 | };
72 | this.pointers = []; // 指针数组用于保存多个触摸点
73 | this.point1 = { x: 0, y: 0 }; // 第一个触摸点
74 | this.point2 = { x: 0, y: 0 }; // 第二个触摸点
75 | this.diff = { x: 0, y: 0 }; // 相对于上一次移动差值
76 | this.distance = { x: 0, y: 0 }; // 移动距离
77 | this.lastDistance = { x: 0, y: 0 }; // 双指滑动时记录上一次移动距离
78 | this.lastPoint1 = { x: 0, y: 0 }; // 上一次第一个触摸点位置,用于判断双击距离是否大于30
79 | this.lastPoint2 = { x: 0, y: 0 }; // 上一次第二个触摸点位置
80 | this.lastMove = { x: 0, y: 0 }; // 上一次移动坐标
81 | this.lastCenter = { x: 0, y: 0 }; // 上一次双指中心位置
82 | this.tapCount = 0; // 点击次数 1 = 单击 大于1 = 双击
83 | this.dragDirection = ''; // 拖拽方向 v(vertical) h(horizontal)
84 | this.dragTarget = ''; // 拖拽目标 wrap img
85 | this.status = ''; // close 时移除dom
86 | this.isPointerdown = false; // 按下标识
87 | this.isAnimating = true; // 是否正在执行动画
88 | this.isWrapAnimating = false; // 是否正在执行wrap切换动画,不响应键盘事件
89 | this.tapTimeout = null; // 单击延时器 250ms 判断双击
90 | this.pointerdownTime = null; // pointerdown time
91 | this.pointermoveTime = null; // 鼠标松开距离最后一次移动小于200ms执行惯性滑动
92 | this.pinchTime = null; // 距离上一次双指缩放的时间
93 | this.inertiaRafId = null; // requestAnimationFrame id 用于停止惯性滑动动画
94 | this.wrapRafId = null; // requestAnimationFrame id 用于停止wrap滑动动画
95 | }
96 | /**
97 | * 设置视口大小
98 | */
99 | NonameGallery.prototype.setWindowSize = function () {
100 | this.windowWidth = window.innerWidth;
101 | this.windowHeight = window.innerHeight;
102 | }
103 | /**
104 | * 设置previewList
105 | */
106 | NonameGallery.prototype.setPreviewList = function () {
107 | for (const img of this.options.list) {
108 | const rect = img.getBoundingClientRect();
109 | const result = this.getImgSize(img.naturalWidth, img.naturalHeight, this.windowWidth, this.windowHeight);
110 | const maxScale = Math.max(this.decimal(img.naturalWidth / result.width, 5), this.options.minScale);
111 | this.previewList.push({
112 | x: this.decimal((this.windowWidth - result.width) / 2, 2), // 预览图片左上角相对于视口的横坐标
113 | y: this.decimal((this.windowHeight - result.height) / 2, 2), // 预览图片左上角相对于视口的纵坐标
114 | width: this.decimal(result.width, 2), // 预览图片显示宽度
115 | height: this.decimal(result.height, 2), // 预览图片显示高度
116 | maxWidth: this.decimal(result.width * maxScale, 2), // 预览图片最大宽度
117 | maxHeight: this.decimal(result.height * maxScale, 2), // 预览图片最大高度
118 | maxScale: maxScale, // 预览图片最大缩放比例
119 | thumbnail: {
120 | x: this.decimal(rect.left, 2), // 缩略图左上角相对于视口的横坐标
121 | y: this.decimal(rect.top, 2), // 缩略图左上角相对于视口的纵坐标
122 | width: this.decimal(rect.width, 2), // 缩略图显示宽度
123 | height: this.decimal(rect.height, 2), // 缩略图显示高度
124 | scaleX: this.decimal(rect.width / result.width, 5), // 缩略图x轴比例
125 | scaleY: this.decimal(rect.height / result.height, 5) // 缩略图y轴比例
126 | }
127 | });
128 | }
129 | }
130 | /**
131 | * 设置当前图片数据
132 | * @param {number} x 横坐标
133 | * @param {number} y 纵坐标
134 | * @param {number} width 宽度
135 | * @param {number} height 高度
136 | * @param {number} scale 缩放值
137 | * @param {number} opacity 透明度
138 | * @param {string} status 状态 shrink verticalToClose inertia
139 | */
140 | NonameGallery.prototype.setCurrentImg = function (x, y, width, height, scale, opacity, status) {
141 | this.currentImg = {
142 | x: x,
143 | y: y,
144 | width: width,
145 | height: height,
146 | scale: scale,
147 | opacity: opacity,
148 | status: status
149 | };
150 | }
151 | /**
152 | * 设置wrap宽度和x轴偏移量
153 | */
154 | NonameGallery.prototype.setWrap = function () {
155 | this.wrapWidth = this.previewList.length * this.windowWidth;
156 | this.wrapX = this.index * this.windowWidth * -1;
157 | }
158 | /**
159 | * 渲染
160 | */
161 | NonameGallery.prototype.render = function () {
162 | // bg背景透明度过渡效果
163 | let cssText = 'opacity: 0;';
164 | if (this.options.useTransform) {
165 | cssText += 'transition: opacity ' + this.options.duration + 'ms ease-out;';
166 | }
167 | let html = ''
168 | + '
'
169 | + '
' + (this.options.index + 1) + ' / ' + this.options.list.length + '
'
170 | + '
';
171 | for (let i = 0, length = this.options.list.length; i < length; i++) {
172 | const item = this.previewList[i];
173 | if (this.options.useTransform) {
174 | cssText = 'width: ' + item.width + 'px; height: ' + item.height + 'px;';
175 | if (this.index === i) {
176 | cssText += 'transform: translate3d(' + item.thumbnail.x + 'px, ' + item.thumbnail.y + 'px, 0) scale(' + item.thumbnail.scaleX + ', ' + item.thumbnail.scaleY + ');';
177 | } else {
178 | cssText += 'transform: translate3d(' + item.x + 'px, ' + item.y + 'px, 0) scale(1);';
179 | }
180 | cssText += 'transition: transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out;';
181 | } else {
182 | if (this.index === i) {
183 | cssText = 'width: ' + item.thumbnail.width + 'px; height: ' + item.thumbnail.height + 'px;';
184 | cssText += 'transform: translate3d(' + item.thumbnail.x + 'px, ' + item.thumbnail.y + 'px, 0);';
185 | } else {
186 | cssText = 'width: ' + item.width + 'px; height: ' + item.height + 'px;';
187 | cssText += 'transform: translate3d(' + item.x + 'px, ' + item.y + 'px, 0);';
188 | }
189 | }
190 | if (this.index === i && this.options.fadeInOut) {
191 | cssText += 'opacity: 0;';
192 | }
193 | cssText += 'cursor: zoom-in;';
194 | html += '- '
195 | + '
'
196 | + ' ';
197 | }
198 | html += '
';
199 | document.body.insertAdjacentHTML('beforeend', html);
200 | }
201 | /**
202 | * 获取元素
203 | */
204 | NonameGallery.prototype.getElement = function () {
205 | this.container = document.querySelector('.noname-gallery-container');
206 | this.bg = document.querySelector('.noname-gallery-bg');
207 | this.counter = document.querySelector('.noname-gallery-counter');
208 | this.wrap = document.querySelector('.noname-gallery-wrap');
209 | this.imgList = document.querySelectorAll('.noname-gallery-img');
210 | for (let i = 0, length = this.imgList.length; i < length; i++) {
211 | this.previewList[i].element = this.imgList[i]; // HTMLImageElement
212 | }
213 | }
214 | /**
215 | * 打开画廊
216 | */
217 | NonameGallery.prototype.open = function () {
218 | const item = this.previewList[this.index];
219 | if (this.options.useTransform) {
220 | // 强制重绘,否则合并计算样式,导致无法触发过渡效果,或使用setTimeout,个人猜测最短时长等于,1000 / 60 = 16.66666 ≈ 17
221 | window.getComputedStyle(item.element).opacity;
222 | this.bg.style.opacity = '1';
223 | if (this.options.fadeInOut) {
224 | item.element.style.opacity = '1';
225 | }
226 | item.element.style.transform = 'translate3d(' + item.x + 'px,' + item.y + 'px, 0) scale(1)';
227 | } else {
228 | const obj = {
229 | bg: {
230 | opacity: { from: 0, to: 1 }
231 | },
232 | img: {
233 | width: { from: item.thumbnail.width, to: item.width },
234 | height: { from: item.thumbnail.height, to: item.height },
235 | x: { from: item.thumbnail.x, to: item.x },
236 | y: { from: item.thumbnail.y, to: item.y },
237 | index: this.index
238 | }
239 | }
240 | if (this.options.fadeInOut) {
241 | obj.img.opacity = { from: 0, to: 1 };
242 | }
243 | this.raf(obj);
244 | }
245 | // 设置当前图片数据
246 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
247 | }
248 | /**
249 | * 关闭画廊
250 | */
251 | NonameGallery.prototype.close = function () {
252 | this.isAnimating = true;
253 | this.status = 'close';
254 | const item = this.previewList[this.index];
255 | if (this.options.useTransform) {
256 | if (this.options.fadeInOut) {
257 | item.element.style.opacity = '0';
258 | }
259 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
260 | item.element.style.transform = 'translate3d(' + item.thumbnail.x + 'px, ' + item.thumbnail.y + 'px, 0) scale(' + item.thumbnail.scaleX + ', ' + item.thumbnail.scaleY + ')';
261 | this.bg.style.transition = 'opacity ' + this.options.duration + 'ms ease-out';
262 | this.bg.style.opacity = '0';
263 | } else {
264 | const obj = {
265 | bg: {
266 | opacity: { from: this.bgOpacity, to: 0 }
267 | },
268 | img: {
269 | width: { from: this.currentImg.width, to: item.thumbnail.width },
270 | height: { from: this.currentImg.height, to: item.thumbnail.height },
271 | x: { from: this.currentImg.x, to: item.thumbnail.x },
272 | y: { from: this.currentImg.y, to: item.thumbnail.y },
273 | index: this.index
274 | }
275 | }
276 | if (this.options.fadeInOut) {
277 | obj.img.opacity = { from: 1, to: 0 };
278 | }
279 | this.raf(obj);
280 | }
281 | }
282 | /**
283 | * 绑定事件
284 | */
285 | NonameGallery.prototype.bindEventListener = function () {
286 | this.handlePointerdown = this.handlePointerdown.bind(this);
287 | this.handlePointermove = this.handlePointermove.bind(this);
288 | this.handlePointerup = this.handlePointerup.bind(this);
289 | this.handlePointercancel = this.handlePointercancel.bind(this);
290 | this.handleResize = this.handleResize.bind(this);
291 | this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
292 | this.container.addEventListener('pointerdown', this.handlePointerdown);
293 | this.container.addEventListener('pointermove', this.handlePointermove);
294 | this.container.addEventListener('pointerup', this.handlePointerup);
295 | this.container.addEventListener('pointercancel', this.handlePointercancel);
296 | this.container.addEventListener('transitionend', this.handleTransitionEnd);
297 | window.addEventListener('resize', this.handleResize);
298 | window.addEventListener('orientationchange', this.handleResize);
299 | if (this.options.openKeyboard) {
300 | this.handleKeydown = this.handleKeydown.bind(this);
301 | window.addEventListener('keydown', this.handleKeydown);
302 | }
303 | }
304 | /**
305 | * 解绑事件
306 | */
307 | NonameGallery.prototype.unbindEventListener = function () {
308 | this.container.removeEventListener('pointerdown', this.handlePointerdown);
309 | this.container.removeEventListener('pointermove', this.handlePointermove);
310 | this.container.removeEventListener('pointerup', this.handlePointerup);
311 | this.container.removeEventListener('pointercancel', this.handlePointercancel);
312 | this.container.removeEventListener('transitionend', this.handleTransitionEnd);
313 | window.removeEventListener('resize', this.handleResize);
314 | window.removeEventListener('orientationchange', this.handleResize);
315 | if (this.options.openKeyboard) {
316 | window.removeEventListener('keydown', this.handleKeydown);
317 | }
318 | }
319 | /**
320 | * 处理pointerdown
321 | * @param {PointerEvent} e
322 | */
323 | NonameGallery.prototype.handlePointerdown = function (e) {
324 | // 非鼠标左键点击或正在执行开始动画,缩放动画,垂直滑动、双指缩小恢复动画,结束动画
325 | if (e.pointerType === 'mouse' && e.button !== 0 || this.isAnimating) {
326 | return;
327 | }
328 | this.pointers.push(e);
329 | this.point1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
330 | if (this.pointers.length === 1) {
331 | this.container.setPointerCapture(e.pointerId);
332 | this.isPointerdown = true;
333 | this.distance = { x: 0, y: 0 };
334 | this.lastDistance = { x: 0, y: 0 };
335 | this.pointerdownTime = Date.now();
336 | this.pinchTime = null;
337 | this.lastMove = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
338 | if (this.isWrapAnimating === false) {
339 | this.tapCount++;
340 | }
341 | // 双击两点距离不超过30
342 | if (this.tapCount > 1 && (Math.abs(this.point1.x - this.lastPoint1.x) > 30 || Math.abs(this.point1.y - this.lastPoint1.y) > 30)) {
343 | this.tapCount = 1;
344 | }
345 | clearTimeout(this.tapTimeout);
346 | window.cancelAnimationFrame(this.inertiaRafId);
347 | window.cancelAnimationFrame(this.wrapRafId);
348 | } else if (this.pointers.length === 2) {
349 | this.tapCount = 0;
350 | this.point2 = { x: this.pointers[1].clientX, y: this.pointers[1].clientY };
351 | this.lastCenter = this.getCenter(this.point1, this.point2);
352 | this.lastDistance = { x: this.distance.x, y: this.distance.y };
353 | this.lastPoint2 = { x: this.pointers[1].clientX, y: this.pointers[1].clientY };
354 | if (this.dragTarget === '') {
355 | this.dragTarget = 'img';
356 | }
357 | }
358 | this.lastPoint1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
359 | }
360 | /**
361 | * 处理pointermove
362 | * @param {PointerEvent} e
363 | */
364 | NonameGallery.prototype.handlePointermove = function (e) {
365 | if (!this.isPointerdown) {
366 | return;
367 | }
368 | this.handlePointers(e, 'update');
369 | const current1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
370 | if (this.pointers.length === 1) {
371 | this.diff = { x: current1.x - this.lastMove.x, y: current1.y - this.lastMove.y };
372 | this.distance = { x: current1.x - this.point1.x + this.lastDistance.x, y: current1.y - this.point1.y + this.lastDistance.y };
373 | this.lastMove = { x: current1.x, y: current1.y };
374 | this.pointermoveTime = Date.now();
375 | if (Math.abs(this.distance.x) > 10 || Math.abs(this.distance.y) > 10) {
376 | this.tapCount = 0;
377 | // 偏移量大于10才判断dragDirection和dragTarget
378 | if (this.dragDirection === '' && this.dragTarget === '') {
379 | this.getDragDirection();
380 | this.getDragTarget();
381 | }
382 | }
383 | if (this.dragTarget === 'wrap') {
384 | this.handleWrapPointermove();
385 | } else if (this.dragTarget === 'img') {
386 | this.handleImgPointermove();
387 | }
388 | } else if (this.pointers.length === 2) {
389 | const current2 = { x: this.pointers[1].clientX, y: this.pointers[1].clientY };
390 | if (this.dragTarget === 'img' && this.currentImg.status !== 'verticalToClose') {
391 | this.handlePinch(current1, current2);
392 | }
393 | this.lastPoint1 = { x: current1.x, y: current1.y };
394 | this.lastPoint2 = { x: current2.x, y: current2.y };
395 | }
396 | // 阻止默认事件,例如拖拽图片
397 | e.preventDefault();
398 | }
399 | /**
400 | * 处理pointerup
401 | * @param {PointerEvent} e
402 | */
403 | NonameGallery.prototype.handlePointerup = function (e) {
404 | if (!this.isPointerdown) {
405 | return;
406 | }
407 | this.handlePointers(e, 'delete');
408 | if (this.pointers.length === 0) {
409 | this.isPointerdown = false;
410 | if (this.tapCount === 0) {
411 | if (this.dragTarget === 'wrap') {
412 | this.handleWrapPointerup();
413 | } else if (this.dragTarget === 'img') {
414 | this.handleImgPointerup();
415 | }
416 | } else if (this.tapCount === 1) {
417 | if (e.pointerType === 'mouse') {
418 | // 由于调用过setPointerCapture方法,导致无法使用e.target来判断触发事件的元素,所以只能根据点击位置来判断
419 | if (e.clientX >= this.currentImg.x && e.clientX <= this.currentImg.x + this.currentImg.width &&
420 | e.clientY >= this.currentImg.y && e.clientY <= this.currentImg.y + this.currentImg.height) {
421 | this.handleZoom({ x: e.clientX, y: e.clientY });
422 | } else {
423 | this.close();
424 | }
425 | } else {
426 | // 触发移动端长按保存图片后不关闭画廊
427 | if (Date.now() - this.pointerdownTime < 500) {
428 | this.tapTimeout = setTimeout(() => {
429 | this.close();
430 | }, 250);
431 | } else {
432 | this.tapCount = 0;
433 | }
434 | }
435 | } else if (this.tapCount > 1) {
436 | this.handleZoom({ x: e.clientX, y: e.clientY });
437 | }
438 | } else if (this.pointers.length === 1) {
439 | this.point1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
440 | this.lastMove = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
441 | }
442 | }
443 | /**
444 | * 处理pointercancel
445 | * @param {PointerEvent} e
446 | */
447 | NonameGallery.prototype.handlePointercancel = function (e) {
448 | this.tapCount = 0;
449 | this.isPointerdown = false;
450 | this.pointers.length = 0;
451 | if (this.isWrapAnimating) {
452 | // 长按图片呼出菜单后继续执行wrap动画
453 | this.handleWrapPointerup();
454 | }
455 | }
456 | /**
457 | * 更新或删除指针
458 | * @param {PointerEvent} e
459 | * @param {string} type update delete
460 | */
461 | NonameGallery.prototype.handlePointers = function (e, type) {
462 | for (let i = 0; i < this.pointers.length; i++) {
463 | if (this.pointers[i].pointerId === e.pointerId) {
464 | if (type === 'update') {
465 | this.pointers[i] = e;
466 | } else if (type === 'delete') {
467 | this.pointers.splice(i, 1);
468 | }
469 | }
470 | }
471 | }
472 | /**
473 | * 处理视口宽高
474 | */
475 | NonameGallery.prototype.handleResize = function () {
476 | // 设置视口大小
477 | this.setWindowSize();
478 | // 设置previewList
479 | this.previewList.length = 0;
480 | this.setPreviewList();
481 | // 设置当前图片数据
482 | const item = this.previewList[this.index];
483 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
484 | // 设置wrap宽度和x轴偏移量
485 | this.setWrap();
486 | this.wrap.style.width = this.wrapWidth + 'px';
487 | this.wrap.style.transform = 'translate3d(' + this.wrapX + 'px, 0, 0)';
488 | // 设置图片数据
489 | for (let i = 0, length = this.imgList.length; i < length; i++) {
490 | const item = this.previewList[i];
491 | item.element = this.imgList[i];
492 | item.element.style.width = item.width + 'px';
493 | item.element.style.height = item.height + 'px';
494 | if (this.options.useTransform) {
495 | item.element.style.transition = 'none';
496 | item.element.style.transform = 'translate3d(' + item.x + 'px, ' + item.y + 'px, 0) scale(1)';
497 | } else {
498 | item.element.style.transform = 'translate3d(' + item.x + 'px, ' + item.y + 'px, 0)';
499 | }
500 | item.element.style.cursor = 'zoom-in';
501 | }
502 | if (this.options.useTransform) {
503 | this.bg.style.transition = 'none';
504 | }
505 | this.bgOpacity = 1;
506 | this.bg.style.opacity = this.bgOpacity;
507 | this.tapCount = 0;
508 | }
509 | /**
510 | * 处理keydown
511 | * @param {KeyboardEvent} e
512 | */
513 | NonameGallery.prototype.handleKeydown = function (e) {
514 | if (this.isAnimating || this.isWrapAnimating) {
515 | return;
516 | }
517 | const lastIndex = this.index;
518 | if (e.key === 'Escape') {
519 | this.close();
520 | } else if (['ArrowLeft', 'ArrowUp'].includes(e.key) && lastIndex > 0) {
521 | this.index--;
522 | } else if (['ArrowRight', 'ArrowDown'].includes(e.key) && lastIndex < this.previewList.length - 1) {
523 | this.index++;
524 | }
525 | window.cancelAnimationFrame(this.inertiaRafId);
526 | this.handleWrapSwipe();
527 | this.handleLastImg(lastIndex);
528 | }
529 | /**
530 | * 处理缩放
531 | */
532 | NonameGallery.prototype.handleZoom = function (point) {
533 | this.isAnimating = true;
534 | // 缩放时重置点击计数器
535 | this.tapCount = 0;
536 | const item = this.previewList[this.index];
537 | if (this.currentImg.scale > 1) {
538 | if (this.options.useTransform) {
539 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
540 | item.element.style.transform = 'translate3d(' + item.x + 'px,' + item.y + 'px, 0) scale(1)';
541 | } else {
542 | const obj = {
543 | img: {
544 | width: { from: this.currentImg.width, to: item.width },
545 | height: { from: this.currentImg.height, to: item.height },
546 | x: { from: this.currentImg.x, to: item.x },
547 | y: { from: this.currentImg.y, to: item.y },
548 | index: this.index
549 | }
550 | }
551 | this.raf(obj);
552 | }
553 | item.element.style.cursor = 'zoom-in';
554 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
555 | } else {
556 | const halfWindowWidth = this.windowWidth / 2;
557 | const halfWindowHeight = this.windowHeight / 2;
558 | const left = this.decimal((point.x - item.x) * item.maxScale, 2);
559 | const top = this.decimal((point.y - item.y) * item.maxScale, 2);
560 | let x, y;
561 | if (item.maxWidth > this.windowWidth) {
562 | if (this.options.zoomToScreenCenter) {
563 | x = halfWindowWidth - left;
564 | } else {
565 | x = point.x - left;
566 | }
567 | if (x > 0) {
568 | x = 0;
569 | } else if (x < this.windowWidth - item.maxWidth) {
570 | x = this.windowWidth - item.maxWidth;
571 | }
572 | } else {
573 | x = (this.windowWidth - item.maxWidth) / 2;
574 | }
575 | x = this.decimal(x, 2);
576 | if (item.maxHeight > this.windowHeight) {
577 | if (this.options.zoomToScreenCenter) {
578 | y = halfWindowHeight - top;
579 | } else {
580 | y = point.y - top;
581 | }
582 | if (y > 0) {
583 | y = 0;
584 | } else if (y < this.windowHeight - item.maxHeight) {
585 | y = this.windowHeight - item.maxHeight;
586 | }
587 | } else {
588 | y = (this.windowHeight - item.maxHeight) / 2;
589 | }
590 | y = this.decimal(y, 2);
591 | if (this.options.useTransform) {
592 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
593 | item.element.style.transform = 'translate3d(' + x + 'px,' + y + 'px, 0) scale(' + item.maxScale + ')';
594 | } else {
595 | const obj = {
596 | img: {
597 | width: { from: item.width, to: item.maxWidth },
598 | height: { from: item.height, to: item.maxHeight },
599 | x: { from: item.x, to: x },
600 | y: { from: item.y, to: y },
601 | index: this.index
602 | }
603 | }
604 | this.raf(obj);
605 | }
606 | item.element.style.cursor = 'zoom-out';
607 | this.setCurrentImg(x, y, item.maxWidth, item.maxHeight, item.maxScale, 1, '');
608 | }
609 | }
610 | /**
611 | * 处理双指缩放
612 | * @param {object} a 第一个点的位置
613 | * @param {object} b 第二个点的位置
614 | */
615 | NonameGallery.prototype.handlePinch = function (a, b) {
616 | const MIN_SCALE = 0.7;
617 | this.pinchTime = Date.now();
618 | let ratio = this.getDistance(a, b) / this.getDistance(this.lastPoint1, this.lastPoint2);
619 | let scale = this.decimal(this.currentImg.scale * ratio, 5);
620 | const item = this.previewList[this.index];
621 | if (scale > item.maxScale) {
622 | ratio = item.maxScale / this.currentImg.scale;
623 | this.currentImg.scale = item.maxScale;
624 | this.currentImg.width = item.maxWidth;
625 | this.currentImg.height = item.maxHeight;
626 | } else if (scale < MIN_SCALE) {
627 | ratio = MIN_SCALE / this.currentImg.scale;
628 | this.currentImg.scale = MIN_SCALE;
629 | this.currentImg.width = this.decimal(item.width * MIN_SCALE, 2);
630 | this.currentImg.height = this.decimal(item.height * MIN_SCALE, 2);
631 | } else {
632 | this.currentImg.scale = scale;
633 | this.currentImg.width = this.decimal(this.currentImg.width * ratio, 2);
634 | this.currentImg.height = this.decimal(this.currentImg.height * ratio, 2);
635 | }
636 | this.currentImg.status = this.currentImg.scale < 1 ? 'shrink' : '';
637 | const center = this.getCenter(a, b);
638 | // 计算偏移量
639 | this.currentImg.x -= (ratio - 1) * (center.x - this.currentImg.x) - center.x + this.lastCenter.x;
640 | this.currentImg.y -= (ratio - 1) * (center.y - this.currentImg.y) - center.y + this.lastCenter.y;
641 | this.lastCenter = { x: center.x, y: center.y };
642 | this.handleBoundary();
643 | if (this.options.useTransform) {
644 | item.element.style.transition = 'none';
645 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0) scale(' + this.currentImg.scale + ')';
646 | } else {
647 | item.element.style.width = this.currentImg.width + 'px';
648 | item.element.style.height = this.currentImg.height + 'px';
649 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0)';
650 | }
651 | }
652 | /**
653 | * 处理边界
654 | */
655 | NonameGallery.prototype.handleBoundary = function () {
656 | if (this.currentImg.width > this.windowWidth) {
657 | if (this.currentImg.x > 0) {
658 | this.currentImg.x = 0
659 | } else if (this.currentImg.x < this.windowWidth - this.currentImg.width) {
660 | this.currentImg.x = this.windowWidth - this.currentImg.width;
661 | }
662 | } else {
663 | this.currentImg.x = (this.windowWidth - this.currentImg.width) / 2;
664 | }
665 | if (this.currentImg.height > this.windowHeight) {
666 | if (this.currentImg.y > 0) {
667 | this.currentImg.y = 0
668 | } else if (this.currentImg.y < this.windowHeight - this.currentImg.height) {
669 | this.currentImg.y = this.windowHeight - this.currentImg.height;
670 | }
671 | } else {
672 | this.currentImg.y = (this.windowHeight - this.currentImg.height) / 2;
673 | }
674 | }
675 | /**
676 | * 获取图片缩放尺寸
677 | * @param {number} naturalWidth 图片自然宽度
678 | * @param {number} naturalHeight 图片自然高度
679 | * @param {number} maxWidth 图片显示最大宽度
680 | * @param {number} maxHeight 图片显示最大高度
681 | * @returns
682 | */
683 | NonameGallery.prototype.getImgSize = function (naturalWidth, naturalHeight, maxWidth, maxHeight) {
684 | const imgRatio = naturalWidth / naturalHeight;
685 | const maxRatio = maxWidth / maxHeight;
686 | let width, height;
687 | // 如果图片自然宽高比例 >= 显示宽高比例
688 | if (imgRatio >= maxRatio) {
689 | if (naturalWidth > maxWidth) {
690 | width = maxWidth;
691 | height = maxWidth / naturalWidth * naturalHeight;
692 | } else {
693 | width = naturalWidth;
694 | height = naturalHeight;
695 | }
696 | } else {
697 | if (naturalHeight > maxHeight) {
698 | width = maxHeight / naturalHeight * naturalWidth;
699 | height = maxHeight;
700 | } else {
701 | width = naturalWidth;
702 | height = naturalHeight;
703 | }
704 | }
705 | return { width: width, height: height }
706 | }
707 | /**
708 | * 获取拖拽方向
709 | */
710 | NonameGallery.prototype.getDragDirection = function () {
711 | if (Math.abs(this.distance.x) > Math.abs(this.distance.y)) {
712 | this.dragDirection = 'h';
713 | } else {
714 | this.dragDirection = 'v';
715 | }
716 | }
717 | /**
718 | * 获取拖拽目标
719 | */
720 | NonameGallery.prototype.getDragTarget = function () {
721 | let flag1 = false, flag2 = false;
722 | if (this.currentImg.width > this.windowWidth) {
723 | if (
724 | (this.diff.x > 0 && this.currentImg.x === 0) ||
725 | (this.diff.x < 0 && this.currentImg.x === this.windowWidth - this.currentImg.width)
726 | ) {
727 | flag1 = true;
728 | }
729 | } else {
730 | if (this.currentImg.width >= this.previewList[this.index].width) {
731 | flag2 = true;
732 | }
733 | }
734 | if (this.dragDirection === 'h' && (flag1 || flag2)) {
735 | this.dragTarget = 'wrap';
736 | } else {
737 | this.dragTarget = 'img';
738 | }
739 | }
740 | /**
741 | * 获取两点距离
742 | * @param {object} a 第一个点的位置
743 | * @param {object} b 第二个点的位置
744 | * @returns
745 | */
746 | NonameGallery.prototype.getDistance = function (a, b) {
747 | const x = a.x - b.x;
748 | const y = a.y - b.y;
749 | return Math.hypot(x, y); // Math.sqrt(x * x + y * y);
750 | }
751 | /**
752 | * 处理wrap移动
753 | */
754 | NonameGallery.prototype.handleWrapPointermove = function () {
755 | if (this.wrapX > 0 || this.wrapX < (this.previewList.length - 1) * this.windowWidth * - 1) {
756 | this.wrapX += this.diff.x * 0.3;
757 | } else {
758 | this.wrapX += this.diff.x;
759 | const LEFT_X = (this.index - 1) * this.windowWidth * -1;
760 | const RIGHT_X = (this.index + 1) * this.windowWidth * -1;
761 | if (this.wrapX > LEFT_X) {
762 | this.wrapX = LEFT_X;
763 | } else if (this.wrapX < RIGHT_X) {
764 | this.wrapX = RIGHT_X;
765 | }
766 | }
767 | this.wrap.style.transform = 'translate3d(' + this.wrapX + 'px, 0, 0)';
768 | }
769 | /**
770 | * 处理img移动
771 | */
772 | NonameGallery.prototype.handleImgPointermove = function () {
773 | const item = this.previewList[this.index];
774 | // 如果图片当前宽高大于视口宽高,拖拽查看图片,可惯性滚动
775 | if (this.currentImg.width > this.windowWidth || this.currentImg.height > this.windowHeight) {
776 | this.currentImg.x += this.diff.x;
777 | this.currentImg.y += this.diff.y;
778 | this.handleBoundary();
779 | this.currentImg.status = 'inertia';
780 | if (this.options.useTransform) {
781 | item.element.style.transition = 'none';
782 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0) scale(' + this.currentImg.scale + ')';
783 | } else {
784 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0)';
785 | }
786 | } else {
787 | // 如果垂直拖拽图片且图片未被放大(某些图片尺寸放到最大也没有超出视口宽高)
788 | if (this.dragDirection === 'v' && this.currentImg.width <= item.width && this.currentImg.height <= item.height) {
789 | this.currentImg.status = 'verticalToClose';
790 | this.bgOpacity = this.decimal(1 - Math.abs(this.distance.y) / (this.windowHeight / 1.2), 5);
791 | if (this.bgOpacity < 0) {
792 | this.bgOpacity = 0;
793 | }
794 | if (this.options.verticalZoom) {
795 | this.currentImg.scale = this.bgOpacity;
796 | this.currentImg.width = this.decimal(item.width * this.currentImg.scale, 2);
797 | this.currentImg.height = this.decimal(item.height * this.currentImg.scale, 2);
798 | this.currentImg.x = item.x + this.distance.x + (item.width - this.currentImg.width) / 2;
799 | this.currentImg.y = item.y + this.distance.y + (item.height - this.currentImg.height) / 2;
800 | } else {
801 | this.currentImg.x = item.x;
802 | this.currentImg.y = item.y + this.distance.y;
803 | this.currentImg.scale = 1;
804 | }
805 | this.bg.style.opacity = this.bgOpacity;
806 | if (this.options.useTransform) {
807 | this.bg.style.transition = 'none';
808 | item.element.style.transition = 'none';
809 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px , 0) scale(' + this.currentImg.scale + ')';
810 | } else {
811 | item.element.style.width = this.currentImg.width + 'px';
812 | item.element.style.height = this.currentImg.height + 'px';
813 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px , 0)';
814 | }
815 | }
816 | }
817 | }
818 | /**
819 | * 处理wrap移动结束
820 | */
821 | NonameGallery.prototype.handleWrapPointerup = function () {
822 | // 拖拽距离超过屏幕宽度10%即可切换下一张图片
823 | const MIN_SWIPE_DISTANCE = Math.round(this.windowWidth * 0.1);
824 | const lastIndex = this.index;
825 | if (Math.abs(this.distance.x) > MIN_SWIPE_DISTANCE) {
826 | if (this.distance.x > 0 && lastIndex > 0) {
827 | this.index--;
828 | } else if (this.distance.x < 0 && lastIndex < this.previewList.length - 1) {
829 | this.index++;
830 | }
831 | }
832 | this.handleWrapSwipe();
833 | this.handleLastImg(lastIndex);
834 | }
835 | /**
836 | * 处理img移动结束
837 | */
838 | NonameGallery.prototype.handleImgPointerup = function () {
839 | // 垂直滑动距离超过屏幕高度10%即可关闭画廊
840 | const MIN_CLOSE_DISTANCE = Math.round(this.windowHeight * 0.1);
841 | const item = this.previewList[this.index];
842 | const now = Date.now();
843 | if (this.currentImg.status === 'inertia' && now - this.pointermoveTime < 200 && now - this.pinchTime > 1000) {
844 | this.handleInertia();
845 | } else if (this.currentImg.status === 'verticalToClose' && Math.abs(this.distance.y) >= MIN_CLOSE_DISTANCE) {
846 | this.close();
847 | } else if (this.currentImg.status === 'shrink' || (this.currentImg.status == 'verticalToClose' && Math.abs(this.distance.y) < MIN_CLOSE_DISTANCE)) {
848 | this.isAnimating = true;
849 | if (this.options.useTransform) {
850 | this.bg.style.opacity = '1';
851 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
852 | item.element.style.transform = 'translate3d(' + item.x + 'px, ' + item.y + 'px, 0) scale(1)';
853 | } else {
854 | const obj = {
855 | bg: {
856 | opacity: { from: this.bgOpacity, to: 1 }
857 | },
858 | img: {
859 | width: { from: this.currentImg.width, to: item.width },
860 | height: { from: this.currentImg.height, to: item.height },
861 | x: { from: this.currentImg.x, to: item.x },
862 | y: { from: this.currentImg.y, to: item.y },
863 | index: this.index
864 | }
865 | }
866 | this.raf(obj);
867 | }
868 | this.bgOpacity = 1;
869 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
870 | }
871 | if (this.isAnimating === false) {
872 | this.dragTarget = '';
873 | this.dragDirection = '';
874 | }
875 | }
876 | /**
877 | * 处理惯性滚动
878 | */
879 | NonameGallery.prototype.handleInertia = function () {
880 | const item = this.previewList[this.index];
881 | const speed = { x: this.diff.x, y: this.diff.y };
882 | const self = this;
883 | function step(timestamp) {
884 | speed.x *= 0.95;
885 | speed.y *= 0.95;
886 | self.currentImg.x = self.decimal(self.currentImg.x + speed.x, 2);
887 | self.currentImg.y = self.decimal(self.currentImg.y + speed.y, 2);
888 | self.handleBoundary();
889 | if (self.options.useTransform) {
890 | item.element.style.transform = 'translate3d(' + self.currentImg.x + 'px, ' + self.currentImg.y + 'px, 0) scale(' + self.currentImg.scale + ')';
891 | } else {
892 | item.element.style.transform = 'translate3d(' + self.currentImg.x + 'px, ' + self.currentImg.y + 'px, 0)';
893 | }
894 | if (Math.abs(speed.x) > 1 || Math.abs(speed.y) > 1) {
895 | self.inertiaRafId = window.requestAnimationFrame(step);
896 | }
897 | }
898 | this.inertiaRafId = window.requestAnimationFrame(step);
899 | }
900 | /**
901 | * 处理wrap滑动
902 | */
903 | NonameGallery.prototype.handleWrapSwipe = function () {
904 | this.isWrapAnimating = true;
905 | const obj = {
906 | wrap: {
907 | x: { from: this.wrapX, to: this.windowWidth * this.index * -1 }
908 | }
909 | }
910 | this.wrapRaf(obj);
911 | this.counter.innerHTML = (this.index + 1) + ' / ' + this.previewList.length;
912 | }
913 | /**
914 | * 处理上一张图片
915 | * @param {number} lastIndex
916 | */
917 | NonameGallery.prototype.handleLastImg = function (lastIndex) {
918 | // 根据索引判断是否切换图片
919 | if (this.index !== lastIndex) {
920 | // 如果上一张图片放大过,则恢复
921 | if (this.currentImg.scale > 1) {
922 | const lastItem = this.previewList[lastIndex];
923 | if (this.options.useTransform) {
924 | lastItem.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
925 | lastItem.element.style.transform = 'translate3d(' + lastItem.x + 'px, ' + lastItem.y + 'px, 0) scale(1)';
926 | } else {
927 | const obj = {
928 | img: {
929 | width: { from: this.currentImg.width, to: lastItem.width },
930 | height: { from: this.currentImg.height, to: lastItem.height },
931 | x: { from: this.currentImg.x, to: lastItem.x },
932 | y: { from: this.currentImg.y, to: lastItem.y },
933 | index: lastIndex
934 | }
935 | }
936 | this.raf(obj);
937 | }
938 | lastItem.element.style.cursor = 'zoom-in';
939 | }
940 | // 设置当前图片数据
941 | const item = this.previewList[this.index];
942 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
943 | }
944 | }
945 | /**
946 | * 过渡结束回调
947 | * @param {TransitionEvent } e
948 | */
949 | NonameGallery.prototype.handleTransitionEnd = function (e) {
950 | // 过滤掉bg transitionend
951 | if (e.target.tagName === 'IMG') {
952 | // wrap滑动,上一张图片恢复动画完成后,不清除dragTarget,因为wrap动画可打断
953 | if (e.target === this.previewList[this.index].element) {
954 | this.isAnimating = false;
955 | this.dragTarget = '';
956 | this.dragDirection = '';
957 | }
958 | if (this.status === 'close') {
959 | // 解绑事件
960 | this.unbindEventListener();
961 | this.container.remove();
962 | }
963 | }
964 | }
965 | /**
966 | * 保留n位小数
967 | * @param {number} num 数字
968 | * @param {number} n n位小数
969 | * @returns
970 | */
971 | NonameGallery.prototype.decimal = function (num, n) {
972 | const x = Math.pow(10, n);
973 | return Math.round(num * x) / x;
974 | }
975 | /**
976 | * 获取中心点
977 | * @param {object} a 第一个点的位置
978 | * @param {object} b 第二个点的位置
979 | * @returns
980 | */
981 | NonameGallery.prototype.getCenter = function (a, b) {
982 | const x = (a.x + b.x) / 2;
983 | const y = (a.y + b.y) / 2;
984 | return { x: x, y: y };
985 | }
986 | /**
987 | * 曲线函数
988 | * @param {number} from 开始位置
989 | * @param {number} to 结束位置
990 | * @param {number} time 动画已执行的时间
991 | * @param {number} duration 动画时长
992 | * @returns
993 | */
994 | NonameGallery.prototype.easeOut = function (from, to, time, duration) {
995 | const change = to - from;
996 | const t = time / duration;
997 | return -change * t * (t - 2) + from;
998 | }
999 | /**
1000 | * 开始、结束、缩放、恢复(例如下滑关闭未达到临界值)动画函数
1001 | * @param {object} obj 属性
1002 | */
1003 | NonameGallery.prototype.raf = function (obj) {
1004 | const self = this;
1005 | let start;
1006 | let count = 0;
1007 | const duration = this.options.duration;
1008 | const item = this.previewList[obj.img.index];
1009 | function step(timestamp) {
1010 | if (start === undefined) {
1011 | start = timestamp;
1012 | }
1013 | let time = timestamp - start;
1014 | if (time > duration) {
1015 | time = duration;
1016 | count++;
1017 | }
1018 | if (obj.bg) {
1019 | const bgOpacity = self.decimal(self.easeOut(obj.bg.opacity.from, obj.bg.opacity.to, time, duration), 5);
1020 | self.bg.style.opacity = bgOpacity;
1021 | }
1022 | if (obj.img.opacity) {
1023 | const opacity = self.decimal(self.easeOut(obj.img.opacity.from, obj.img.opacity.to, time, duration), 5);
1024 | item.element.style.opacity = opacity;
1025 | }
1026 | const width = self.decimal(self.easeOut(obj.img.width.from, obj.img.width.to, time, duration), 2);
1027 | const height = self.decimal(self.easeOut(obj.img.height.from, obj.img.height.to, time, duration), 2);
1028 | const x = self.decimal(self.easeOut(obj.img.x.from, obj.img.x.to, time, duration), 2);
1029 | const y = self.decimal(self.easeOut(obj.img.y.from, obj.img.y.to, time, duration), 2);
1030 | item.element.style.width = width + 'px';
1031 | item.element.style.height = height + 'px';
1032 | item.element.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
1033 | if (count <= 1) {
1034 | window.requestAnimationFrame(step);
1035 | } else {
1036 | if (obj.img.index === self.index) {
1037 | self.isAnimating = false;
1038 | self.dragTarget = '';
1039 | self.dragDirection = '';
1040 | }
1041 | if (self.status === 'close') {
1042 | self.unbindEventListener();
1043 | self.container.remove();
1044 | }
1045 | }
1046 | }
1047 | window.requestAnimationFrame(step);
1048 | }
1049 | /**
1050 | * 动画函数
1051 | * @param {object} obj
1052 | */
1053 | NonameGallery.prototype.wrapRaf = function (obj) {
1054 | const self = this;
1055 | let start;
1056 | let count = 0;
1057 | const duration = this.options.duration;
1058 | function step(timestamp) {
1059 | if (start === undefined) {
1060 | start = timestamp;
1061 | }
1062 | let time = timestamp - start;
1063 | if (time > duration) {
1064 | time = duration;
1065 | count++;
1066 | }
1067 | self.wrapX = self.decimal(self.easeOut(obj.wrap.x.from, obj.wrap.x.to, time, duration), 2);
1068 | self.wrap.style.transform = 'translate3d(' + self.wrapX + 'px, 0, 0)';
1069 | if (count <= 1) {
1070 | self.wrapRafId = window.requestAnimationFrame(step);
1071 | } else {
1072 | self.isWrapAnimating = false;
1073 | self.dragTarget = '';
1074 | self.dragDirection = '';
1075 | }
1076 | }
1077 | this.wrapRafId = window.requestAnimationFrame(step);
1078 | }
--------------------------------------------------------------------------------
/dist/noname-gallery.js:
--------------------------------------------------------------------------------
1 | ; (function () {
2 | 'use strict';
3 | /**
4 | * 使用PointerEvent实现的图片预览插件
5 | * @param {object} options 配置项
6 | */
7 | function NonameGallery(options) {
8 | // 抛出错误
9 | if (options.list.length === 0) {
10 | throw new Error('options.list can not be empty array');
11 | }
12 | this.options = Object.assign({}, this.defaults, options);
13 | }
14 | /**
15 | * 默认配置项
16 | */
17 | NonameGallery.prototype.defaults = {
18 | list: [], // HTMLImageElement[]
19 | index: 0, // 索引
20 | fadeInOut: true, // 淡入淡出
21 | useTransform: true, // 宽高缩放只能使用requestAnimationFrame,transition同时过渡宽高和transform时会发生抖动以及动画轨迹偏移的问题,手机端微信浏览器尤其严重
22 | verticalZoom: true, // 垂直滑动缩放图片
23 | openKeyboard: false, // 开启键盘 esc关闭,方向键切换图片
24 | zoomToScreenCenter: false, // 将放大区域移动到屏幕中心显示
25 | duration: 300, // 动画持续时间
26 | minScale: 1.5 // 最小放大倍数
27 | }
28 | /**
29 | * 初始化
30 | */
31 | NonameGallery.prototype.init = function () {
32 | // 设置属性值
33 | this.setProperties();
34 | // 设置视口大小
35 | this.setWindowSize();
36 | // 设置previewList
37 | this.setPreviewList();
38 | // 设置wrap宽度和x轴偏移量
39 | this.setWrap();
40 | // 渲染
41 | this.render();
42 | // 获取元素
43 | this.getElement();
44 | // 绑定事件
45 | this.bindEventListener();
46 | // 打开画廊
47 | this.open();
48 | }
49 | /**
50 | * 设置属性值(防止全局实例,多次调用出现问题)
51 | */
52 | NonameGallery.prototype.setProperties = function () {
53 | this.container = null; // .noname-gallery-container
54 | this.bg = null; // .noname-gallery-bg
55 | this.counter = null; // .noname-gallery-counter
56 | this.wrap = null; // .noname-gallery-wrap
57 | this.imgList = null; // .noname-gallery-img
58 | this.bgOpacity = 1; // .noname-gallery-bg 透明度
59 | this.windowWidth = 0; // 视口宽度
60 | this.windowHeight = 0; // 视口高度
61 | this.index = this.options.index; // 预览图片索引
62 | this.wrapWidth = 0; // .noname-gallery-wrap 宽度
63 | this.wrapX = 0; // .noname-gallery-wrap x轴偏移量
64 | this.previewList = []; // 预览图片列表
65 | this.currentImg = {
66 | x: 0, // 当前图片旋转中心(已设置为左上角)相对屏幕左上角偏移值
67 | y: 0, // 当前图片旋转中心(已设置为左上角)相对屏幕左上角偏移值
68 | width: 0, // 当前图片宽度
69 | height: 0, // 当前图片高度
70 | scale: 1, // 当前图片缩放倍数
71 | opacity: 1, // 当前图片透明度 开启淡入淡出时会使用到
72 | status: '' // 当前图片状态 shrink(scale < 1) verticalToClose inertia
73 | };
74 | this.pointers = []; // 指针数组用于保存多个触摸点
75 | this.point1 = { x: 0, y: 0 }; // 第一个触摸点
76 | this.point2 = { x: 0, y: 0 }; // 第二个触摸点
77 | this.diff = { x: 0, y: 0 }; // 相对于上一次移动差值
78 | this.distance = { x: 0, y: 0 }; // 移动距离
79 | this.lastDistance = { x: 0, y: 0 }; // 双指滑动时记录上一次移动距离
80 | this.lastPoint1 = { x: 0, y: 0 }; // 上一次第一个触摸点位置,用于判断双击距离是否大于30
81 | this.lastPoint2 = { x: 0, y: 0 }; // 上一次第二个触摸点位置
82 | this.lastMove = { x: 0, y: 0 }; // 上一次移动坐标
83 | this.lastCenter = { x: 0, y: 0 }; // 上一次双指中心位置
84 | this.tapCount = 0; // 点击次数 1 = 单击 大于1 = 双击
85 | this.dragDirection = ''; // 拖拽方向 v(vertical) h(horizontal)
86 | this.dragTarget = ''; // 拖拽目标 wrap img
87 | this.status = ''; // close 时移除dom
88 | this.isPointerdown = false; // 按下标识
89 | this.isAnimating = true; // 是否正在执行动画
90 | this.isWrapAnimating = false; // 是否正在执行wrap切换动画,不响应键盘事件
91 | this.tapTimeout = null; // 单击延时器 250ms 判断双击
92 | this.pointerdownTime = null; // pointerdown time
93 | this.pointermoveTime = null; // 鼠标松开距离最后一次移动小于200ms执行惯性滑动
94 | this.pinchTime = null; // 距离上一次双指缩放的时间
95 | this.inertiaRafId = null; // requestAnimationFrame id 用于停止惯性滑动动画
96 | this.wrapRafId = null; // requestAnimationFrame id 用于停止wrap滑动动画
97 | }
98 | /**
99 | * 设置视口大小
100 | */
101 | NonameGallery.prototype.setWindowSize = function () {
102 | this.windowWidth = window.innerWidth;
103 | this.windowHeight = window.innerHeight;
104 | }
105 | /**
106 | * 设置previewList
107 | */
108 | NonameGallery.prototype.setPreviewList = function () {
109 | for (const img of this.options.list) {
110 | const rect = img.getBoundingClientRect();
111 | const result = this.getImgSize(img.naturalWidth, img.naturalHeight, this.windowWidth, this.windowHeight);
112 | const maxScale = Math.max(this.decimal(img.naturalWidth / result.width, 5), this.options.minScale);
113 | this.previewList.push({
114 | x: this.decimal((this.windowWidth - result.width) / 2, 2), // 预览图片左上角相对于视口的横坐标
115 | y: this.decimal((this.windowHeight - result.height) / 2, 2), // 预览图片左上角相对于视口的纵坐标
116 | width: this.decimal(result.width, 2), // 预览图片显示宽度
117 | height: this.decimal(result.height, 2), // 预览图片显示高度
118 | maxWidth: this.decimal(result.width * maxScale, 2), // 预览图片最大宽度
119 | maxHeight: this.decimal(result.height * maxScale, 2), // 预览图片最大高度
120 | maxScale: maxScale, // 预览图片最大缩放比例
121 | thumbnail: {
122 | x: this.decimal(rect.left, 2), // 缩略图左上角相对于视口的横坐标
123 | y: this.decimal(rect.top, 2), // 缩略图左上角相对于视口的纵坐标
124 | width: this.decimal(rect.width, 2), // 缩略图显示宽度
125 | height: this.decimal(rect.height, 2), // 缩略图显示高度
126 | scaleX: this.decimal(rect.width / result.width, 5), // 缩略图x轴比例
127 | scaleY: this.decimal(rect.height / result.height, 5) // 缩略图y轴比例
128 | }
129 | });
130 | }
131 | }
132 | /**
133 | * 设置当前图片数据
134 | * @param {number} x 横坐标
135 | * @param {number} y 纵坐标
136 | * @param {number} width 宽度
137 | * @param {number} height 高度
138 | * @param {number} scale 缩放值
139 | * @param {number} opacity 透明度
140 | * @param {string} status 状态 shrink verticalToClose inertia
141 | */
142 | NonameGallery.prototype.setCurrentImg = function (x, y, width, height, scale, opacity, status) {
143 | this.currentImg = {
144 | x: x,
145 | y: y,
146 | width: width,
147 | height: height,
148 | scale: scale,
149 | opacity: opacity,
150 | status: status
151 | };
152 | }
153 | /**
154 | * 设置wrap宽度和x轴偏移量
155 | */
156 | NonameGallery.prototype.setWrap = function () {
157 | this.wrapWidth = this.previewList.length * this.windowWidth;
158 | this.wrapX = this.index * this.windowWidth * -1;
159 | }
160 | /**
161 | * 渲染
162 | */
163 | NonameGallery.prototype.render = function () {
164 | // bg背景透明度过渡效果
165 | let cssText = 'opacity: 0;';
166 | if (this.options.useTransform) {
167 | cssText += 'transition: opacity ' + this.options.duration + 'ms ease-out;';
168 | }
169 | let html = ''
170 | + '
'
171 | + '
' + (this.options.index + 1) + ' / ' + this.options.list.length + '
'
172 | + '
';
173 | for (let i = 0, length = this.options.list.length; i < length; i++) {
174 | const item = this.previewList[i];
175 | if (this.options.useTransform) {
176 | cssText = 'width: ' + item.width + 'px; height: ' + item.height + 'px;';
177 | if (this.index === i) {
178 | cssText += 'transform: translate3d(' + item.thumbnail.x + 'px, ' + item.thumbnail.y + 'px, 0) scale(' + item.thumbnail.scaleX + ', ' + item.thumbnail.scaleY + ');';
179 | } else {
180 | cssText += 'transform: translate3d(' + item.x + 'px, ' + item.y + 'px, 0) scale(1);';
181 | }
182 | cssText += 'transition: transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out;';
183 | } else {
184 | if (this.index === i) {
185 | cssText = 'width: ' + item.thumbnail.width + 'px; height: ' + item.thumbnail.height + 'px;';
186 | cssText += 'transform: translate3d(' + item.thumbnail.x + 'px, ' + item.thumbnail.y + 'px, 0);';
187 | } else {
188 | cssText = 'width: ' + item.width + 'px; height: ' + item.height + 'px;';
189 | cssText += 'transform: translate3d(' + item.x + 'px, ' + item.y + 'px, 0);';
190 | }
191 | }
192 | if (this.index === i && this.options.fadeInOut) {
193 | cssText += 'opacity: 0;';
194 | }
195 | cssText += 'cursor: zoom-in;';
196 | html += '- '
197 | + '
'
198 | + ' ';
199 | }
200 | html += '
';
201 | document.body.insertAdjacentHTML('beforeend', html);
202 | }
203 | /**
204 | * 获取元素
205 | */
206 | NonameGallery.prototype.getElement = function () {
207 | this.container = document.querySelector('.noname-gallery-container');
208 | this.bg = document.querySelector('.noname-gallery-bg');
209 | this.counter = document.querySelector('.noname-gallery-counter');
210 | this.wrap = document.querySelector('.noname-gallery-wrap');
211 | this.imgList = document.querySelectorAll('.noname-gallery-img');
212 | for (let i = 0, length = this.imgList.length; i < length; i++) {
213 | this.previewList[i].element = this.imgList[i]; // HTMLImageElement
214 | }
215 | }
216 | /**
217 | * 打开画廊
218 | */
219 | NonameGallery.prototype.open = function () {
220 | const item = this.previewList[this.index];
221 | if (this.options.useTransform) {
222 | // 强制重绘,否则合并计算样式,导致无法触发过渡效果,或使用setTimeout,个人猜测最短时长等于,1000 / 60 = 16.66666 ≈ 17
223 | window.getComputedStyle(item.element).opacity;
224 | this.bg.style.opacity = '1';
225 | if (this.options.fadeInOut) {
226 | item.element.style.opacity = '1';
227 | }
228 | item.element.style.transform = 'translate3d(' + item.x + 'px,' + item.y + 'px, 0) scale(1)';
229 | } else {
230 | const obj = {
231 | bg: {
232 | opacity: { from: 0, to: 1 }
233 | },
234 | img: {
235 | width: { from: item.thumbnail.width, to: item.width },
236 | height: { from: item.thumbnail.height, to: item.height },
237 | x: { from: item.thumbnail.x, to: item.x },
238 | y: { from: item.thumbnail.y, to: item.y },
239 | index: this.index
240 | }
241 | }
242 | if (this.options.fadeInOut) {
243 | obj.img.opacity = { from: 0, to: 1 };
244 | }
245 | this.raf(obj);
246 | }
247 | // 设置当前图片数据
248 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
249 | }
250 | /**
251 | * 关闭画廊
252 | */
253 | NonameGallery.prototype.close = function () {
254 | this.isAnimating = true;
255 | this.status = 'close';
256 | const item = this.previewList[this.index];
257 | if (this.options.useTransform) {
258 | if (this.options.fadeInOut) {
259 | item.element.style.opacity = '0';
260 | }
261 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
262 | item.element.style.transform = 'translate3d(' + item.thumbnail.x + 'px, ' + item.thumbnail.y + 'px, 0) scale(' + item.thumbnail.scaleX + ', ' + item.thumbnail.scaleY + ')';
263 | this.bg.style.transition = 'opacity ' + this.options.duration + 'ms ease-out';
264 | this.bg.style.opacity = '0';
265 | } else {
266 | const obj = {
267 | bg: {
268 | opacity: { from: this.bgOpacity, to: 0 }
269 | },
270 | img: {
271 | width: { from: this.currentImg.width, to: item.thumbnail.width },
272 | height: { from: this.currentImg.height, to: item.thumbnail.height },
273 | x: { from: this.currentImg.x, to: item.thumbnail.x },
274 | y: { from: this.currentImg.y, to: item.thumbnail.y },
275 | index: this.index
276 | }
277 | }
278 | if (this.options.fadeInOut) {
279 | obj.img.opacity = { from: 1, to: 0 };
280 | }
281 | this.raf(obj);
282 | }
283 | }
284 | /**
285 | * 绑定事件
286 | */
287 | NonameGallery.prototype.bindEventListener = function () {
288 | this.handlePointerdown = this.handlePointerdown.bind(this);
289 | this.handlePointermove = this.handlePointermove.bind(this);
290 | this.handlePointerup = this.handlePointerup.bind(this);
291 | this.handlePointercancel = this.handlePointercancel.bind(this);
292 | this.handleResize = this.handleResize.bind(this);
293 | this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
294 | this.container.addEventListener('pointerdown', this.handlePointerdown);
295 | this.container.addEventListener('pointermove', this.handlePointermove);
296 | this.container.addEventListener('pointerup', this.handlePointerup);
297 | this.container.addEventListener('pointercancel', this.handlePointercancel);
298 | this.container.addEventListener('transitionend', this.handleTransitionEnd);
299 | window.addEventListener('resize', this.handleResize);
300 | window.addEventListener('orientationchange', this.handleResize);
301 | if (this.options.openKeyboard) {
302 | this.handleKeydown = this.handleKeydown.bind(this);
303 | window.addEventListener('keydown', this.handleKeydown);
304 | }
305 | }
306 | /**
307 | * 解绑事件
308 | */
309 | NonameGallery.prototype.unbindEventListener = function () {
310 | this.container.removeEventListener('pointerdown', this.handlePointerdown);
311 | this.container.removeEventListener('pointermove', this.handlePointermove);
312 | this.container.removeEventListener('pointerup', this.handlePointerup);
313 | this.container.removeEventListener('pointercancel', this.handlePointercancel);
314 | this.container.removeEventListener('transitionend', this.handleTransitionEnd);
315 | window.removeEventListener('resize', this.handleResize);
316 | window.removeEventListener('orientationchange', this.handleResize);
317 | if (this.options.openKeyboard) {
318 | window.removeEventListener('keydown', this.handleKeydown);
319 | }
320 | }
321 | /**
322 | * 处理pointerdown
323 | * @param {PointerEvent} e
324 | */
325 | NonameGallery.prototype.handlePointerdown = function (e) {
326 | // 非鼠标左键点击或正在执行开始动画,缩放动画,垂直滑动、双指缩小恢复动画,结束动画
327 | if (e.pointerType === 'mouse' && e.button !== 0 || this.isAnimating) {
328 | return;
329 | }
330 | this.pointers.push(e);
331 | this.point1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
332 | if (this.pointers.length === 1) {
333 | this.container.setPointerCapture(e.pointerId);
334 | this.isPointerdown = true;
335 | this.distance = { x: 0, y: 0 };
336 | this.lastDistance = { x: 0, y: 0 };
337 | this.pointerdownTime = Date.now();
338 | this.pinchTime = null;
339 | this.lastMove = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
340 | if (this.isWrapAnimating === false) {
341 | this.tapCount++;
342 | }
343 | // 双击两点距离不超过30
344 | if (this.tapCount > 1 && (Math.abs(this.point1.x - this.lastPoint1.x) > 30 || Math.abs(this.point1.y - this.lastPoint1.y) > 30)) {
345 | this.tapCount = 1;
346 | }
347 | clearTimeout(this.tapTimeout);
348 | window.cancelAnimationFrame(this.inertiaRafId);
349 | window.cancelAnimationFrame(this.wrapRafId);
350 | } else if (this.pointers.length === 2) {
351 | this.tapCount = 0;
352 | this.point2 = { x: this.pointers[1].clientX, y: this.pointers[1].clientY };
353 | this.lastCenter = this.getCenter(this.point1, this.point2);
354 | this.lastDistance = { x: this.distance.x, y: this.distance.y };
355 | this.lastPoint2 = { x: this.pointers[1].clientX, y: this.pointers[1].clientY };
356 | if (this.dragTarget === '') {
357 | this.dragTarget = 'img';
358 | }
359 | }
360 | this.lastPoint1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
361 | }
362 | /**
363 | * 处理pointermove
364 | * @param {PointerEvent} e
365 | */
366 | NonameGallery.prototype.handlePointermove = function (e) {
367 | if (!this.isPointerdown) {
368 | return;
369 | }
370 | this.handlePointers(e, 'update');
371 | const current1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
372 | if (this.pointers.length === 1) {
373 | this.diff = { x: current1.x - this.lastMove.x, y: current1.y - this.lastMove.y };
374 | this.distance = { x: current1.x - this.point1.x + this.lastDistance.x, y: current1.y - this.point1.y + this.lastDistance.y };
375 | this.lastMove = { x: current1.x, y: current1.y };
376 | this.pointermoveTime = Date.now();
377 | if (Math.abs(this.distance.x) > 10 || Math.abs(this.distance.y) > 10) {
378 | this.tapCount = 0;
379 | // 偏移量大于10才判断dragDirection和dragTarget
380 | if (this.dragDirection === '' && this.dragTarget === '') {
381 | this.getDragDirection();
382 | this.getDragTarget();
383 | }
384 | }
385 | if (this.dragTarget === 'wrap') {
386 | this.handleWrapPointermove();
387 | } else if (this.dragTarget === 'img') {
388 | this.handleImgPointermove();
389 | }
390 | } else if (this.pointers.length === 2) {
391 | const current2 = { x: this.pointers[1].clientX, y: this.pointers[1].clientY };
392 | if (this.dragTarget === 'img' && this.currentImg.status !== 'verticalToClose') {
393 | this.handlePinch(current1, current2);
394 | }
395 | this.lastPoint1 = { x: current1.x, y: current1.y };
396 | this.lastPoint2 = { x: current2.x, y: current2.y };
397 | }
398 | // 阻止默认事件,例如拖拽图片
399 | e.preventDefault();
400 | }
401 | /**
402 | * 处理pointerup
403 | * @param {PointerEvent} e
404 | */
405 | NonameGallery.prototype.handlePointerup = function (e) {
406 | if (!this.isPointerdown) {
407 | return;
408 | }
409 | this.handlePointers(e, 'delete');
410 | if (this.pointers.length === 0) {
411 | this.isPointerdown = false;
412 | if (this.tapCount === 0) {
413 | if (this.dragTarget === 'wrap') {
414 | this.handleWrapPointerup();
415 | } else if (this.dragTarget === 'img') {
416 | this.handleImgPointerup();
417 | }
418 | } else if (this.tapCount === 1) {
419 | if (e.pointerType === 'mouse') {
420 | // 由于调用过setPointerCapture方法,导致无法使用e.target来判断触发事件的元素,所以只能根据点击位置来判断
421 | if (e.clientX >= this.currentImg.x && e.clientX <= this.currentImg.x + this.currentImg.width &&
422 | e.clientY >= this.currentImg.y && e.clientY <= this.currentImg.y + this.currentImg.height) {
423 | this.handleZoom({ x: e.clientX, y: e.clientY });
424 | } else {
425 | this.close();
426 | }
427 | } else {
428 | // 触发移动端长按保存图片后不关闭画廊
429 | if (Date.now() - this.pointerdownTime < 500) {
430 | this.tapTimeout = setTimeout(() => {
431 | this.close();
432 | }, 250);
433 | } else {
434 | this.tapCount = 0;
435 | }
436 | }
437 | } else if (this.tapCount > 1) {
438 | this.handleZoom({ x: e.clientX, y: e.clientY });
439 | }
440 | } else if (this.pointers.length === 1) {
441 | this.point1 = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
442 | this.lastMove = { x: this.pointers[0].clientX, y: this.pointers[0].clientY };
443 | }
444 | }
445 | /**
446 | * 处理pointercancel
447 | * @param {PointerEvent} e
448 | */
449 | NonameGallery.prototype.handlePointercancel = function (e) {
450 | this.tapCount = 0;
451 | this.isPointerdown = false;
452 | this.pointers.length = 0;
453 | if (this.isWrapAnimating) {
454 | // 长按图片呼出菜单后继续执行wrap动画
455 | this.handleWrapPointerup();
456 | }
457 | }
458 | /**
459 | * 更新或删除指针
460 | * @param {PointerEvent} e
461 | * @param {string} type update delete
462 | */
463 | NonameGallery.prototype.handlePointers = function (e, type) {
464 | for (let i = 0; i < this.pointers.length; i++) {
465 | if (this.pointers[i].pointerId === e.pointerId) {
466 | if (type === 'update') {
467 | this.pointers[i] = e;
468 | } else if (type === 'delete') {
469 | this.pointers.splice(i, 1);
470 | }
471 | }
472 | }
473 | }
474 | /**
475 | * 处理视口宽高
476 | */
477 | NonameGallery.prototype.handleResize = function () {
478 | // 设置视口大小
479 | this.setWindowSize();
480 | // 设置previewList
481 | this.previewList.length = 0;
482 | this.setPreviewList();
483 | // 设置当前图片数据
484 | const item = this.previewList[this.index];
485 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
486 | // 设置wrap宽度和x轴偏移量
487 | this.setWrap();
488 | this.wrap.style.width = this.wrapWidth + 'px';
489 | this.wrap.style.transform = 'translate3d(' + this.wrapX + 'px, 0, 0)';
490 | // 设置图片数据
491 | for (let i = 0, length = this.imgList.length; i < length; i++) {
492 | const item = this.previewList[i];
493 | item.element = this.imgList[i];
494 | item.element.style.width = item.width + 'px';
495 | item.element.style.height = item.height + 'px';
496 | if (this.options.useTransform) {
497 | item.element.style.transition = 'none';
498 | item.element.style.transform = 'translate3d(' + item.x + 'px, ' + item.y + 'px, 0) scale(1)';
499 | } else {
500 | item.element.style.transform = 'translate3d(' + item.x + 'px, ' + item.y + 'px, 0)';
501 | }
502 | item.element.style.cursor = 'zoom-in';
503 | }
504 | if (this.options.useTransform) {
505 | this.bg.style.transition = 'none';
506 | }
507 | this.bgOpacity = 1;
508 | this.bg.style.opacity = this.bgOpacity;
509 | this.tapCount = 0;
510 | }
511 | /**
512 | * 处理keydown
513 | * @param {KeyboardEvent} e
514 | */
515 | NonameGallery.prototype.handleKeydown = function (e) {
516 | if (this.isAnimating || this.isWrapAnimating) {
517 | return;
518 | }
519 | const lastIndex = this.index;
520 | if (e.key === 'Escape') {
521 | this.close();
522 | } else if (['ArrowLeft', 'ArrowUp'].includes(e.key) && lastIndex > 0) {
523 | this.index--;
524 | } else if (['ArrowRight', 'ArrowDown'].includes(e.key) && lastIndex < this.previewList.length - 1) {
525 | this.index++;
526 | }
527 | window.cancelAnimationFrame(this.inertiaRafId);
528 | this.handleWrapSwipe();
529 | this.handleLastImg(lastIndex);
530 | }
531 | /**
532 | * 处理缩放
533 | */
534 | NonameGallery.prototype.handleZoom = function (point) {
535 | this.isAnimating = true;
536 | // 缩放时重置点击计数器
537 | this.tapCount = 0;
538 | const item = this.previewList[this.index];
539 | if (this.currentImg.scale > 1) {
540 | if (this.options.useTransform) {
541 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
542 | item.element.style.transform = 'translate3d(' + item.x + 'px,' + item.y + 'px, 0) scale(1)';
543 | } else {
544 | const obj = {
545 | img: {
546 | width: { from: this.currentImg.width, to: item.width },
547 | height: { from: this.currentImg.height, to: item.height },
548 | x: { from: this.currentImg.x, to: item.x },
549 | y: { from: this.currentImg.y, to: item.y },
550 | index: this.index
551 | }
552 | }
553 | this.raf(obj);
554 | }
555 | item.element.style.cursor = 'zoom-in';
556 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
557 | } else {
558 | const halfWindowWidth = this.windowWidth / 2;
559 | const halfWindowHeight = this.windowHeight / 2;
560 | const left = this.decimal((point.x - item.x) * item.maxScale, 2);
561 | const top = this.decimal((point.y - item.y) * item.maxScale, 2);
562 | let x, y;
563 | if (item.maxWidth > this.windowWidth) {
564 | if (this.options.zoomToScreenCenter) {
565 | x = halfWindowWidth - left;
566 | } else {
567 | x = point.x - left;
568 | }
569 | if (x > 0) {
570 | x = 0;
571 | } else if (x < this.windowWidth - item.maxWidth) {
572 | x = this.windowWidth - item.maxWidth;
573 | }
574 | } else {
575 | x = (this.windowWidth - item.maxWidth) / 2;
576 | }
577 | x = this.decimal(x, 2);
578 | if (item.maxHeight > this.windowHeight) {
579 | if (this.options.zoomToScreenCenter) {
580 | y = halfWindowHeight - top;
581 | } else {
582 | y = point.y - top;
583 | }
584 | if (y > 0) {
585 | y = 0;
586 | } else if (y < this.windowHeight - item.maxHeight) {
587 | y = this.windowHeight - item.maxHeight;
588 | }
589 | } else {
590 | y = (this.windowHeight - item.maxHeight) / 2;
591 | }
592 | y = this.decimal(y, 2);
593 | if (this.options.useTransform) {
594 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
595 | item.element.style.transform = 'translate3d(' + x + 'px,' + y + 'px, 0) scale(' + item.maxScale + ')';
596 | } else {
597 | const obj = {
598 | img: {
599 | width: { from: item.width, to: item.maxWidth },
600 | height: { from: item.height, to: item.maxHeight },
601 | x: { from: item.x, to: x },
602 | y: { from: item.y, to: y },
603 | index: this.index
604 | }
605 | }
606 | this.raf(obj);
607 | }
608 | item.element.style.cursor = 'zoom-out';
609 | this.setCurrentImg(x, y, item.maxWidth, item.maxHeight, item.maxScale, 1, '');
610 | }
611 | }
612 | /**
613 | * 处理双指缩放
614 | * @param {object} a 第一个点的位置
615 | * @param {object} b 第二个点的位置
616 | */
617 | NonameGallery.prototype.handlePinch = function (a, b) {
618 | const MIN_SCALE = 0.7;
619 | this.pinchTime = Date.now();
620 | let ratio = this.getDistance(a, b) / this.getDistance(this.lastPoint1, this.lastPoint2);
621 | let scale = this.decimal(this.currentImg.scale * ratio, 5);
622 | const item = this.previewList[this.index];
623 | if (scale > item.maxScale) {
624 | ratio = item.maxScale / this.currentImg.scale;
625 | this.currentImg.scale = item.maxScale;
626 | this.currentImg.width = item.maxWidth;
627 | this.currentImg.height = item.maxHeight;
628 | } else if (scale < MIN_SCALE) {
629 | ratio = MIN_SCALE / this.currentImg.scale;
630 | this.currentImg.scale = MIN_SCALE;
631 | this.currentImg.width = this.decimal(item.width * MIN_SCALE, 2);
632 | this.currentImg.height = this.decimal(item.height * MIN_SCALE, 2);
633 | } else {
634 | this.currentImg.scale = scale;
635 | this.currentImg.width = this.decimal(this.currentImg.width * ratio, 2);
636 | this.currentImg.height = this.decimal(this.currentImg.height * ratio, 2);
637 | }
638 | this.currentImg.status = this.currentImg.scale < 1 ? 'shrink' : '';
639 | const center = this.getCenter(a, b);
640 | // 计算偏移量
641 | this.currentImg.x -= (ratio - 1) * (center.x - this.currentImg.x) - center.x + this.lastCenter.x;
642 | this.currentImg.y -= (ratio - 1) * (center.y - this.currentImg.y) - center.y + this.lastCenter.y;
643 | this.lastCenter = { x: center.x, y: center.y };
644 | this.handleBoundary();
645 | if (this.options.useTransform) {
646 | item.element.style.transition = 'none';
647 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0) scale(' + this.currentImg.scale + ')';
648 | } else {
649 | item.element.style.width = this.currentImg.width + 'px';
650 | item.element.style.height = this.currentImg.height + 'px';
651 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0)';
652 | }
653 | }
654 | /**
655 | * 处理边界
656 | */
657 | NonameGallery.prototype.handleBoundary = function () {
658 | if (this.currentImg.width > this.windowWidth) {
659 | if (this.currentImg.x > 0) {
660 | this.currentImg.x = 0
661 | } else if (this.currentImg.x < this.windowWidth - this.currentImg.width) {
662 | this.currentImg.x = this.windowWidth - this.currentImg.width;
663 | }
664 | } else {
665 | this.currentImg.x = (this.windowWidth - this.currentImg.width) / 2;
666 | }
667 | if (this.currentImg.height > this.windowHeight) {
668 | if (this.currentImg.y > 0) {
669 | this.currentImg.y = 0
670 | } else if (this.currentImg.y < this.windowHeight - this.currentImg.height) {
671 | this.currentImg.y = this.windowHeight - this.currentImg.height;
672 | }
673 | } else {
674 | this.currentImg.y = (this.windowHeight - this.currentImg.height) / 2;
675 | }
676 | }
677 | /**
678 | * 获取图片缩放尺寸
679 | * @param {number} naturalWidth 图片自然宽度
680 | * @param {number} naturalHeight 图片自然高度
681 | * @param {number} maxWidth 图片显示最大宽度
682 | * @param {number} maxHeight 图片显示最大高度
683 | * @returns
684 | */
685 | NonameGallery.prototype.getImgSize = function (naturalWidth, naturalHeight, maxWidth, maxHeight) {
686 | const imgRatio = naturalWidth / naturalHeight;
687 | const maxRatio = maxWidth / maxHeight;
688 | let width, height;
689 | // 如果图片自然宽高比例 >= 显示宽高比例
690 | if (imgRatio >= maxRatio) {
691 | if (naturalWidth > maxWidth) {
692 | width = maxWidth;
693 | height = maxWidth / naturalWidth * naturalHeight;
694 | } else {
695 | width = naturalWidth;
696 | height = naturalHeight;
697 | }
698 | } else {
699 | if (naturalHeight > maxHeight) {
700 | width = maxHeight / naturalHeight * naturalWidth;
701 | height = maxHeight;
702 | } else {
703 | width = naturalWidth;
704 | height = naturalHeight;
705 | }
706 | }
707 | return { width: width, height: height }
708 | }
709 | /**
710 | * 获取拖拽方向
711 | */
712 | NonameGallery.prototype.getDragDirection = function () {
713 | if (Math.abs(this.distance.x) > Math.abs(this.distance.y)) {
714 | this.dragDirection = 'h';
715 | } else {
716 | this.dragDirection = 'v';
717 | }
718 | }
719 | /**
720 | * 获取拖拽目标
721 | */
722 | NonameGallery.prototype.getDragTarget = function () {
723 | let flag1 = false, flag2 = false;
724 | if (this.currentImg.width > this.windowWidth) {
725 | if (
726 | (this.diff.x > 0 && this.currentImg.x === 0) ||
727 | (this.diff.x < 0 && this.currentImg.x === this.windowWidth - this.currentImg.width)
728 | ) {
729 | flag1 = true;
730 | }
731 | } else {
732 | if (this.currentImg.width >= this.previewList[this.index].width) {
733 | flag2 = true;
734 | }
735 | }
736 | if (this.dragDirection === 'h' && (flag1 || flag2)) {
737 | this.dragTarget = 'wrap';
738 | } else {
739 | this.dragTarget = 'img';
740 | }
741 | }
742 | /**
743 | * 获取两点距离
744 | * @param {object} a 第一个点的位置
745 | * @param {object} b 第二个点的位置
746 | * @returns
747 | */
748 | NonameGallery.prototype.getDistance = function (a, b) {
749 | const x = a.x - b.x;
750 | const y = a.y - b.y;
751 | return Math.hypot(x, y); // Math.sqrt(x * x + y * y);
752 | }
753 | /**
754 | * 处理wrap移动
755 | */
756 | NonameGallery.prototype.handleWrapPointermove = function () {
757 | if (this.wrapX > 0 || this.wrapX < (this.previewList.length - 1) * this.windowWidth * - 1) {
758 | this.wrapX += this.diff.x * 0.3;
759 | } else {
760 | this.wrapX += this.diff.x;
761 | const LEFT_X = (this.index - 1) * this.windowWidth * -1;
762 | const RIGHT_X = (this.index + 1) * this.windowWidth * -1;
763 | if (this.wrapX > LEFT_X) {
764 | this.wrapX = LEFT_X;
765 | } else if (this.wrapX < RIGHT_X) {
766 | this.wrapX = RIGHT_X;
767 | }
768 | }
769 | this.wrap.style.transform = 'translate3d(' + this.wrapX + 'px, 0, 0)';
770 | }
771 | /**
772 | * 处理img移动
773 | */
774 | NonameGallery.prototype.handleImgPointermove = function () {
775 | const item = this.previewList[this.index];
776 | // 如果图片当前宽高大于视口宽高,拖拽查看图片,可惯性滚动
777 | if (this.currentImg.width > this.windowWidth || this.currentImg.height > this.windowHeight) {
778 | this.currentImg.x += this.diff.x;
779 | this.currentImg.y += this.diff.y;
780 | this.handleBoundary();
781 | this.currentImg.status = 'inertia';
782 | if (this.options.useTransform) {
783 | item.element.style.transition = 'none';
784 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0) scale(' + this.currentImg.scale + ')';
785 | } else {
786 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px, 0)';
787 | }
788 | } else {
789 | // 如果垂直拖拽图片且图片未被放大(某些图片尺寸放到最大也没有超出视口宽高)
790 | if (this.dragDirection === 'v' && this.currentImg.width <= item.width && this.currentImg.height <= item.height) {
791 | this.currentImg.status = 'verticalToClose';
792 | this.bgOpacity = this.decimal(1 - Math.abs(this.distance.y) / (this.windowHeight / 1.2), 5);
793 | if (this.bgOpacity < 0) {
794 | this.bgOpacity = 0;
795 | }
796 | if (this.options.verticalZoom) {
797 | this.currentImg.scale = this.bgOpacity;
798 | this.currentImg.width = this.decimal(item.width * this.currentImg.scale, 2);
799 | this.currentImg.height = this.decimal(item.height * this.currentImg.scale, 2);
800 | this.currentImg.x = item.x + this.distance.x + (item.width - this.currentImg.width) / 2;
801 | this.currentImg.y = item.y + this.distance.y + (item.height - this.currentImg.height) / 2;
802 | } else {
803 | this.currentImg.x = item.x;
804 | this.currentImg.y = item.y + this.distance.y;
805 | this.currentImg.scale = 1;
806 | }
807 | this.bg.style.opacity = this.bgOpacity;
808 | if (this.options.useTransform) {
809 | this.bg.style.transition = 'none';
810 | item.element.style.transition = 'none';
811 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px , 0) scale(' + this.currentImg.scale + ')';
812 | } else {
813 | item.element.style.width = this.currentImg.width + 'px';
814 | item.element.style.height = this.currentImg.height + 'px';
815 | item.element.style.transform = 'translate3d(' + this.currentImg.x + 'px, ' + this.currentImg.y + 'px , 0)';
816 | }
817 | }
818 | }
819 | }
820 | /**
821 | * 处理wrap移动结束
822 | */
823 | NonameGallery.prototype.handleWrapPointerup = function () {
824 | // 拖拽距离超过屏幕宽度10%即可切换下一张图片
825 | const MIN_SWIPE_DISTANCE = Math.round(this.windowWidth * 0.1);
826 | const lastIndex = this.index;
827 | if (Math.abs(this.distance.x) > MIN_SWIPE_DISTANCE) {
828 | if (this.distance.x > 0 && lastIndex > 0) {
829 | this.index--;
830 | } else if (this.distance.x < 0 && lastIndex < this.previewList.length - 1) {
831 | this.index++;
832 | }
833 | }
834 | this.handleWrapSwipe();
835 | this.handleLastImg(lastIndex);
836 | }
837 | /**
838 | * 处理img移动结束
839 | */
840 | NonameGallery.prototype.handleImgPointerup = function () {
841 | // 垂直滑动距离超过屏幕高度10%即可关闭画廊
842 | const MIN_CLOSE_DISTANCE = Math.round(this.windowHeight * 0.1);
843 | const item = this.previewList[this.index];
844 | const now = Date.now();
845 | if (this.currentImg.status === 'inertia' && now - this.pointermoveTime < 200 && now - this.pinchTime > 1000) {
846 | this.handleInertia();
847 | } else if (this.currentImg.status === 'verticalToClose' && Math.abs(this.distance.y) >= MIN_CLOSE_DISTANCE) {
848 | this.close();
849 | } else if (this.currentImg.status === 'shrink' || (this.currentImg.status == 'verticalToClose' && Math.abs(this.distance.y) < MIN_CLOSE_DISTANCE)) {
850 | this.isAnimating = true;
851 | if (this.options.useTransform) {
852 | this.bg.style.opacity = '1';
853 | item.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
854 | item.element.style.transform = 'translate3d(' + item.x + 'px, ' + item.y + 'px, 0) scale(1)';
855 | } else {
856 | const obj = {
857 | bg: {
858 | opacity: { from: this.bgOpacity, to: 1 }
859 | },
860 | img: {
861 | width: { from: this.currentImg.width, to: item.width },
862 | height: { from: this.currentImg.height, to: item.height },
863 | x: { from: this.currentImg.x, to: item.x },
864 | y: { from: this.currentImg.y, to: item.y },
865 | index: this.index
866 | }
867 | }
868 | this.raf(obj);
869 | }
870 | this.bgOpacity = 1;
871 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
872 | }
873 | if (this.isAnimating === false) {
874 | this.dragTarget = '';
875 | this.dragDirection = '';
876 | }
877 | }
878 | /**
879 | * 处理惯性滚动
880 | */
881 | NonameGallery.prototype.handleInertia = function () {
882 | const item = this.previewList[this.index];
883 | const speed = { x: this.diff.x, y: this.diff.y };
884 | const self = this;
885 | function step(timestamp) {
886 | speed.x *= 0.95;
887 | speed.y *= 0.95;
888 | self.currentImg.x = self.decimal(self.currentImg.x + speed.x, 2);
889 | self.currentImg.y = self.decimal(self.currentImg.y + speed.y, 2);
890 | self.handleBoundary();
891 | if (self.options.useTransform) {
892 | item.element.style.transform = 'translate3d(' + self.currentImg.x + 'px, ' + self.currentImg.y + 'px, 0) scale(' + self.currentImg.scale + ')';
893 | } else {
894 | item.element.style.transform = 'translate3d(' + self.currentImg.x + 'px, ' + self.currentImg.y + 'px, 0)';
895 | }
896 | if (Math.abs(speed.x) > 1 || Math.abs(speed.y) > 1) {
897 | self.inertiaRafId = window.requestAnimationFrame(step);
898 | }
899 | }
900 | this.inertiaRafId = window.requestAnimationFrame(step);
901 | }
902 | /**
903 | * 处理wrap滑动
904 | */
905 | NonameGallery.prototype.handleWrapSwipe = function () {
906 | this.isWrapAnimating = true;
907 | const obj = {
908 | wrap: {
909 | x: { from: this.wrapX, to: this.windowWidth * this.index * -1 }
910 | }
911 | }
912 | this.wrapRaf(obj);
913 | this.counter.innerHTML = (this.index + 1) + ' / ' + this.previewList.length;
914 | }
915 | /**
916 | * 处理上一张图片
917 | * @param {number} lastIndex
918 | */
919 | NonameGallery.prototype.handleLastImg = function (lastIndex) {
920 | // 根据索引判断是否切换图片
921 | if (this.index !== lastIndex) {
922 | // 如果上一张图片放大过,则恢复
923 | if (this.currentImg.scale > 1) {
924 | const lastItem = this.previewList[lastIndex];
925 | if (this.options.useTransform) {
926 | lastItem.element.style.transition = 'transform ' + this.options.duration + 'ms ease-out, opacity ' + this.options.duration + 'ms ease-out';
927 | lastItem.element.style.transform = 'translate3d(' + lastItem.x + 'px, ' + lastItem.y + 'px, 0) scale(1)';
928 | } else {
929 | const obj = {
930 | img: {
931 | width: { from: this.currentImg.width, to: lastItem.width },
932 | height: { from: this.currentImg.height, to: lastItem.height },
933 | x: { from: this.currentImg.x, to: lastItem.x },
934 | y: { from: this.currentImg.y, to: lastItem.y },
935 | index: lastIndex
936 | }
937 | }
938 | this.raf(obj);
939 | }
940 | lastItem.element.style.cursor = 'zoom-in';
941 | }
942 | // 设置当前图片数据
943 | const item = this.previewList[this.index];
944 | this.setCurrentImg(item.x, item.y, item.width, item.height, 1, 1, '');
945 | }
946 | }
947 | /**
948 | * 过渡结束回调
949 | * @param {TransitionEvent } e
950 | */
951 | NonameGallery.prototype.handleTransitionEnd = function (e) {
952 | // 过滤掉bg transitionend
953 | if (e.target.tagName === 'IMG') {
954 | // wrap滑动,上一张图片恢复动画完成后,不清除dragTarget,因为wrap动画可打断
955 | if (e.target === this.previewList[this.index].element) {
956 | this.isAnimating = false;
957 | this.dragTarget = '';
958 | this.dragDirection = '';
959 | }
960 | if (this.status === 'close') {
961 | // 解绑事件
962 | this.unbindEventListener();
963 | this.container.remove();
964 | }
965 | }
966 | }
967 | /**
968 | * 保留n位小数
969 | * @param {number} num 数字
970 | * @param {number} n n位小数
971 | * @returns
972 | */
973 | NonameGallery.prototype.decimal = function (num, n) {
974 | const x = Math.pow(10, n);
975 | return Math.round(num * x) / x;
976 | }
977 | /**
978 | * 获取中心点
979 | * @param {object} a 第一个点的位置
980 | * @param {object} b 第二个点的位置
981 | * @returns
982 | */
983 | NonameGallery.prototype.getCenter = function (a, b) {
984 | const x = (a.x + b.x) / 2;
985 | const y = (a.y + b.y) / 2;
986 | return { x: x, y: y };
987 | }
988 | /**
989 | * 曲线函数
990 | * @param {number} from 开始位置
991 | * @param {number} to 结束位置
992 | * @param {number} time 动画已执行的时间
993 | * @param {number} duration 动画时长
994 | * @returns
995 | */
996 | NonameGallery.prototype.easeOut = function (from, to, time, duration) {
997 | const change = to - from;
998 | const t = time / duration;
999 | return -change * t * (t - 2) + from;
1000 | }
1001 | /**
1002 | * 开始、结束、缩放、恢复(例如下滑关闭未达到临界值)动画函数
1003 | * @param {object} obj 属性
1004 | */
1005 | NonameGallery.prototype.raf = function (obj) {
1006 | const self = this;
1007 | let start;
1008 | let count = 0;
1009 | const duration = this.options.duration;
1010 | const item = this.previewList[obj.img.index];
1011 | function step(timestamp) {
1012 | if (start === undefined) {
1013 | start = timestamp;
1014 | }
1015 | let time = timestamp - start;
1016 | if (time > duration) {
1017 | time = duration;
1018 | count++;
1019 | }
1020 | if (obj.bg) {
1021 | const bgOpacity = self.decimal(self.easeOut(obj.bg.opacity.from, obj.bg.opacity.to, time, duration), 5);
1022 | self.bg.style.opacity = bgOpacity;
1023 | }
1024 | if (obj.img.opacity) {
1025 | const opacity = self.decimal(self.easeOut(obj.img.opacity.from, obj.img.opacity.to, time, duration), 5);
1026 | item.element.style.opacity = opacity;
1027 | }
1028 | const width = self.decimal(self.easeOut(obj.img.width.from, obj.img.width.to, time, duration), 2);
1029 | const height = self.decimal(self.easeOut(obj.img.height.from, obj.img.height.to, time, duration), 2);
1030 | const x = self.decimal(self.easeOut(obj.img.x.from, obj.img.x.to, time, duration), 2);
1031 | const y = self.decimal(self.easeOut(obj.img.y.from, obj.img.y.to, time, duration), 2);
1032 | item.element.style.width = width + 'px';
1033 | item.element.style.height = height + 'px';
1034 | item.element.style.transform = 'translate3d(' + x + 'px, ' + y + 'px, 0)';
1035 | if (count <= 1) {
1036 | window.requestAnimationFrame(step);
1037 | } else {
1038 | if (obj.img.index === self.index) {
1039 | self.isAnimating = false;
1040 | self.dragTarget = '';
1041 | self.dragDirection = '';
1042 | }
1043 | if (self.status === 'close') {
1044 | self.unbindEventListener();
1045 | self.container.remove();
1046 | }
1047 | }
1048 | }
1049 | window.requestAnimationFrame(step);
1050 | }
1051 | /**
1052 | * 动画函数
1053 | * @param {object} obj
1054 | */
1055 | NonameGallery.prototype.wrapRaf = function (obj) {
1056 | const self = this;
1057 | let start;
1058 | let count = 0;
1059 | const duration = this.options.duration;
1060 | function step(timestamp) {
1061 | if (start === undefined) {
1062 | start = timestamp;
1063 | }
1064 | let time = timestamp - start;
1065 | if (time > duration) {
1066 | time = duration;
1067 | count++;
1068 | }
1069 | self.wrapX = self.decimal(self.easeOut(obj.wrap.x.from, obj.wrap.x.to, time, duration), 2);
1070 | self.wrap.style.transform = 'translate3d(' + self.wrapX + 'px, 0, 0)';
1071 | if (count <= 1) {
1072 | self.wrapRafId = window.requestAnimationFrame(step);
1073 | } else {
1074 | self.isWrapAnimating = false;
1075 | self.dragTarget = '';
1076 | self.dragDirection = '';
1077 | }
1078 | }
1079 | this.wrapRafId = window.requestAnimationFrame(step);
1080 | }
1081 | if (typeof define === 'function' && define.amd) {
1082 | define(function () { return NonameGallery; });
1083 | } else if (typeof module === 'object' && typeof exports === 'object') {
1084 | module.exports = NonameGallery;
1085 | } else {
1086 | window.NonameGallery = NonameGallery;
1087 | }
1088 | })();
--------------------------------------------------------------------------------