Skip to content

Commit 4724dd0

Browse files
committed
Fix scroll to zoom, resize small images to fill
Setting the background to white is removed since it doesn't work well with the zooming (Outside the image boundaries is still transparent) Signed-off-by: ktprograms <[email protected]>
1 parent c6efba1 commit 4724dd0

File tree

3 files changed

+69629
-94
lines changed

3 files changed

+69629
-94
lines changed

js/viewer-main.js

+69,569-3
Large diffs are not rendered by default.

js/viewer-main.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Images.vue

+59-90
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,14 @@
2424
<img
2525
:class="{
2626
dragging,
27-
loaded,
28-
zoomed: zoomRatio !== 1
27+
'animate-transition': animateTransition,
2928
}"
3029
:src="data"
3130
: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 + ')',
3632
}"
3733
@load="updateImgSize"
38-
@wheel="updateZoom"
34+
@wheel.prevent.stop="updateZoom"
3935
@dblclick.prevent="onDblclick"
4036
@mousedown.prevent="dragStart">
4137
</template>
@@ -60,20 +56,12 @@ export default {
6056
data() {
6157
return {
6258
dragging: false,
63-
shiftX: 0,
64-
shiftY: 0,
59+
animateTransition: false,
6560
zoomRatio: 1,
61+
posX: 0,
62+
posY: 0,
6663
}
6764
},
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-
7765
asyncComputed: {
7866
data() {
7967
switch (this.mime) {
@@ -121,47 +109,46 @@ export default {
121109

122110
/**
123111
* Handle zooming
112+
* Code based on https://stackoverflow.com/a/46833254/15603854
124113
*
125114
* @param {Event} event the scroll event
126115
* @returns {null}
127116
*/
128117
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
137118
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
143120
// do not continue, img is back to its original state
144-
if (newZoomRatio === 1) {
121+
if (this.zoomRatio + delta === 1) {
145122
return this.resetZoom()
146123
}
147124

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
154125
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
158142
},
159143

160144
resetZoom() {
145+
this.animateTransition = true
161146
this.enableSwipe()
162147
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)
165152
},
166153

167154
/**
@@ -170,10 +157,8 @@ export default {
170157
* @param {Event} event the event
171158
*/
172159
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
177162
this.dragging = true
178163
this.$el.onmouseup = this.dragEnd
179164
this.$el.onmousemove = this.dragHandler
@@ -186,72 +171,56 @@ export default {
186171
this.$el.onmousemove = null
187172
},
188173
dragHandler(event) {
189-
event.preventDefault()
190-
const { pageX, pageY } = event
174+
const { clientX, clientY } = event
191175

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)
197179

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
202182
}
203183
},
184+
185+
/**
186+
* Double click handler
187+
*/
204188
onDblclick() {
205189
if (this.zoomRatio > 1) {
206190
this.resetZoom()
207191
} 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)
208197
this.zoomRatio = 1.3
198+
// Animation is 100ms so give it double the time to finish
199+
setTimeout(() => { this.animateTransition = false }, 200)
209200
}
210201
},
211202
},
212203
}
213204
</script>
214205

215206
<style scoped lang="scss">
216-
$checkered-size: 8px;
217-
$checkered-color: #efefef;
218-
219207
img {
220-
max-width: 100%;
221-
max-height: 100%;
222208
align-self: center;
223209
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;
251216

252217
&.dragging {
253218
transition: none !important;
254219
cursor: move;
255220
}
221+
222+
&.animate-transition {
223+
transition: transform 100ms ease !important;
224+
}
256225
}
257226
</style>

0 commit comments

Comments
 (0)