|
|
@@ -8,16 +8,46 @@
|
|
|
</view>
|
|
|
<template v-else>
|
|
|
<!-- #ifdef H5 -->
|
|
|
- <view class="pdf-viewport" :id="viewportId" :style="{ height: viewerHeight }">
|
|
|
- <view class="pdf-transform-layer" :style="transformStyle">
|
|
|
- <view class="pdf-container" :id="containerId"></view>
|
|
|
+ <scroll-view
|
|
|
+ scroll-y
|
|
|
+ scroll-x
|
|
|
+ class="pdf-content-area"
|
|
|
+ :style="{ height: viewerHeight }"
|
|
|
+ >
|
|
|
+ <view
|
|
|
+ v-if="contentReady"
|
|
|
+ class="pdf-scroll-wrapper"
|
|
|
+ :style="scrollWrapperStyle"
|
|
|
+ >
|
|
|
+ <view class="pdf-scale-layer" :style="scaleLayerStyle">
|
|
|
+ <view class="pdf-container" :id="containerId"></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- <view class="pdf-zoom-bar">
|
|
|
+ </scroll-view>
|
|
|
+ <!-- #endif -->
|
|
|
+ <!-- #ifndef H5 -->
|
|
|
+ <scroll-view
|
|
|
+ scroll-y
|
|
|
+ scroll-x
|
|
|
+ class="pdf-content-area"
|
|
|
+ :style="{ height: viewerHeight }"
|
|
|
+ >
|
|
|
+ <view v-if="contentReady" class="pdf-scroll-wrapper" :style="scrollWrapperStyle">
|
|
|
+ <view class="pdf-scale-layer" :style="scaleLayerStyle">
|
|
|
+ <image
|
|
|
+ :src="imageSrc"
|
|
|
+ mode="widthFix"
|
|
|
+ class="pdf-image"
|
|
|
+ :style="{ width: contentWidth + 'px' }"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ <!-- #endif -->
|
|
|
+ <view v-if="contentReady" class="pdf-zoom-bar">
|
|
|
<view class="zoom-btn" @click="zoomOut">
|
|
|
<text class="zoom-btn-text">−</text>
|
|
|
</view>
|
|
|
- <text class="zoom-label">{{ zoomPercent }}%</text>
|
|
|
<view class="zoom-btn" @click="zoomIn">
|
|
|
<text class="zoom-btn-text">+</text>
|
|
|
</view>
|
|
|
@@ -25,33 +55,6 @@
|
|
|
<text class="zoom-btn-text">重置</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <!-- #endif -->
|
|
|
- <!-- #ifndef H5 -->
|
|
|
- <view v-if="imageSrc" class="pdf-image-area" :style="{ height: viewerHeight }">
|
|
|
- <movable-area class="pdf-movable-area">
|
|
|
- <movable-view
|
|
|
- v-if="imageWidth > 0 && imageHeight > 0"
|
|
|
- direction="all"
|
|
|
- :scale="true"
|
|
|
- scale-min="0.5"
|
|
|
- scale-max="5"
|
|
|
- :scale-value="1"
|
|
|
- class="pdf-movable-view"
|
|
|
- :style="{ width: imageWidth + 'px', height: imageHeight + 'px' }"
|
|
|
- >
|
|
|
- <image
|
|
|
- :src="imageSrc"
|
|
|
- mode="aspectFit"
|
|
|
- class="pdf-image"
|
|
|
- :style="{ width: imageWidth + 'px' }"
|
|
|
- />
|
|
|
- </movable-view>
|
|
|
- </movable-area>
|
|
|
- </view>
|
|
|
- <view v-else class="pdf-placeholder">
|
|
|
- <text class="placeholder-text">暂无PDF预览</text>
|
|
|
- </view>
|
|
|
- <!-- #endif -->
|
|
|
</template>
|
|
|
</view>
|
|
|
</template>
|
|
|
@@ -80,267 +83,109 @@ const errorMsg = ref('')
|
|
|
const pageCount = ref(0)
|
|
|
let pdfDoc: any = null
|
|
|
|
|
|
+const contentReady = ref(false)
|
|
|
+const contentWidth = ref(0)
|
|
|
+const contentHeight = ref(0)
|
|
|
+const firstPageHeight = ref(0)
|
|
|
+const fitScale = ref(1)
|
|
|
+const currentScale = ref(1)
|
|
|
+
|
|
|
const imageSrc = ref('')
|
|
|
-const imageWidth = ref(0)
|
|
|
-const imageHeight = ref(0)
|
|
|
|
|
|
let isMounted = false
|
|
|
|
|
|
// #ifdef H5
|
|
|
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs'
|
|
|
import pdfjsWorker from 'pdfjs-dist/legacy/build/pdf.worker.min.mjs?url'
|
|
|
+// #endif
|
|
|
|
|
|
-const viewportId = ref(`pdf-vp-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`)
|
|
|
-
|
|
|
-const MIN_ZOOM = 0.5
|
|
|
const MAX_ZOOM = 5
|
|
|
const ZOOM_STEP = 0.25
|
|
|
|
|
|
-const zoomScale = ref(1)
|
|
|
-const panX = ref(0)
|
|
|
-const panY = ref(0)
|
|
|
-
|
|
|
-let contentHeight = 0
|
|
|
-
|
|
|
-const zoomPercent = computed(() => Math.round(zoomScale.value * 100))
|
|
|
+const scaleMin = computed(() => Math.max(0.5, fitScale.value))
|
|
|
|
|
|
-const transformStyle = computed(() => ({
|
|
|
- transform: `translate(${panX.value}px, ${panY.value}px) scale(${zoomScale.value})`,
|
|
|
+const scaleLayerStyle = computed(() => ({
|
|
|
+ width: `${contentWidth.value}px`,
|
|
|
+ height: `${contentHeight.value}px`,
|
|
|
+ transform: `scale(${currentScale.value})`,
|
|
|
transformOrigin: '0 0',
|
|
|
}))
|
|
|
|
|
|
-let touchStartDistance = 0
|
|
|
-let touchStartScale = 1
|
|
|
-let touchStartPanX = 0
|
|
|
-let touchStartPanY = 0
|
|
|
-let pinchCenterX = 0
|
|
|
-let pinchCenterY = 0
|
|
|
-let isPinching = false
|
|
|
-let isPanning = false
|
|
|
-let isScrolling = false
|
|
|
-let singleTouchStartX = 0
|
|
|
-let singleTouchStartY = 0
|
|
|
-let scrollMoveStartY = 0
|
|
|
-let touchStartClientY = 0
|
|
|
-let lastTapTime = 0
|
|
|
-let scrollVelocity = 0
|
|
|
-let momentumAnimId = 0
|
|
|
-let lastTouchY = 0
|
|
|
-let lastTouchTime = 0
|
|
|
+const scrollWrapperStyle = computed(() => ({
|
|
|
+ width: `${Math.ceil(contentWidth.value * currentScale.value)}px`,
|
|
|
+ height: `${Math.ceil(contentHeight.value * currentScale.value)}px`,
|
|
|
+ position: 'relative' as const,
|
|
|
+}))
|
|
|
|
|
|
-function getTouchDistance(touches: TouchList): number {
|
|
|
- const dx = touches[0].clientX - touches[1].clientX
|
|
|
- const dy = touches[0].clientY - touches[1].clientY
|
|
|
- return Math.sqrt(dx * dx + dy * dy)
|
|
|
+function zoomIn() {
|
|
|
+ const newScale = Math.min(MAX_ZOOM, currentScale.value + ZOOM_STEP)
|
|
|
+ currentScale.value = newScale
|
|
|
}
|
|
|
|
|
|
-function getTouchCenter(touches: TouchList): { x: number; y: number } {
|
|
|
- return {
|
|
|
- x: (touches[0].clientX + touches[1].clientX) / 2,
|
|
|
- y: (touches[0].clientY + touches[1].clientY) / 2,
|
|
|
- }
|
|
|
+function zoomOut() {
|
|
|
+ const newScale = Math.max(scaleMin.value, currentScale.value - ZOOM_STEP)
|
|
|
+ currentScale.value = newScale
|
|
|
}
|
|
|
|
|
|
-function clampScroll() {
|
|
|
- if (zoomScale.value > 1.01) return
|
|
|
- panX.value = 0
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (!viewport) return
|
|
|
- const vh = viewport.clientHeight
|
|
|
- const maxScroll = Math.max(0, contentHeight - vh)
|
|
|
- panY.value = Math.max(-maxScroll, Math.min(0, panY.value))
|
|
|
+function resetZoom() {
|
|
|
+ currentScale.value = fitScale.value
|
|
|
}
|
|
|
|
|
|
-function onTouchStart(e: TouchEvent) {
|
|
|
- cancelAnimationFrame(momentumAnimId)
|
|
|
- scrollVelocity = 0
|
|
|
-
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (!viewport) return
|
|
|
- const rect = viewport.getBoundingClientRect()
|
|
|
-
|
|
|
- if (e.touches.length === 2) {
|
|
|
- isPinching = true
|
|
|
- isPanning = false
|
|
|
- isScrolling = false
|
|
|
- touchStartDistance = getTouchDistance(e.touches)
|
|
|
- touchStartScale = zoomScale.value
|
|
|
- touchStartPanX = panX.value
|
|
|
- touchStartPanY = panY.value
|
|
|
- const center = getTouchCenter(e.touches)
|
|
|
- pinchCenterX = center.x - rect.left
|
|
|
- pinchCenterY = center.y - rect.top
|
|
|
- e.preventDefault()
|
|
|
- } else if (e.touches.length === 1) {
|
|
|
- const now = Date.now()
|
|
|
- touchStartClientY = e.touches[0].clientY
|
|
|
- if (now - lastTapTime < 300) {
|
|
|
- if (zoomScale.value > 1.05) {
|
|
|
- zoomScale.value = 1
|
|
|
- panX.value = 0
|
|
|
- clampScroll()
|
|
|
- } else {
|
|
|
- const cx = e.touches[0].clientX - rect.left
|
|
|
- const cy = e.touches[0].clientY - rect.top
|
|
|
- const newScale = 2
|
|
|
- const ratio = newScale / zoomScale.value
|
|
|
- panX.value = cx - cx * ratio
|
|
|
- panY.value = cy - (cy - panY.value) * ratio
|
|
|
- zoomScale.value = newScale
|
|
|
- }
|
|
|
- e.preventDefault()
|
|
|
- } else if (zoomScale.value > 1.01) {
|
|
|
- isPanning = true
|
|
|
- isPinching = false
|
|
|
- isScrolling = false
|
|
|
- singleTouchStartX = e.touches[0].clientX
|
|
|
- singleTouchStartY = e.touches[0].clientY
|
|
|
- touchStartPanX = panX.value
|
|
|
- touchStartPanY = panY.value
|
|
|
- } else {
|
|
|
- isScrolling = true
|
|
|
- isPanning = false
|
|
|
- isPinching = false
|
|
|
- scrollMoveStartY = e.touches[0].clientY
|
|
|
- lastTouchY = scrollMoveStartY
|
|
|
- lastTouchTime = Date.now()
|
|
|
- }
|
|
|
- lastTapTime = now
|
|
|
+const getContainerSize = async (): Promise<{ width: number; height: number }> => {
|
|
|
+ // #ifdef H5
|
|
|
+ await nextTick()
|
|
|
+ const el = document.querySelector('.pdf-content-area')
|
|
|
+ if (el) {
|
|
|
+ const rect = el.getBoundingClientRect()
|
|
|
+ return { width: rect.width, height: rect.height }
|
|
|
}
|
|
|
-}
|
|
|
+ // #endif
|
|
|
|
|
|
-function onTouchMove(e: TouchEvent) {
|
|
|
- if (isPinching && e.touches.length >= 2) {
|
|
|
- e.preventDefault()
|
|
|
- const currentDistance = getTouchDistance(e.touches)
|
|
|
- const newScale = Math.min(
|
|
|
- MAX_ZOOM,
|
|
|
- Math.max(MIN_ZOOM, touchStartScale * (currentDistance / touchStartDistance)),
|
|
|
- )
|
|
|
- const ratio = newScale / touchStartScale
|
|
|
- panX.value = pinchCenterX - (pinchCenterX - touchStartPanX) * ratio
|
|
|
- panY.value = pinchCenterY - (pinchCenterY - touchStartPanY) * ratio
|
|
|
- zoomScale.value = newScale
|
|
|
- } else if (isPanning && e.touches.length === 1) {
|
|
|
- e.preventDefault()
|
|
|
- panX.value = touchStartPanX + (e.touches[0].clientX - singleTouchStartX)
|
|
|
- panY.value = touchStartPanY + (e.touches[0].clientY - singleTouchStartY)
|
|
|
- } else if (isScrolling && e.touches.length === 1) {
|
|
|
- e.preventDefault()
|
|
|
- const y = e.touches[0].clientY
|
|
|
- if (Math.abs(y - touchStartClientY) > 10) {
|
|
|
- lastTapTime = 0
|
|
|
- }
|
|
|
- const now = Date.now()
|
|
|
- const dt = now - lastTouchTime
|
|
|
- if (dt > 0) {
|
|
|
- scrollVelocity = ((lastTouchY - y) / dt) * 16
|
|
|
- }
|
|
|
- lastTouchY = y
|
|
|
- lastTouchTime = now
|
|
|
- panY.value += y - scrollMoveStartY
|
|
|
- scrollMoveStartY = y
|
|
|
- clampScroll()
|
|
|
+ const sysInfo = uni.getSystemInfoSync()
|
|
|
+ const h = props.viewerHeight
|
|
|
+ let height = sysInfo.windowHeight
|
|
|
+ if (h.endsWith('vh')) {
|
|
|
+ height = (parseFloat(h) / 100) * sysInfo.windowHeight
|
|
|
+ } else if (h.endsWith('px')) {
|
|
|
+ height = parseFloat(h)
|
|
|
}
|
|
|
+ return { width: sysInfo.windowWidth, height }
|
|
|
}
|
|
|
|
|
|
-function onTouchEnd(e: TouchEvent) {
|
|
|
- if (e.touches.length === 0) {
|
|
|
- if (isScrolling) {
|
|
|
- isScrolling = false
|
|
|
- startMomentumScroll()
|
|
|
- }
|
|
|
- isPinching = false
|
|
|
- isPanning = false
|
|
|
- if (zoomScale.value >= 0.95 && zoomScale.value < 1.05) {
|
|
|
- zoomScale.value = 1
|
|
|
- panX.value = 0
|
|
|
- clampScroll()
|
|
|
- }
|
|
|
- } else if (e.touches.length === 1 && isPinching) {
|
|
|
- isPinching = false
|
|
|
- if (zoomScale.value > 1.01) {
|
|
|
- isPanning = true
|
|
|
- singleTouchStartX = e.touches[0].clientX
|
|
|
- singleTouchStartY = e.touches[0].clientY
|
|
|
- touchStartPanX = panX.value
|
|
|
- touchStartPanY = panY.value
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+const applyFitScale = async () => {
|
|
|
+ if (!contentReady.value) return
|
|
|
|
|
|
-function startMomentumScroll() {
|
|
|
- const friction = 0.95
|
|
|
- function step() {
|
|
|
- if (Math.abs(scrollVelocity) < 0.5) return
|
|
|
- panY.value += scrollVelocity
|
|
|
- scrollVelocity *= friction
|
|
|
- clampScroll()
|
|
|
- momentumAnimId = requestAnimationFrame(step)
|
|
|
- }
|
|
|
- momentumAnimId = requestAnimationFrame(step)
|
|
|
-}
|
|
|
+ const { width: containerWidth, height: containerHeight } = await getContainerSize()
|
|
|
|
|
|
-function onWheel(e: WheelEvent) {
|
|
|
- e.preventDefault()
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (!viewport) return
|
|
|
- const rect = viewport.getBoundingClientRect()
|
|
|
+ const scaleByHeight = containerHeight > 0 && firstPageHeight.value > 0
|
|
|
+ ? containerHeight / firstPageHeight.value
|
|
|
+ : 1
|
|
|
+ const scaleByWidth = containerWidth > 0 && contentWidth.value > 0
|
|
|
+ ? containerWidth / contentWidth.value
|
|
|
+ : 1
|
|
|
|
|
|
- if (e.ctrlKey || e.metaKey) {
|
|
|
- const delta = -e.deltaY
|
|
|
- const factor = delta > 0 ? 1.1 : 1 / 1.1
|
|
|
- const newScale = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoomScale.value * factor))
|
|
|
- const cx = e.clientX - rect.left
|
|
|
- const cy = e.clientY - rect.top
|
|
|
- const ratio = newScale / zoomScale.value
|
|
|
- panX.value = cx - (cx - panX.value) * ratio
|
|
|
- panY.value = cy - (cy - panY.value) * ratio
|
|
|
- zoomScale.value = newScale
|
|
|
- if (newScale <= 1.01) {
|
|
|
- panX.value = 0
|
|
|
- clampScroll()
|
|
|
- }
|
|
|
- } else {
|
|
|
- if (zoomScale.value > 1.01) {
|
|
|
- panY.value -= e.deltaY
|
|
|
- panX.value -= e.deltaX
|
|
|
- } else {
|
|
|
- panY.value -= e.deltaY
|
|
|
- clampScroll()
|
|
|
- }
|
|
|
- }
|
|
|
+ fitScale.value = Math.min(scaleByHeight, scaleByWidth)
|
|
|
+ currentScale.value = fitScale.value
|
|
|
}
|
|
|
|
|
|
-function applyZoom(newScale: number, cx: number, cy: number) {
|
|
|
- const ratio = newScale / zoomScale.value
|
|
|
- panX.value = cx - (cx - panX.value) * ratio
|
|
|
- panY.value = cy - (cy - panY.value) * ratio
|
|
|
- zoomScale.value = newScale
|
|
|
- if (newScale <= 1.01) {
|
|
|
- panX.value = 0
|
|
|
- clampScroll()
|
|
|
+// #ifdef H5
|
|
|
+const calculateContentSize = async () => {
|
|
|
+ if (!pdfDoc) return
|
|
|
+ const containerWidth = uni.getSystemInfoSync().windowWidth - 32
|
|
|
+ let totalHeight = 0
|
|
|
+ for (let i = 1; i <= pdfDoc.numPages; i++) {
|
|
|
+ const page = await pdfDoc.getPage(i)
|
|
|
+ const unscaledViewport = page.getViewport({ scale: 1 })
|
|
|
+ const scale = containerWidth / unscaledViewport.width
|
|
|
+ const viewport = page.getViewport({ scale })
|
|
|
+ totalHeight += viewport.height
|
|
|
+ if (i === 1) {
|
|
|
+ firstPageHeight.value = viewport.height
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
-
|
|
|
-function zoomIn() {
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (!viewport) return
|
|
|
- const rect = viewport.getBoundingClientRect()
|
|
|
- applyZoom(Math.min(MAX_ZOOM, zoomScale.value + ZOOM_STEP), rect.width / 2, rect.height / 2)
|
|
|
-}
|
|
|
-
|
|
|
-function zoomOut() {
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (!viewport) return
|
|
|
- const rect = viewport.getBoundingClientRect()
|
|
|
- applyZoom(Math.max(MIN_ZOOM, zoomScale.value - ZOOM_STEP), rect.width / 2, rect.height / 2)
|
|
|
-}
|
|
|
-
|
|
|
-function resetZoom() {
|
|
|
- zoomScale.value = 1
|
|
|
- panX.value = 0
|
|
|
- panY.value = 0
|
|
|
+ contentWidth.value = containerWidth
|
|
|
+ contentHeight.value = totalHeight
|
|
|
+ contentReady.value = true
|
|
|
}
|
|
|
|
|
|
const renderAllPages = async () => {
|
|
|
@@ -353,7 +198,6 @@ const renderAllPages = async () => {
|
|
|
|
|
|
const containerWidth = uni.getSystemInfoSync().windowWidth - 32
|
|
|
const dpr = window.devicePixelRatio || 1
|
|
|
- let totalHeight = 0
|
|
|
|
|
|
for (let i = 1; i <= pdfDoc.numPages; i++) {
|
|
|
const page = await pdfDoc.getPage(i)
|
|
|
@@ -373,16 +217,52 @@ const renderAllPages = async () => {
|
|
|
await page.render({ canvasContext: context!, viewport }).promise
|
|
|
|
|
|
container.appendChild(canvas)
|
|
|
- totalHeight += viewport.height
|
|
|
}
|
|
|
-
|
|
|
- contentHeight = totalHeight
|
|
|
} catch (error) {
|
|
|
console.error('PDF页面渲染失败:', error)
|
|
|
errorMsg.value = 'PDF页面渲染失败'
|
|
|
emit('error', 'PDF页面渲染失败')
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+let pinchStartDistance = 0
|
|
|
+let pinchStartScale = 1
|
|
|
+
|
|
|
+function getTouchDistance(touches: TouchList): number {
|
|
|
+ const dx = touches[0].clientX - touches[1].clientX
|
|
|
+ const dy = touches[0].clientY - touches[1].clientY
|
|
|
+ return Math.sqrt(dx * dx + dy * dy)
|
|
|
+}
|
|
|
+
|
|
|
+function handleTouchStart(e: TouchEvent) {
|
|
|
+ if (e.touches.length === 2) {
|
|
|
+ e.preventDefault()
|
|
|
+ pinchStartDistance = getTouchDistance(e.touches)
|
|
|
+ pinchStartScale = currentScale.value
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleTouchMove(e: TouchEvent) {
|
|
|
+ if (e.touches.length === 2) {
|
|
|
+ e.preventDefault()
|
|
|
+ const currentDistance = getTouchDistance(e.touches)
|
|
|
+ const newScale = Math.min(
|
|
|
+ MAX_ZOOM,
|
|
|
+ Math.max(scaleMin.value, pinchStartScale * (currentDistance / pinchStartDistance)),
|
|
|
+ )
|
|
|
+ currentScale.value = newScale
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleWheel(e: WheelEvent) {
|
|
|
+ if (e.ctrlKey || e.metaKey) {
|
|
|
+ e.preventDefault()
|
|
|
+ const delta = -e.deltaY
|
|
|
+ const factor = delta > 0 ? 1.1 : 1 / 1.1
|
|
|
+ const newScale = Math.min(MAX_ZOOM, Math.max(scaleMin.value, currentScale.value * factor))
|
|
|
+ currentScale.value = newScale
|
|
|
+ }
|
|
|
+}
|
|
|
// #endif
|
|
|
|
|
|
const loadPDFFromArrayBuffer = async (data: ArrayBuffer) => {
|
|
|
@@ -401,6 +281,9 @@ const loadPDFFromArrayBuffer = async (data: ArrayBuffer) => {
|
|
|
pdfDoc = pdf
|
|
|
pageCount.value = pdf.numPages
|
|
|
|
|
|
+ await calculateContentSize()
|
|
|
+ await applyFitScale()
|
|
|
+
|
|
|
loading.value = false
|
|
|
emit('loaded', pdf.numPages)
|
|
|
|
|
|
@@ -418,19 +301,27 @@ const loadPDFFromArrayBuffer = async (data: ArrayBuffer) => {
|
|
|
const base64 = btoa(binaryString)
|
|
|
imageSrc.value = `data:application/pdf;base64,${base64}`
|
|
|
|
|
|
+ const screenWidth = uni.getSystemInfoSync().windowWidth - 32
|
|
|
+ contentWidth.value = screenWidth
|
|
|
+
|
|
|
uni.getImageInfo({
|
|
|
- src: `data:application/pdf;base64,${base64}`,
|
|
|
- success: (imageInfo) => {
|
|
|
- const screenWidth = uni.getSystemInfoSync().windowWidth - 32
|
|
|
- imageWidth.value = screenWidth
|
|
|
- imageHeight.value = (screenWidth * imageInfo.height) / imageInfo.width
|
|
|
+ src: imageSrc.value,
|
|
|
+ success: async (imageInfo) => {
|
|
|
+ contentHeight.value = (screenWidth * imageInfo.height) / imageInfo.width
|
|
|
+ firstPageHeight.value = contentHeight.value
|
|
|
+ contentReady.value = true
|
|
|
+ await applyFitScale()
|
|
|
pageCount.value = 1
|
|
|
loading.value = false
|
|
|
emit('loaded', 1)
|
|
|
},
|
|
|
- fail: () => {
|
|
|
- loading.value = false
|
|
|
+ fail: async () => {
|
|
|
+ contentHeight.value = Math.floor(screenWidth * 1.414)
|
|
|
+ firstPageHeight.value = contentHeight.value
|
|
|
+ contentReady.value = true
|
|
|
+ await applyFitScale()
|
|
|
pageCount.value = 1
|
|
|
+ loading.value = false
|
|
|
emit('loaded', 1)
|
|
|
},
|
|
|
})
|
|
|
@@ -453,15 +344,12 @@ const resetViewer = () => {
|
|
|
pageCount.value = 0
|
|
|
imageSrc.value = ''
|
|
|
errorMsg.value = ''
|
|
|
-
|
|
|
- // #ifdef H5
|
|
|
- zoomScale.value = 1
|
|
|
- panX.value = 0
|
|
|
- panY.value = 0
|
|
|
- contentHeight = 0
|
|
|
- scrollVelocity = 0
|
|
|
- cancelAnimationFrame(momentumAnimId)
|
|
|
- // #endif
|
|
|
+ contentReady.value = false
|
|
|
+ contentWidth.value = 0
|
|
|
+ contentHeight.value = 0
|
|
|
+ firstPageHeight.value = 0
|
|
|
+ fitScale.value = 1
|
|
|
+ currentScale.value = 1
|
|
|
}
|
|
|
|
|
|
watch(
|
|
|
@@ -489,16 +377,18 @@ watch(
|
|
|
},
|
|
|
)
|
|
|
|
|
|
+let scrollEl: HTMLElement | null = null
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
isMounted = true
|
|
|
|
|
|
// #ifdef H5
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (viewport) {
|
|
|
- viewport.addEventListener('touchstart', onTouchStart, { passive: false })
|
|
|
- viewport.addEventListener('touchmove', onTouchMove, { passive: false })
|
|
|
- viewport.addEventListener('touchend', onTouchEnd, { passive: false })
|
|
|
- viewport.addEventListener('wheel', onWheel, { passive: false })
|
|
|
+ await nextTick()
|
|
|
+ scrollEl = document.querySelector('.pdf-content-area')
|
|
|
+ if (scrollEl) {
|
|
|
+ scrollEl.addEventListener('touchstart', handleTouchStart, { passive: false })
|
|
|
+ scrollEl.addEventListener('touchmove', handleTouchMove, { passive: false })
|
|
|
+ scrollEl.addEventListener('wheel', handleWheel, { passive: false })
|
|
|
}
|
|
|
// #endif
|
|
|
|
|
|
@@ -515,13 +405,11 @@ onUnmounted(() => {
|
|
|
isMounted = false
|
|
|
|
|
|
// #ifdef H5
|
|
|
- cancelAnimationFrame(momentumAnimId)
|
|
|
- const viewport = document.getElementById(viewportId.value)
|
|
|
- if (viewport) {
|
|
|
- viewport.removeEventListener('touchstart', onTouchStart)
|
|
|
- viewport.removeEventListener('touchmove', onTouchMove)
|
|
|
- viewport.removeEventListener('touchend', onTouchEnd)
|
|
|
- viewport.removeEventListener('wheel', onWheel)
|
|
|
+ if (scrollEl) {
|
|
|
+ scrollEl.removeEventListener('touchstart', handleTouchStart)
|
|
|
+ scrollEl.removeEventListener('touchmove', handleTouchMove)
|
|
|
+ scrollEl.removeEventListener('wheel', handleWheel)
|
|
|
+ scrollEl = null
|
|
|
}
|
|
|
// #endif
|
|
|
|
|
|
@@ -569,22 +457,20 @@ defineExpose({
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/* #ifdef H5 */
|
|
|
-.pdf-viewport {
|
|
|
- position: relative;
|
|
|
+.pdf-content-area {
|
|
|
width: 100%;
|
|
|
- overflow: hidden;
|
|
|
- touch-action: none;
|
|
|
- user-select: none;
|
|
|
- -webkit-user-select: none;
|
|
|
background-color: #f5f5f5;
|
|
|
border-radius: 8px;
|
|
|
}
|
|
|
|
|
|
-.pdf-transform-layer {
|
|
|
+.pdf-scale-layer {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
will-change: transform;
|
|
|
}
|
|
|
|
|
|
+/* #ifdef H5 */
|
|
|
.pdf-container {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
@@ -595,6 +481,12 @@ defineExpose({
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/* #endif */
|
|
|
+
|
|
|
+.pdf-image {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
.pdf-zoom-bar {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -602,7 +494,7 @@ defineExpose({
|
|
|
gap: 8px;
|
|
|
padding: 6px 12px;
|
|
|
margin: 8px auto 0;
|
|
|
- background-color: rgba(0, 0, 0, 0.65);
|
|
|
+ background-color: rgba(0, 0, 0, 0.55);
|
|
|
border-radius: 20px;
|
|
|
width: fit-content;
|
|
|
}
|
|
|
@@ -615,7 +507,6 @@ defineExpose({
|
|
|
height: 32px;
|
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
|
border-radius: 50%;
|
|
|
- cursor: pointer;
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
|
&:active {
|
|
|
@@ -631,59 +522,11 @@ defineExpose({
|
|
|
|
|
|
.zoom-btn-reset {
|
|
|
width: auto;
|
|
|
- padding: 0 10px;
|
|
|
+ padding: 0 8px;
|
|
|
border-radius: 12px;
|
|
|
- margin-left: 4px;
|
|
|
|
|
|
.zoom-btn-text {
|
|
|
- font-size: 12px;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.zoom-label {
|
|
|
- font-size: 13px;
|
|
|
- color: #fff;
|
|
|
- min-width: 40px;
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-/* #endif */
|
|
|
-
|
|
|
-/* #ifndef H5 */
|
|
|
-.pdf-image-area {
|
|
|
- width: 100%;
|
|
|
- background-color: #f5f5f5;
|
|
|
- border-radius: 8px;
|
|
|
- overflow: hidden;
|
|
|
-}
|
|
|
-
|
|
|
-.pdf-movable-area {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
-}
|
|
|
-
|
|
|
-.pdf-movable-view {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-
|
|
|
-.pdf-image {
|
|
|
- display: block;
|
|
|
-}
|
|
|
-/* #endif */
|
|
|
-
|
|
|
-.pdf-placeholder {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- width: 100%;
|
|
|
- height: 200px;
|
|
|
- background-color: #f5f5f5;
|
|
|
- border-radius: 8px;
|
|
|
-
|
|
|
- .placeholder-text {
|
|
|
- font-size: 14px;
|
|
|
- color: #999;
|
|
|
+ font-size: 11px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|