slidev-theme-watabegg 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +23 -0
- package/README.md +23 -0
- package/example.md +3 -0
- package/layouts/image-scroll.vue +327 -52
- package/package.json +1 -1
- package/styles/layout.css +14 -4
package/README.ja.md
CHANGED
|
@@ -87,6 +87,29 @@ image: /path/to/long-bg.jpg
|
|
|
87
87
|
|
|
88
88
|

|
|
89
89
|
|
|
90
|
+
image-scroll オプション(frontmatter `imageScroll`):
|
|
91
|
+
- `fit`: `contain | cover | width | height`(既定 `contain`)
|
|
92
|
+
- `position`: `center | top | bottom | left | right` もしくは組み合わせ(例: `top left`)
|
|
93
|
+
- `offset`: `[x, y]` または `{ x, y }`(px)
|
|
94
|
+
|
|
95
|
+
操作:
|
|
96
|
+
- Wheel: 縦パン
|
|
97
|
+
- Shift + Wheel: 横パン
|
|
98
|
+
- Alt + Wheel: 10% ずつ拡大縮小
|
|
99
|
+
- タッチ: ドラッグでパン、ピンチでズーム
|
|
100
|
+
|
|
101
|
+
初期フィット/位置の例:
|
|
102
|
+
```markdown
|
|
103
|
+
---
|
|
104
|
+
layout: image-scroll
|
|
105
|
+
image: /path/to/long-bg.jpg
|
|
106
|
+
imageScroll:
|
|
107
|
+
fit: width
|
|
108
|
+
position: center
|
|
109
|
+
offset: [0, -120]
|
|
110
|
+
---
|
|
111
|
+
```
|
|
112
|
+
|
|
90
113
|
## コンポーネント
|
|
91
114
|
### QuestionList
|
|
92
115
|
入れ子質問/解答・Markdown 埋め込み・階層別スタイル。
|
package/README.md
CHANGED
|
@@ -91,6 +91,29 @@ image: /path/to/long-bg.jpg
|
|
|
91
91
|
|
|
92
92
|

