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