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

原理

使用 transformOrigin: ${state.x}px ${state.y}px 0
重新设置偏移中心点

待解决问题

缩放后进行拖拽会使计算的中心点位置与双指中心位置存在偏差,如果网友有解决这个问题,请贴代码到我的评论区,谢谢。

直接贴出代码

这里有关pdf的代码可以自行去掉

<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"><divv-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=csdn_github_acceleratorimport structuredClone from 'core-js-pure/actual/structured-clone';// 解决 TypeError: key.split(...).at is not a function// https://github.com/wojtekmaj/react-pdf/issues/1465import '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];// 去除pxy = 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%95fetch(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 ratiolet 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页面的初始视口,缩放比例为1const 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 || {};if (relName) {// 设置 uniapp 当前页面标题uni.setNavigationBarTitle({title: relName,});}if (previewUrl) {loadFile(previewUrl);// nextTick(() => {//   initPinchZoom();// });} else {showFailToast({message: '预览地址不存在',duration: 0,});}});
</script>
<style scoped lang="less">uni-page-body {overflow-y: scroll;}.content-box {width: 100%;height: 100%;overflow: hidden;position: relative;// overflow-y: auto;}.movableView {border: 1px solid #ccc;box-sizing: border-box;}:deep(uni-movable-view) {width: 100%;height: auto;// overflow-y: auto;}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/38665.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LVGL实现字库的下载和使用

1 字库 字库的概念&#xff1a;相应文字或字符的合集。 点阵字库&#xff1a;按字库顺序排列的字符/汉字字模的合集。 LVGL中字库使用Unicode编码&#xff0c;Unicode 是全球文字统一编码。它把世界上的各种文字的每一个字符指定唯一编码&#xff0c;实现跨语种、跨平台的应…

深入学习 Kafka(3)- SpringBoot 整合 Kafka

1. 引入 jar <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId> </dependency>2. yml 配置 spring:kafka:bootstrap-servers: localhost:9092,localhost:9093,localhost:9094 # kafka 服…

大数据开发助手:Coze平台上一款致力于高效解决大数据开发问题的智能Bot!

大数据开发助手&#xff1a;Coze平台上一款致力于高效解决大数据开发问题的智能Bot 核心技术揭秘1. **自然语言处理&#xff08;NLP&#xff09;**2. **知识图谱构建**3. **个性化推荐算法** 功能特色概览1. **即时问题解答**2. **最佳实践分享**3. **个性化学习路径**4. **社区…

哪个牌子的超声波清洗器好?精选四大超强超声波清洗机力荐

生活中戴眼镜的人群不在少数&#xff0c;然而要维持眼镜的干净却不得不每次都需要清洗&#xff0c;只是通过手洗的方式实在太慢并且容易操作不当让镜片磨损更加严重&#xff01;所以超声波清洗机就诞生了&#xff01;超声波清洗机能够轻松清洗机眼镜上面的油脂污渍&#xff0c;…

使用Java构建可伸缩的云原生应用架构

使用Java构建可伸缩的云原生应用架构 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 什么是云原生应用架构&#xff1f; 云原生应用架构是一种利用云计算平台…

synchronized 锁优化原理

目录 一、轻量级锁 二、锁膨胀 三、自旋优化 四、偏向锁 五、锁消除 一、轻量级锁 1. 会创建一个锁记录 Lock Record&#xff08;保存在线程栈中&#xff09;&#xff0c;尝试 CAS 修改 Mark Word 中的对象头&#xff0c;是一种乐观锁的思想&#xff0c;而不是将 Java 对…

【多线程开发 4】从源码学习LockSupport

从源码学习LockSupport 2024年6月30日 大家好啊&#xff0c;好久没写博客了&#xff0c;今天打算写一下&#xff0c;讲一下JUC里面LockSupport这个类。 这个是一个工具类&#xff0c;实际上也是为了线程通信开发的。它的源码比较短&#xff0c;也只引用了Unsafe一个类。所以…

机器学习——强化学习状态值函数V和动作值函数Q的个人思考

最近在回顾《西瓜书》的理论知识&#xff0c;回顾到最后一章——“强化学习”时对于值函数部分有些懵了&#xff0c;所以重新在网上查了一下&#xff0c;发现之前理解的&#xff0c;包括网上的大多数对于值函数的描述都过于学术化、公式化&#xff0c;不太能直观的理解值函数以…

SQL常用经典语句大全

SQL经典语句大全 一、基础 1、说明&#xff1a;创建数据库 CREATE DATABASE database-name 2、说明&#xff1a;删除数据库 drop database dbname 3、说明&#xff1a;备份sql server — 创建 备份数据的 device USE master EXEC sp_addumpdevice ‘disk’, ‘testBack’, ‘c:…

macos Automator自动操作 app, 创建自定义 应用程序 app 的方法

mac内置的这个 自动操作 automator 应用程序&#xff0c;可以帮助我们做很多的重复的工作&#xff0c;可以创建工作流&#xff0c; 可以录制并回放操作&#xff0c; 还可以帮助我们创建自定的应用程序&#xff0c;下面我们就以创建一个自定义启动参数的chrome.app为例&#xff…

C语言 求数列 S(n) = a + aa + aaa + …aa…a (n 个 a)的和

求数列S(n)aaaaaa…aa…a(n个a)之值&#xff0c;其中a是一个数字&#xff0c;n表示a的位数&#xff0c;n由键盘输入。例如222222222222222&#xff08;此时n5&#xff09; 这个程序读取用户输入的一个数字 a 和一个正整数 n&#xff0c;计算并输出数列 S(n) 的值。 #include …

cube-studio 开源一站式云原生机器学习/深度学习/大模型训练推理平台介绍

全栈工程师开发手册 &#xff08;作者&#xff1a;栾鹏&#xff09; 一站式云原生机器学习平台 前言 开源地址&#xff1a;https://github.com/tencentmusic/cube-studio cube studio 腾讯开源的国内最热门的一站式机器学习mlops/大模型训练平台&#xff0c;支持多租户&…

绘图黑系配色

随便看了几篇小论文&#xff0c;里面的黑配色挺喜欢的&#xff0c;虽然平时SCI系配色用的多&#xff0c;但看到纯黑配色与黑加蓝配色&#xff0c;那就是我最心上的最优style。

一文了解IP地址冲突的起因与解决方案

IP 地址冲突是困扰网络管理员影响网络的正常运行的常见因素。深入理解并有效解决 IP 地址冲突故障对于维护网络的高效稳定运行具有重要意义。 一、IP 地址冲突的原因 &#xff08;一&#xff09;人为配置错误 网络用户在手动配置 IP 地址时&#xff0c;对网络配置了解不多用户…

OpenGL3.3_C++_Windows(23)

伽ga马校正 物理亮度 光子数量 线性空间&#xff1a;光子数(亮度&#xff09;和颜色值的线性关系人眼感知的亮度&#xff1a;对比较暗的颜色变化更敏感&#xff0c;感知亮度基于人的感觉非线性空间&#xff1a;光子数(亮度&#xff09;和 颜色值^2.2&#xff0c;恰好符合屏幕…

一些项目的说明

这是一个管理系统&#xff0c;比较缝合&#xff0c;可能想到什么有用的功能就写&#xff0c;也没太多的针对性&#xff0c;需要的功能可以自己拆解去用&#xff0c;也欢迎往上添加新功能。 业余玩家&#xff0c;代码有空就写。 项目相关的业务设计写在CSDN博客里。用户IDYuboc…

为什么我学个 JAVA 就已经耗尽所有而有些人还能同时学习多门语言

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「JAVA的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;我的入门语言是C&#xff0c…

Android InputChannel连接

InputChannel是InputDispatcher 和应用程序 (InputTarget) 的通讯桥梁&#xff0c;InputDispatcher 通知应用程序有输入事件&#xff0c;通过InputChannel中的socket进行通信。 连接InputDispatcher和窗口 WinodwManagerService:addwindow: WMS 添加窗口时&#xff0c;会创建…

互联网场景下人脸服务基线方案总结

1.简介 1.1目的 在过去的一段时间里&#xff0c;因为听见业务对人脸服务方案的需求&#xff0c;针对网络视频中关键人物定位的检索任务&#xff0c;完成了基于互联网场景的人脸基线服务的构建。本文档是对当前基线服务以后之后解决方案的优化进行总结。 1.2范围 本文档描述的人…

c++读取文件时出现中文乱码

原因&#xff1a;UTF-8格式不支持汉字编码 解决&#xff1a;改成ANSI&#xff0c;因为ANSI编码支持汉字编码