24
24
<img
25
25
:class =" {
26
26
dragging,
27
- loaded,
28
- zoomed: zoomRatio !== 1
27
+ 'animate-transition': animateTransition,
29
28
}"
30
29
:src =" data"
31
30
:style =" {
32
- marginTop: shiftY + 'px',
33
- marginLeft: shiftX + 'px',
34
- maxHeight: zoomRatio * 100 + '%',
35
- maxWidth: zoomRatio * 100 + '%',
31
+ transform: 'translate(' + posX + 'px, ' + posY + 'px) scale(' + zoomRatio + ', ' + zoomRatio + ')',
36
32
}"
37
33
@load =" updateImgSize"
38
- @wheel =" updateZoom"
34
+ @wheel.prevent.stop =" updateZoom"
39
35
@dblclick.prevent =" onDblclick"
40
36
@mousedown.prevent =" dragStart" >
41
37
</template >
@@ -60,20 +56,12 @@ export default {
60
56
data () {
61
57
return {
62
58
dragging: false ,
63
- shiftX: 0 ,
64
- shiftY: 0 ,
59
+ animateTransition: false ,
65
60
zoomRatio: 1 ,
61
+ posX: 0 ,
62
+ posY: 0 ,
66
63
}
67
64
},
68
- computed: {
69
- zoomHeight () {
70
- return Math .round (this .height * this .zoomRatio )
71
- },
72
- zoomWidth () {
73
- return Math .round (this .width * this .zoomRatio )
74
- },
75
- },
76
-
77
65
asyncComputed: {
78
66
data () {
79
67
switch (this .mime ) {
@@ -121,47 +109,46 @@ export default {
121
109
122
110
/**
123
111
* Handle zooming
112
+ * Code based on https://stackoverflow.com/a/46833254/15603854
124
113
*
125
114
* @param {Event} event the scroll event
126
115
* @returns {null}
127
116
*/
128
117
updateZoom (event ) {
129
- event .stopPropagation ()
130
- event .preventDefault ()
131
-
132
- // scrolling position relative to the image
133
- const scrollX = event .clientX - this .$el .x - (this .width * this .zoomRatio / 2 )
134
- const scrollY = event .clientY - this .$el .y - (this .height * this .zoomRatio / 2 )
135
- const scrollPercX = Math .round (scrollX / (this .width * this .zoomRatio ) * 100 ) / 100
136
- const scrollPercY = Math .round (scrollY / (this .height * this .zoomRatio ) * 100 ) / 100
137
118
const isZoomIn = event .deltaY < 0
138
-
139
- const newZoomRatio = isZoomIn
140
- ? Math .min (this .zoomRatio + 0.1 , 5 ) // prevent too big zoom
141
- : Math .max (this .zoomRatio - 0.1 , 1 ) // prevent too small zoom
142
-
119
+ const delta = isZoomIn ? 0.1 : - 0.1
143
120
// do not continue, img is back to its original state
144
- if (newZoomRatio === 1 ) {
121
+ if (this . zoomRatio + delta === 1 ) {
145
122
return this .resetZoom ()
146
123
}
147
124
148
- // calc how much the img grow from its current size
149
- // and adjust the margin accordingly
150
- const growX = this .width * newZoomRatio - this .width * this .zoomRatio
151
- const growY = this .height * newZoomRatio - this .height * this .zoomRatio
152
-
153
- // compensate for existing margins
154
125
this .disableSwipe ()
155
- this .shiftX = this .shiftX + Math .round (- scrollPercX * growX)
156
- this .shiftY = this .shiftY + Math .round (- scrollPercY * growY)
157
- this .zoomRatio = newZoomRatio
126
+
127
+ // scrolling position in the page
128
+ const scrollX = event .clientX - this .$el .x
129
+ const scrollY = event .clientY - this .$el .y
130
+ // scrolling position relative to the image
131
+ const targetX = (scrollX - this .posX ) / this .zoomRatio
132
+ const targetY = (scrollY - this .posY ) / this .zoomRatio
133
+
134
+ // apply zoom
135
+ this .zoomRatio += delta
136
+ this .zoomRatio = Math .max (1 , Math .min (5 , this .zoomRatio ))
137
+ // bounds checking: ^-smallest ^-largest zoom ratio
138
+
139
+ // calculate x and y based on zoom
140
+ this .posX = - targetX * this .zoomRatio + scrollX
141
+ this .posY = - targetY * this .zoomRatio + scrollY
158
142
},
159
143
160
144
resetZoom () {
145
+ this .animateTransition = true
161
146
this .enableSwipe ()
162
147
this .zoomRatio = 1
163
- this .shiftX = 0
164
- this .shiftY = 0
148
+ this .posX = 0
149
+ this .posY = 0
150
+ // Animation is 100ms so give it double the time to finish
151
+ setTimeout (() => { this .animateTransition = false }, 200 )
165
152
},
166
153
167
154
/**
@@ -170,10 +157,8 @@ export default {
170
157
* @param {Event} event the event
171
158
*/
172
159
dragStart (event ) {
173
- const { pageX , pageY } = event
174
-
175
- this .dragX = pageX
176
- this .dragY = pageY
160
+ this .dragX = event .clientX
161
+ this .dragY = event .clientY
177
162
this .dragging = true
178
163
this .$el .onmouseup = this .dragEnd
179
164
this .$el .onmousemove = this .dragHandler
@@ -186,72 +171,56 @@ export default {
186
171
this .$el .onmousemove = null
187
172
},
188
173
dragHandler (event ) {
189
- event .preventDefault ()
190
- const { pageX , pageY } = event
174
+ const { clientX , clientY } = event
191
175
192
- if (this .dragging && this .zoomRatio > 1 && pageX > 0 && pageY > 0 ) {
193
- const moveX = this .shiftX + (pageX - this .dragX )
194
- const moveY = this .shiftY + (pageY - this .dragY )
195
- const growX = this .zoomWidth - this .width
196
- const growY = this .zoomHeight - this .height
176
+ if (this .dragging && this .zoomRatio > 1 && clientX > 0 && clientY > 0 ) {
177
+ this .posX += (clientX - this .dragX )
178
+ this .posY += (clientY - this .dragY )
197
179
198
- this .shiftX = Math .min (Math .max (moveX, - growX / 2 ), growX / 2 )
199
- this .shiftY = Math .min (Math .max (moveY, - growY / 2 ), growX / 2 )
200
- this .dragX = pageX
201
- this .dragY = pageY
180
+ this .dragX = clientX
181
+ this .dragY = clientY
202
182
}
203
183
},
184
+
185
+ /**
186
+ * Double click handler
187
+ */
204
188
onDblclick () {
205
189
if (this .zoomRatio > 1 ) {
206
190
this .resetZoom ()
207
191
} else {
192
+ this .animateTransition = true
193
+ // 0.15 is half of the delta in zoom,
194
+ // so that multiplied by width/height centers the image
195
+ this .posX = - (this .$el .width * 0.15 )
196
+ this .posY = - (this .$el .height * 0.15 )
208
197
this .zoomRatio = 1.3
198
+ // Animation is 100ms so give it double the time to finish
199
+ setTimeout (() => { this .animateTransition = false }, 200 )
209
200
}
210
201
},
211
202
},
212
203
}
213
204
</script >
214
205
215
206
<style scoped lang="scss">
216
- $checkered-size : 8px ;
217
- $checkered-color : #efefef ;
218
-
219
207
img {
220
- max-width : 100% ;
221
- max-height : 100% ;
222
208
align-self : center ;
223
209
justify-self : center ;
224
- // black while loading
225
- background-color : #000 ;
226
- // animate zooming/resize
227
- transition : height 100ms ease ,
228
- width 100ms ease ,
229
- margin-top 100ms ease ,
230
- margin-left 100ms ease ;
231
- // show checkered bg on hover if not currently zooming (but ok if zoomed)
232
- & :hover {
233
- background-image : linear-gradient (45deg , #{$checkered-color } 25% , transparent 25% ),
234
- linear-gradient (45deg , transparent 75% , #{$checkered-color } 75% ),
235
- linear-gradient (45deg , transparent 75% , #{$checkered-color } 75% ),
236
- linear-gradient (45deg , #{$checkered-color } 25% , #fff 25% );
237
- background-size : 2 * $checkered-size 2 * $checkered-size ;
238
- background-position : 0 0 , 0 0 , - #{$checkered-size } - #{$checkered-size } , $checkered-size $checkered-size ;
239
- }
240
- & .loaded {
241
- // white once done loading
242
- background-color : #fff ;
243
- }
244
- & .zoomed {
245
- position : absolute ;
246
- max-height : none ;
247
- max-width : none ;
248
- z-index : 10010 ;
249
- cursor : move ;
250
- }
210
+ position : absolute ;
211
+ width : 100% ;
212
+ height : 100vh ;
213
+ object-fit : contain ;
214
+ transform-origin : 0 0 ;
215
+ transition : none !important ;
251
216
252
217
& .dragging {
253
218
transition : none !important ;
254
219
cursor : move ;
255
220
}
221
+
222
+ & .animate-transition {
223
+ transition : transform 100ms ease !important ;
224
+ }
256
225
}
257
226
</style >
0 commit comments