|
|
93
93
|
|
|
94
|
+
image-scroll options (frontmatter `imageScroll`):
|
|
95
|
+
- `fit`: `contain | cover | width | height` (default `contain`)
|
|
96
|
+
- `position`: `center | top | bottom | left | right` or combined (e.g. `top left`)
|
|
97
|
+
- `offset`: `[x, y]` or `{ x, y }` in px
|
|
98
|
+
|
|
99
|
+
Controls:
|
|
100
|
+
- Wheel: pan vertically
|
|
101
|
+
- Shift + Wheel: pan horizontally
|
|
102
|
+
- Alt + Wheel: zoom in/out by 10%
|
|
103
|
+
- Touch: drag to pan, pinch to zoom
|
|
104
|
+
|
|
105
|
+
Example with initial fit/position:
|
|
106
|
+
```markdown
|
|
107
|
+
---
|
|
108
|
+
layout: image-scroll
|
|
109
|
+
image: /path/to/long-bg.jpg
|
|
110
|
+
imageScroll:
|
|
111
|
+
fit: width
|
|
112
|
+
position: center
|
|
113
|
+
offset: [0, -120]
|
|
114
|
+
---
|
|
115
|
+
```
|
|
116
|
+
|
|
94
117
|
## Components
|
|
95
118
|
### QuestionList
|
|
96
119
|
Nested questions/answers with per-level styles and Markdown rendering.
|
package/example.md
CHANGED
|
@@ -139,6 +139,9 @@ image: 'https://images.unsplash.com/photo-1501594907352-04cda38ebc29?auto=format
|
|
|
139
139
|
---
|
|
140
140
|
layout: image-scroll
|
|
141
141
|
image: 'https://images.unsplash.com/photo-1501785888041-af3ef285b470?auto=format&fit=crop&w=2400&q=80'
|
|
142
|
+
imageScroll:
|
|
143
|
+
fit: width
|
|
144
|
+
position: center
|
|
142
145
|
---
|
|
143
146
|
|
|
144
147
|
- マウスホイール: 拡大
|
package/layouts/image-scroll.vue
CHANGED
|
@@ -3,16 +3,23 @@
|
|
|
3
3
|
class="slidev-layout image-scroll"
|
|
4
4
|
ref="wrapperRef"
|
|
5
5
|
@wheel="onWheel"
|
|
6
|
-
@
|
|
6
|
+
@pointerdown="onPointerDown"
|
|
7
|
+
@pointermove="onPointerMove"
|
|
8
|
+
@pointerup="onPointerUp"
|
|
9
|
+
@pointercancel="onPointerUp"
|
|
7
10
|
>
|
|
8
11
|
<div class="image-layer" :class="{ 'is-panning': isPanning }">
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<div class="image-translate" :style="translateStyle">
|
|
13
|
+
<img
|
|
14
|
+
v-if="imageSrc"
|
|
15
|
+
ref="imageRef"
|
|
16
|
+
:src="imageSrc"
|
|
17
|
+
alt="Scrollable background"
|
|
18
|
+
:style="imageStyle"
|
|
19
|
+
draggable="false"
|
|
20
|
+
@load="onImageLoad"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
16
23
|
</div>
|
|
17
24
|
<div class="content">
|
|
18
25
|
<slot />
|
|
@@ -29,85 +36,344 @@ const { $frontmatter } = useSlideContext()
|
|
|
29
36
|
const imageSrc = computed(() => $frontmatter.image)
|
|
30
37
|
|
|
31
38
|
const wrapperRef = ref<HTMLElement | null>(null)
|
|
32
|
-
const
|
|
33
|
-
|
|
39
|
+
const imageRef = ref<HTMLImageElement | null>(null)
|
|
40
|
+
|
|
41
|
+
type FitMode = 'contain' | 'cover' | 'width' | 'height'
|
|
42
|
+
|
|
43
|
+
type ImageScrollFrontmatter = {
|
|
44
|
+
fit?: FitMode | string
|
|
45
|
+
position?: string
|
|
46
|
+
offset?: [number, number] | { x?: number; y?: number }
|
|
47
|
+
offsetX?: number
|
|
48
|
+
offsetY?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const imageScrollOptions = computed<ImageScrollFrontmatter>(() => {
|
|
52
|
+
const raw = $frontmatter.imageScroll
|
|
53
|
+
if (raw && typeof raw === 'object') return raw as ImageScrollFrontmatter
|
|
54
|
+
return {}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const fitMode = computed<FitMode>(() => {
|
|
58
|
+
const value = imageScrollOptions.value.fit
|
|
59
|
+
if (!value) return 'contain'
|
|
60
|
+
const normalized = String(value).toLowerCase()
|
|
61
|
+
if (normalized === 'cover') return 'cover'
|
|
62
|
+
if (normalized === 'width' || normalized === 'fit-width') return 'width'
|
|
63
|
+
if (normalized === 'height' || normalized === 'fit-height') return 'height'
|
|
64
|
+
return 'contain'
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const positionMode = computed(() => imageScrollOptions.value.position ?? 'center')
|
|
68
|
+
|
|
69
|
+
const offsetMode = computed(() => {
|
|
70
|
+
const raw = imageScrollOptions.value
|
|
71
|
+
const offset = raw.offset
|
|
72
|
+
let x = Number(raw.offsetX ?? 0)
|
|
73
|
+
let y = Number(raw.offsetY ?? 0)
|
|
74
|
+
if (Array.isArray(offset)) {
|
|
75
|
+
x = Number(offset[0] ?? x)
|
|
76
|
+
y = Number(offset[1] ?? y)
|
|
77
|
+
} else if (offset && typeof offset === 'object') {
|
|
78
|
+
x = Number((offset as { x?: number }).x ?? x)
|
|
79
|
+
y = Number((offset as { y?: number }).y ?? y)
|
|
80
|
+
}
|
|
81
|
+
return { x, y }
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const baseTransform = reactive({ x: 0, y: 0, scale: 1 })
|
|
85
|
+
const userTransform = reactive({ x: 0, y: 0, scale: 1 })
|
|
86
|
+
|
|
87
|
+
const imageSize = reactive({ width: 0, height: 0 })
|
|
88
|
+
const wrapperSize = reactive({ width: 0, height: 0 })
|
|
89
|
+
|
|
34
90
|
const isPanning = ref(false)
|
|
35
91
|
const lastPointer = reactive({ x: 0, y: 0 })
|
|
92
|
+
const activePointers = new Map<number, { x: number; y: number }>()
|
|
93
|
+
const pinchState = reactive({
|
|
94
|
+
active: false,
|
|
95
|
+
startDistance: 0,
|
|
96
|
+
startUserScale: 1,
|
|
97
|
+
anchorImagePoint: { x: 0, y: 0 },
|
|
98
|
+
})
|
|
36
99
|
|
|
37
100
|
const MIN_SCALE = 0.4
|
|
38
101
|
const MAX_SCALE = 4
|
|
39
|
-
const ZOOM_STEP = 0.
|
|
102
|
+
const ZOOM_STEP = 0.1
|
|
40
103
|
|
|
41
104
|
const clamp = (val: number, min: number, max: number) => Math.min(max, Math.max(min, val))
|
|
42
105
|
|
|
43
106
|
const resetTransform = () => {
|
|
44
|
-
scale
|
|
45
|
-
|
|
46
|
-
|
|
107
|
+
baseTransform.scale = 1
|
|
108
|
+
baseTransform.x = 0
|
|
109
|
+
baseTransform.y = 0
|
|
110
|
+
userTransform.scale = 1
|
|
111
|
+
userTransform.x = 0
|
|
112
|
+
userTransform.y = 0
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const panBy = (dx: number, dy: number) => {
|
|
116
|
+
userTransform.x += dx
|
|
117
|
+
userTransform.y += dy
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const getWrapperPoint = (event: { clientX: number; clientY: number }) => {
|
|
121
|
+
const rect = wrapperRef.value?.getBoundingClientRect()
|
|
122
|
+
if (!rect) return { x: 0, y: 0 }
|
|
123
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const getTotalTransform = () => ({
|
|
127
|
+
x: baseTransform.x + userTransform.x,
|
|
128
|
+
y: baseTransform.y + userTransform.y,
|
|
129
|
+
scale: baseTransform.scale * userTransform.scale,
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const getImagePoint = (anchor: { x: number; y: number }, total: { x: number; y: number; scale: number }) => ({
|
|
133
|
+
x: (anchor.x - total.x) / total.scale,
|
|
134
|
+
y: (anchor.y - total.y) / total.scale,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const applyScaleAtPoint = (nextUserScale: number, anchor: { x: number; y: number }, anchorImagePoint?: { x: number; y: number }) => {
|
|
138
|
+
const currentTotal = getTotalTransform()
|
|
139
|
+
const imagePoint = anchorImagePoint ?? getImagePoint(anchor, currentTotal)
|
|
140
|
+
const nextTotalScale = baseTransform.scale * nextUserScale
|
|
141
|
+
const nextTotalX = anchor.x - imagePoint.x * nextTotalScale
|
|
142
|
+
const nextTotalY = anchor.y - imagePoint.y * nextTotalScale
|
|
143
|
+
userTransform.scale = nextUserScale
|
|
144
|
+
userTransform.x = nextTotalX - baseTransform.x
|
|
145
|
+
userTransform.y = nextTotalY - baseTransform.y
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const normalizeWheel = (event: WheelEvent) => {
|
|
149
|
+
let { deltaX, deltaY } = event
|
|
150
|
+
if (event.deltaMode === 1) {
|
|
151
|
+
deltaX *= 16
|
|
152
|
+
deltaY *= 16
|
|
153
|
+
} else if (event.deltaMode === 2) {
|
|
154
|
+
deltaX *= wrapperSize.height || 1
|
|
155
|
+
deltaY *= wrapperSize.height || 1
|
|
156
|
+
}
|
|
157
|
+
return { deltaX, deltaY }
|
|
47
158
|
}
|
|
48
159
|
|
|
49
160
|
const onWheel = (event: WheelEvent) => {
|
|
50
161
|
if (!imageSrc.value) return
|
|
51
162
|
|
|
52
|
-
|
|
53
|
-
const direction = event.shiftKey ? -1 : event.deltaY < 0 ? 1 : -1
|
|
54
|
-
const next = clamp(scale.value + direction * ZOOM_STEP, MIN_SCALE, MAX_SCALE)
|
|
163
|
+
event.preventDefault()
|
|
55
164
|
|
|
56
|
-
if (
|
|
165
|
+
if (event.altKey) {
|
|
166
|
+
const anchor = getWrapperPoint(event)
|
|
167
|
+
const zoomFactor = event.deltaY < 0 ? 1 + ZOOM_STEP : 1 - ZOOM_STEP
|
|
168
|
+
const nextScale = clamp(userTransform.scale * zoomFactor, MIN_SCALE, MAX_SCALE)
|
|
169
|
+
if (nextScale === userTransform.scale) return
|
|
170
|
+
applyScaleAtPoint(nextScale, anchor)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
57
173
|
|
|
58
|
-
event
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
isPanning.value = true
|
|
65
|
-
lastPointer.x = event.clientX
|
|
66
|
-
lastPointer.y = event.clientY
|
|
67
|
-
window.addEventListener('mousemove', onMouseMove)
|
|
68
|
-
window.addEventListener('mouseup', onMouseUp)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const onMouseMove = (event: MouseEvent) => {
|
|
72
|
-
if (!isPanning.value) return
|
|
73
|
-
const dx = event.clientX - lastPointer.x
|
|
74
|
-
const dy = event.clientY - lastPointer.y
|
|
75
|
-
position.x += dx
|
|
76
|
-
position.y += dy
|
|
77
|
-
lastPointer.x = event.clientX
|
|
78
|
-
lastPointer.y = event.clientY
|
|
79
|
-
event.preventDefault()
|
|
174
|
+
const { deltaX, deltaY } = normalizeWheel(event)
|
|
175
|
+
if (event.shiftKey) {
|
|
176
|
+
panBy(-deltaY, 0)
|
|
177
|
+
} else {
|
|
178
|
+
panBy(0, -deltaY)
|
|
179
|
+
}
|
|
80
180
|
}
|
|
81
181
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
182
|
+
let resizeObserver: ResizeObserver | null = null
|
|
183
|
+
|
|
184
|
+
const onPointerDown = (event: PointerEvent) => {
|
|
185
|
+
if (!imageSrc.value) return
|
|
186
|
+
if (event.pointerType === 'mouse' && event.button !== 0) return
|
|
187
|
+
wrapperRef.value?.setPointerCapture(event.pointerId)
|
|
188
|
+
const point = getWrapperPoint(event)
|
|
189
|
+
activePointers.set(event.pointerId, point)
|
|
190
|
+
if (activePointers.size === 1) {
|
|
191
|
+
isPanning.value = true
|
|
192
|
+
lastPointer.x = point.x
|
|
193
|
+
lastPointer.y = point.y
|
|
194
|
+
}
|
|
195
|
+
if (activePointers.size === 2) {
|
|
196
|
+
isPanning.value = false
|
|
197
|
+
const [first, second] = Array.from(activePointers.values())
|
|
198
|
+
const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 }
|
|
199
|
+
const distance = Math.hypot(first.x - second.x, first.y - second.y)
|
|
200
|
+
const total = getTotalTransform()
|
|
201
|
+
pinchState.active = true
|
|
202
|
+
pinchState.startDistance = distance
|
|
203
|
+
pinchState.startUserScale = userTransform.scale
|
|
204
|
+
pinchState.anchorImagePoint = getImagePoint(center, total)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const onPointerMove = (event: PointerEvent) => {
|
|
209
|
+
if (!activePointers.has(event.pointerId)) return
|
|
210
|
+
const point = getWrapperPoint(event)
|
|
211
|
+
activePointers.set(event.pointerId, point)
|
|
212
|
+
|
|
213
|
+
if (activePointers.size === 1 && !pinchState.active) {
|
|
214
|
+
const dx = point.x - lastPointer.x
|
|
215
|
+
const dy = point.y - lastPointer.y
|
|
216
|
+
panBy(dx, dy)
|
|
217
|
+
lastPointer.x = point.x
|
|
218
|
+
lastPointer.y = point.y
|
|
219
|
+
event.preventDefault()
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (activePointers.size >= 2 && pinchState.active) {
|
|
224
|
+
const [first, second] = Array.from(activePointers.values())
|
|
225
|
+
const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 }
|
|
226
|
+
const distance = Math.hypot(first.x - second.x, first.y - second.y)
|
|
227
|
+
if (pinchState.startDistance > 0) {
|
|
228
|
+
const factor = distance / pinchState.startDistance
|
|
229
|
+
const nextScale = clamp(pinchState.startUserScale * factor, MIN_SCALE, MAX_SCALE)
|
|
230
|
+
applyScaleAtPoint(nextScale, center, pinchState.anchorImagePoint)
|
|
231
|
+
event.preventDefault()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const onPointerUp = (event: PointerEvent) => {
|
|
237
|
+
if (activePointers.has(event.pointerId)) {
|
|
238
|
+
activePointers.delete(event.pointerId)
|
|
239
|
+
}
|
|
240
|
+
if (activePointers.size < 2) {
|
|
241
|
+
pinchState.active = false
|
|
242
|
+
}
|
|
243
|
+
if (activePointers.size === 1) {
|
|
244
|
+
const [remaining] = Array.from(activePointers.values())
|
|
245
|
+
lastPointer.x = remaining.x
|
|
246
|
+
lastPointer.y = remaining.y
|
|
247
|
+
isPanning.value = true
|
|
248
|
+
} else {
|
|
249
|
+
isPanning.value = false
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const computeBaseTransform = () => {
|
|
254
|
+
if (!imageSrc.value) return
|
|
255
|
+
if (!imageSize.width || !imageSize.height) return
|
|
256
|
+
if (!wrapperSize.width || !wrapperSize.height) return
|
|
257
|
+
|
|
258
|
+
const scaleX = wrapperSize.width / imageSize.width
|
|
259
|
+
const scaleY = wrapperSize.height / imageSize.height
|
|
260
|
+
let scale = scaleX
|
|
261
|
+
switch (fitMode.value) {
|
|
262
|
+
case 'cover':
|
|
263
|
+
scale = Math.max(scaleX, scaleY)
|
|
264
|
+
break
|
|
265
|
+
case 'height':
|
|
266
|
+
scale = scaleY
|
|
267
|
+
break
|
|
268
|
+
case 'width':
|
|
269
|
+
scale = scaleX
|
|
270
|
+
break
|
|
271
|
+
default:
|
|
272
|
+
scale = Math.min(scaleX, scaleY)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const scaledWidth = imageSize.width * scale
|
|
276
|
+
const scaledHeight = imageSize.height * scale
|
|
277
|
+
const position = String(positionMode.value).toLowerCase()
|
|
278
|
+
const parts = position.split(/\s+/).filter(Boolean)
|
|
279
|
+
let xAlign: 'left' | 'center' | 'right' = 'center'
|
|
280
|
+
let yAlign: 'top' | 'center' | 'bottom' = 'center'
|
|
281
|
+
|
|
282
|
+
for (const part of parts) {
|
|
283
|
+
if (part === 'left' || part === 'center' || part === 'right') xAlign = part
|
|
284
|
+
if (part === 'top' || part === 'center' || part === 'bottom') yAlign = part
|
|
285
|
+
}
|
|
286
|
+
if (parts.length === 1) {
|
|
287
|
+
if (parts[0] === 'top' || parts[0] === 'bottom') {
|
|
288
|
+
yAlign = parts[0] as typeof yAlign
|
|
289
|
+
xAlign = 'center'
|
|
290
|
+
}
|
|
291
|
+
if (parts[0] === 'left' || parts[0] === 'right') {
|
|
292
|
+
xAlign = parts[0] as typeof xAlign
|
|
293
|
+
yAlign = 'center'
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const alignAxis = (container: number, content: number, align: 'left' | 'center' | 'right' | 'top' | 'bottom') => {
|
|
298
|
+
if (align === 'left' || align === 'top') return 0
|
|
299
|
+
if (align === 'right' || align === 'bottom') return container - content
|
|
300
|
+
return (container - content) / 2
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const offset = offsetMode.value
|
|
304
|
+
baseTransform.scale = scale
|
|
305
|
+
baseTransform.x = alignAxis(wrapperSize.width, scaledWidth, xAlign) + offset.x
|
|
306
|
+
baseTransform.y = alignAxis(wrapperSize.height, scaledHeight, yAlign) + offset.y
|
|
307
|
+
userTransform.scale = 1
|
|
308
|
+
userTransform.x = 0
|
|
309
|
+
userTransform.y = 0
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const onImageLoad = (event: Event) => {
|
|
313
|
+
const target = event.target as HTMLImageElement
|
|
314
|
+
imageSize.width = target.naturalWidth
|
|
315
|
+
imageSize.height = target.naturalHeight
|
|
316
|
+
computeBaseTransform()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const updateWrapperSize = () => {
|
|
320
|
+
const rect = wrapperRef.value?.getBoundingClientRect()
|
|
321
|
+
if (!rect) return
|
|
322
|
+
wrapperSize.width = rect.width
|
|
323
|
+
wrapperSize.height = rect.height
|
|
86
324
|
}
|
|
87
325
|
|
|
88
326
|
const imageStyle = computed(() => ({
|
|
89
|
-
|
|
327
|
+
width: imageSize.width ? `${imageSize.width}px` : 'auto',
|
|
328
|
+
height: imageSize.height ? `${imageSize.height}px` : 'auto',
|
|
329
|
+
transform: `scale(${baseTransform.scale * userTransform.scale})`,
|
|
330
|
+
transformOrigin: '0 0',
|
|
331
|
+
}))
|
|
332
|
+
|
|
333
|
+
const translateStyle = computed(() => ({
|
|
334
|
+
transform: `translate3d(${baseTransform.x + userTransform.x}px, ${baseTransform.y + userTransform.y}px, 0)`,
|
|
90
335
|
}))
|
|
91
336
|
|
|
92
337
|
onMounted(() => {
|
|
93
|
-
|
|
338
|
+
updateWrapperSize()
|
|
339
|
+
if (imageRef.value?.complete && imageRef.value.naturalWidth) {
|
|
340
|
+
imageSize.width = imageRef.value.naturalWidth
|
|
341
|
+
imageSize.height = imageRef.value.naturalHeight
|
|
342
|
+
computeBaseTransform()
|
|
343
|
+
}
|
|
344
|
+
if (wrapperRef.value) {
|
|
345
|
+
resizeObserver = new ResizeObserver(() => {
|
|
346
|
+
updateWrapperSize()
|
|
347
|
+
computeBaseTransform()
|
|
348
|
+
})
|
|
349
|
+
resizeObserver.observe(wrapperRef.value)
|
|
350
|
+
}
|
|
94
351
|
})
|
|
95
352
|
|
|
96
353
|
onBeforeUnmount(() => {
|
|
97
|
-
|
|
98
|
-
|
|
354
|
+
resizeObserver?.disconnect()
|
|
355
|
+
resizeObserver = null
|
|
356
|
+
activePointers.clear()
|
|
99
357
|
})
|
|
100
358
|
|
|
101
359
|
watch(imageSrc, () => {
|
|
360
|
+
imageSize.width = 0
|
|
361
|
+
imageSize.height = 0
|
|
102
362
|
resetTransform()
|
|
103
363
|
})
|
|
364
|
+
|
|
365
|
+
watch([fitMode, positionMode, offsetMode], () => {
|
|
366
|
+
computeBaseTransform()
|
|
367
|
+
})
|
|
104
368
|
</script>
|
|
105
369
|
|
|
106
370
|
<style scoped>
|
|
107
371
|
.slidev-layout.image-scroll {
|
|
108
372
|
position: relative;
|
|
109
373
|
height: 100%;
|
|
374
|
+
padding: 0;
|
|
110
375
|
overflow: hidden;
|
|
376
|
+
touch-action: none;
|
|
111
377
|
}
|
|
112
378
|
|
|
113
379
|
.image-layer {
|
|
@@ -115,18 +381,27 @@ watch(imageSrc, () => {
|
|
|
115
381
|
inset: 0;
|
|
116
382
|
overflow: hidden;
|
|
117
383
|
cursor: grab;
|
|
384
|
+
user-select: none;
|
|
118
385
|
}
|
|
119
386
|
|
|
120
387
|
.image-layer.is-panning {
|
|
121
388
|
cursor: grabbing;
|
|
122
389
|
}
|
|
123
390
|
|
|
391
|
+
.image-translate {
|
|
392
|
+
position: absolute;
|
|
393
|
+
top: 0;
|
|
394
|
+
left: 0;
|
|
395
|
+
will-change: transform;
|
|
396
|
+
}
|
|
397
|
+
|
|
124
398
|
.image-layer img {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
399
|
+
display: block;
|
|
400
|
+
max-width: none;
|
|
401
|
+
max-height: none;
|
|
402
|
+
pointer-events: none;
|
|
403
|
+
user-select: none;
|
|
128
404
|
will-change: transform;
|
|
129
|
-
transition: transform 80ms ease-out;
|
|
130
405
|
}
|
|
131
406
|
|
|
132
407
|
.content {
|
package/package.json
CHANGED
package/styles/layout.css
CHANGED
|
@@ -110,24 +110,34 @@
|
|
|
110
110
|
position: relative;
|
|
111
111
|
padding: 0;
|
|
112
112
|
overflow: hidden;
|
|
113
|
+
touch-action: none;
|
|
113
114
|
|
|
114
115
|
.image-layer {
|
|
115
116
|
position: absolute;
|
|
116
117
|
inset: 0;
|
|
117
118
|
cursor: grab;
|
|
118
119
|
overflow: hidden;
|
|
120
|
+
user-select: none;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
.image-layer.is-panning {
|
|
122
124
|
cursor: grabbing;
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
.image-translate {
|
|
128
|
+
position: absolute;
|
|
129
|
+
top: 0;
|
|
130
|
+
left: 0;
|
|
131
|
+
will-change: transform;
|
|
132
|
+
}
|
|
133
|
+
|
|
125
134
|
.image-layer img {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
display: block;
|
|
136
|
+
max-width: none;
|
|
137
|
+
max-height: none;
|
|
138
|
+
pointer-events: none;
|
|
139
|
+
user-select: none;
|
|
129
140
|
will-change: transform;
|
|
130
|
-
transition: transform 80ms ease-out;
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
.content {
|