uniapp使用 movable-area movable-view 实现按双指中心位置缩放及拖拽功能

<template> <div style="width: 100%; height: 100%; position: relative; overflow-y: auto"> <movable-area id="pdf-view" ref="pdfView" class="content-box" :scale-area="true"> <movable-view :out-of-bounds="true" :style="{ transformOrigin: `${state.x}px ${state.y}px 0`, }" class="movableView" direction="all" :scale="true" scale-min="1" damping="1" ref="movableViewRef" friction="2" :x="0" :y="0" :scale-max="4" :animation="false" @scale="pdfScaleChange" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @change="onScroll" @click="onViewClick" > <div :style="{ width: '15px', height: '15px', background: 'gold', position: 'absolute', left: state.x + 'px', top: state.y + 'px', transform: `translate(-50%,-50%)`, zIndex: 100, }" > </div> <div ref="pdfViewContainer"> <div v-for="pageNumber in state.pdfPages" :key="pageNumber" :ref="(el) => (pageRefs[pageNumber - 1] = el)" @touchstart="onPdfClick($event, pageNumber)" ></div> </div> </movable-view> </movable-area> </div> <je-loading v-show="loading" /> </template> <script setup> //解决 structuredClone // https://developer.mozilla.org/en-US/docs/Web/API/structuredClone#browser_compatibility // https://gitcode.com/zloirock/core-js/overview?utm_source=****_github_accelerator import structuredClone from 'core-js-pure/actual/structured-clone'; // 解决 TypeError: key.split(...).at is not a function // https://github.com/wojtekmaj/react-pdf/issues/1465 import 'core-js/features/array/at'; window.structuredClone = structuredClone; // if (!Array.prototype.at) { // Array.prototype.at = function (index) { // if (index < 0) { // index = this.length + index; // } // if (index >= 0 && index < this.length) { // return this[index]; // } // return undefined; // }; // } import * as pdfjsWorker from 'pdfjs-dist/lib/pdf.worker.js'; // 解决 pdfjsWorker 未定义 window.pdfjsWorker = pdfjsWorker; import 'pdfjs-dist/web/pdf_viewer.css'; import * as PDF from 'pdfjs-dist'; // import * as PDF from 'pdfjs-dist/build/pdf.js'; import { useRoute } from 'vue-router'; import { ref, reactive, onMounted, nextTick, defineProps } from 'vue'; import { showFailToast } from 'vant'; import { onPageScroll } from '@dcloudio/uni-app'; const route = useRoute(); const props = defineProps({ src: { type: String, default: '', }, }); const pdfViewContainer = ref(null); const movableViewRef = ref(null); const pdfView = ref(null); const pageRefs = ref([]); const loading = ref(false); const state = reactive({ // 总页数 pdfPages: 1, pdfPageList: [], //有效页码列表 // 页面缩放 pdfScale: 1, x: 0, y: 0, translateX: 0, translateY: 0, sCenterX: 0, sCenterY: 0, startTouches: [], // 初始触摸列表 endTouches: [], // 结束触摸列表 scrollTop: 0, scrollPage: 1, currentHeight: 0, currentWidth: 0, xData: 0, }); const cData = reactive({ move: {}, click: {}, }); let pdfDoc = null; const onPdfClick = (e, page) => { // state.scrollTop = e.clientY; state.scrollPage = page; state.currentHeight = pageRefs.value[state.scrollPage - 1].clientHeight; state.currentWidth = pageRefs.value[state.scrollPage - 1].clientWidth; // console.log('height', pageRefs.value[state.scrollPage - 1]); }; const pdfScaleChange = (e) => { console.log('放大', e); state.pdfScale = e.detail.scale; state.xData = e.detail.x; state.scrollTop = e.detail.y; getTranslate(); }; const touchStart = (event) => { state.startTouches = event.touches; }; const onViewClick = (event) => { cData.click = event.touches[0]; }; const touchMove = (event) => { cData.move = event.touches[0]; state.endTouches = event.touches; getTranslate(); // 判断是否为双指缩放 if (state.endTouches.length === 2) { let beforeDistance = calculateCenter(state.startTouches[0], state.startTouches[1]); // 监听移动,更新中心点坐标 let moveTouch1 = state.startTouches.find((t) => t.identifier === event.touches[0].identifier); let moveTouch2 = state.startTouches.find((t) => t.identifier === event.touches[1].identifier); if (moveTouch1 && moveTouch2) { let centerX = (moveTouch1.clientX + moveTouch2.clientX) / 2; let centerY = (moveTouch1.clientY + moveTouch2.clientY) / 2; state.sCenterX = centerX; state.sCenterY = centerY; state.y = centerY - state.translateY; state.x = centerX - state.translateX; // 边界判断 if (state.x < 0) { state.x = 0; } else if (state.x > state.currentWidth) { state.x = state.currentWidth; } let offsetHeight = movableViewRef.value.$el.offsetHeight; if (state.y < 0) { state.y = 0; } else if (state.y > offsetHeight) { state.y = offsetHeight; } } } }; const touchEnd = (event) => { if (event.touches.length < 2) { state.startTouches = []; state.endTouches = []; } }; const calculateCenter = (startTouches, endTouches) => { let xDistance = startTouches?.clientX - endTouches?.clientX; let yDistance = startTouches?.clientY - endTouches?.clientY; return Math.sqrt(xDistance * xDistance + yDistance * yDistance); }; onPageScroll((e) => { // 页面滚动时会触发 // this.scrollTop = e.scrollTop; // 更新滚动位置 // console.log('页面滚动', e.scrollTop); }); // 获取偏移量 const getTranslate = () => { let styleM = movableViewRef.value.$el.style; let transform = styleM.transform; // 获取y轴偏移量 let y = transform.match(/translateY\(([^)]+)\)/)[1]; // 去除px y = y.replace('px', ''); // state.translateY = Math.abs(Number(y) || 0); state.translateY = Number(y); let x = transform.match(/translateX\(([^)]+)\)/)[1]; x = x.replace('px', ''); // state.translateX = Math.abs(Number(x) || 0); state.translateX = Number(x); }; // 设置中心点 const setCenter = () => { let offsetHeight = movableViewRef.value.$el.offsetHeight; state.x = state.sCenterX - state.translateX; state.y = state.sCenterY - state.translateY; if (state.x < 0) { state.x = 0; } else if (state.x > state.currentWidth) { state.x = state.currentWidth; } if (state.y < 0) { state.y = 0; } else if (state.y > offsetHeight) { state.y = offsetHeight; } }; const onScroll = (e) => { // 页面滚动时会触发 state.xData = e.detail.x; state.scrollTop = e.detail.y; getTranslate(); setCenter(); }; async function loadFile(url) { // { // url, // cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/', // cMapPacked: true, // } loading.value = true; // 设置配置选项 手势缩放 PDF?.DefaultViewerConfig?.set({ handToolOnDblClick: true, mouseWheelScale: true, }); let arrayBufferPDF; // // if (navigator.userAgent.indexOf('QQ')) { // const pdfData = await fetch(url); // arrayBufferPDF = await pdfData.arrayBuffer(); // } // 解决部分机型浏览器 undefined is not an object(evaluating 'response.body.getReader') // https://www.qingcong.tech/technology/javascript/a-pdfjs-bug-in-qq.html#%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95 fetch(url).then(async (pdfData) => { console.log('pdfData', pdfData); if (!pdfData.ok) { loading.value = false; showFailToast({ message: '预览地址不存在或已失效', duration: 0, }); // window.JE.alert('预览地址不存在', 'error'); return; } arrayBufferPDF = await pdfData.arrayBuffer(); const loadingTask = arrayBufferPDF ? PDF.getDocument({ data: arrayBufferPDF }) : PDF.getDocument(url); loadingTask.promise.then((pdf) => { pdfDoc = pdf; // 获取pdf文件总页数 state.pdfPages = pdf.numPages; nextTick(() => { for (let i = 0; i < state.pdfPages; i++) { renderPage(i + 1); // 从第一页开始渲染 } }); }); }); } function renderPage(num) { pdfDoc.getPage(num).then((page) => { // 获取当前页面对应的DOM容器元素 const container = pageRefs.value[num - 1]; // 创建一个新的canvas元素 const canvas = document.createElement('canvas'); // 获取canvas的2D渲染上下文 const ctx = canvas.getContext('2d'); // 获取设备像素比 let devicePixelRatio = window.devicePixelRatio || 1; // 获取画布的backing store ratio let backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; // 获取pdfViewContainer元素的宽度 const pdfWrapperElWidth = pdfViewContainer.value.clientWidth || pdfViewContainer.value.offsetWidth || pdfViewContainer.value.style.width; // 获取PDF页面的初始视口,缩放比例为1 const intialisedViewport = page.getViewport({ scale: 1 }); // 计算缩放比例,使PDF页面宽度与容器宽度一致 const scale = pdfWrapperElWidth / intialisedViewport.width; // 计算设备像素比与backing store ratio的比值 let ratio = devicePixelRatio / backingStoreRatio; // 根据缩放比例获取PDF页面的视口 const viewport = page.getViewport({ scale }); // 设置canvas的宽度为容器宽度乘以ratio,确保高分辨率下的清晰度 canvas.width = pdfWrapperElWidth * ratio; // 设置canvas的高度为视口高度乘以ratio,确保高分辨率下的清晰度 canvas.height = viewport.height * ratio; // 设置canvas的样式宽度为100%,与容器宽度一致 canvas.style.width = '100%'; // 设置canvas的样式高度为auto,根据宽度自适应 canvas.style.height = 'auto'; // 缩放画布的渲染上下文,根据ratio进行缩放,确保在高分辨率下绘制的清晰度 ctx.scale(ratio, ratio); const renderContext = { canvasContext: ctx, viewport, }; // 设置页面容器的高度为视口高度 container.style.height = `${viewport.height}px`; page .render(renderContext) .promise.then(() => { state.pdfPageList.push(num); // 如果 container 存在 canvas元素 覆盖canvas元素 container?.firstChild && container.removeChild(container.firstChild); container && container.appendChild(canvas); }) .finally(() => { if (num === state.pdfPages) { loading.value = false; } }); }); } onMounted(() => { const file = route.query.file && JSON.parse(decodeURIComponent(route.query.file)); const { relName, previewUrl } = file || {}
上一篇:微软开放OCR提取安卓手机照片文本功能:便利与挑战并存


下一篇:农学VR虚拟仿真情景实训教学