LongScreenshot代码流程概述

本文介绍了长截图的实现过程,从静态注册ScreenShotReceiver开始,启动ScreenshotService并动态注册接收器。在服务中初始化窗口并添加监听器,监听滚动和保存单个截屏。NextScreenThread用于滚动和计算参数,主线程负责滚动。SavePictureThread负责拼接所有截图。通过StopSelfBySelf()停止截图,isScrollToBottom()判断是否滚动到底。消息处理包括显示边框、禁用滚动和捕获屏幕等。点击下一步时,判断是否需要滚动,滚动前先截图,滚动后对比图片以确定是否到达底部。保存图片使用StorageHelper,考虑了SD卡存储权限问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


首先静态注册ScreenShotReceiver,通过这个receiver启动ScreenShotService,在ScreenshotService中动态注册上图中3个Receiver。

先通过在ScreenShotService中初始化窗口,为View添加监听器。事件的处理就从监听器入手。主要是滚动和保存单个截屏,最后拼接成一个长截屏图片。

保存单个截屏和计算滚动的一些参数的工作主要由NextScreenThread子线程去完成,滚动由NextScreenThread发消息给mUpdateScreenHanler在主线程中完成。

而最终保存长截图的工作有SavePictureThread子线程负责,其中就包括完成所有截屏的拼接工作。



停止screenshot:stopSelfBySelf();

判断是否到底了:isScrollToBottom();

mHandler中处理按下nextScreen后的三种消息:

1.MESSAGE_SHOW_DRAG_UP_VIEW---滚屏完后,显示出边框和bottomview

2.MESSAGE_DISABLE_NEXT_SCREEN---滚动后发现没有下一屏了。

3.MESSAGE_CAPTURE_SCREEN) ---scrollScreen()执行完了,即滚完屏幕了


点击nextScreen的处理流程:

1.判断是否需要滚动,需要就执行第2步。不需要就不执行第2步。

2.若是需要滚动,则先然所有外加边框和按钮都隐藏了。

(前面两步都是preNextScreen()做的,下面的步骤在NextScreenTread中完成)

3.在滚动前,先使用captrueScreen()截取图片。

<!-- pages/webview/webview.vue --> <template> <view class="webview-container"> <cover-view class="webview-wrapper"> <web-view :src="webviewPath" id="targetWebview"></web-view> </cover-view> <cover-view class="bottom-action-area"> <cover-view class="capture-btn" @click="captureWebview"> <cover-view class="btn-text">立即取证</cover-view> </cover-view> </cover-view> <!-- 预览组件 --> <cover-view v-if="showPreview" class="custom-preview"> <cover-view class="preview-header"> <cover-view class="title-container"> <cover-view class="preview-title">预览界面</cover-view> </cover-view> </cover-view> <cover-image :src="screenshotPath" class="preview-image" mode="aspectFit" @error="handleImageError" ></cover-image > <cover-view class="action-buttons"> <cover-view class="action-btn cancel-btn" @click="handleAction('放弃')">放弃</cover-view> <cover-view class="action-btn confirm-btn" @click="handleAction('固化')">固化</cover-view> </cover-view> <cover-view> </cover-view> </cover-view> <!-- 加载提示 --> <cover-view v-if="isLoading" class="loading-overlay"> <cover-view class="loading-spinner"></cover-view> <cover-view class="loading-text">截图中...</cover-view> </cover-view> </view> </template> <script setup lang="ts"> import { onLoad, onReady } from '@dcloudio/uni-app'; import html2canvas from 'html2canvas'; import { ref } from 'vue'; declare const plus: any; const webviewPath = ref(''); const ws = ref<any>(null); const screenshotPath = ref(''); const showPreview = ref(false); const isLoading = ref(false); const platform = ref(''); onLoad((options: any) => { if (options.url) { webviewPath.value = decodeURIComponent(options.url); } // 获取当前平台 const systemInfo = uni.getSystemInfoSync(); platform.value = systemInfo.platform; }); onReady(() => { const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentWebview = currentPage.$getAppWebview(); setTimeout(() => { if (currentWebview.children().length > 0) { const wv = currentWebview.children()[0]; ws.value = wv; wv.setStyle({ height: 'auto' }); } }, 1000); }); const captureWebview = async () => { if (!ws.value) { uni.showToast({ title: 'WebView未准备好', icon: 'none' }); return; } // #ifdef APP-PLUS const bitmap = new plus.nativeObj.Bitmap('screenshot') const fileName = `${Date.now()}.jpg` // const savePath = `_doc/${fileName}` const rand = Math.floor(Math.random() * 10000) const saveUrl = '_doc/' + rand + 'order.jpg' // 绘制页面截图 ws.value.draw(bitmap, () => { // 保存图片 bitmap.save( saveUrl, { format: 'jpg', quality: 90 }, (res:any) => { // 将本地路径转换为可以展示的路径 const previewPath = plus.io.convertLocalFileSystemURL(res.target) screenshotPath.value = previewPath console.log('截图保存成功,预览路径:', previewPath) // 延迟展示,确保加载完成 setTimeout(() => { showPreview.value = true isLoading.value = false }, 300) bitmap.clear() // 清理 bitmap 占用资源 }, (err:any) => { console.error('保存失败:', err) isLoading.value = false uni.showToast({ title: '保存失败: ' + err.message, icon: 'none', }) bitmap.clear() } ) }, (err:any) => { console.error('截图失败:', err) isLoading.value = false uni.showToast({ title: '截图失败: ' + err.message, icon: 'none', }) }) // #endif // #ifdef APP-H5 html2canvas(document.querySelector('.webview-wrapper')).then((canvas) => { const dataUrl = canvas.toDataURL('image/jpeg') screenshotPath.value = dataUrl showPreview.value = true }) // #endif } const closePreview = () => { showPreview.value = false; } const handleAction = (action: string) => { switch(action) { case '放弃': closePreview(); break; case '固化': uni.saveImageToPhotosAlbum({ filePath: screenshotPath.value, success: () => { uni.showToast({ title: '证据已固化保存' }); closePreview(); }, fail: (err) => { uni.showToast({ title: '固化失败: ' + err.errMsg, icon: 'none' }); } }); break; } } const handleImageError = (e: any) => { console.error('图片加载错误:', e); uni.showToast({ title: '图片加载错误', icon: 'none' }); } </script> <style> .webview-container { position: relative; width: 100%; height: 100vh; overflow: hidden; } .webview-wrapper { height: calc(100vh - 120rpx); width: 100%; overflow: hidden; } .bottom-action-area { position: fixed; bottom: 0; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); display: flex; justify-content: center; align-items: center; } .capture-btn { width: 100%; height: 90rpx; background-color: #007aff; border-radius: 45rpx; box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2); display: flex; align-items: center; justify-content: center; } .btn-text { color: #ffffff; font-size: 32rpx; font-weight: 500; } /* 优化预览组件 */ .custom-preview { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; background-color: rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; /* 垂直排列子元素 */ align-items: center; /* 关键修改:去掉顶部内边距,只保留底部内边距 */ padding: 0 0 40rpx 0; box-sizing: border-box; } .preview-header { width: 100%; height: 200rpx; /* 增加高度 */ background-color: #007aff; color: #ffffff; font-size: 34rpx; /* 增大字体 */ font-weight: bold; display: flex; align-items: center; justify-content: center; /* 添加圆角效果 */ border-top-left-radius: 16rpx; border-top-right-radius: 16rpx; } .preview-title { color: #ffffff; /* 白色字体 */ font-size: 28rpx; /* 字体大小适配40rpx高度 */ font-weight: bold; line-height: 40rpx; /* 与容器高度一致,实现垂直居中 */ width: 100%; text-align: center; } .preview-image { width: 100%; height: 100%; object-fit: contain; border: 1rpx solid rgba(255, 255, 255, 0.1); border-radius: 8rpx; background-color: #f5f5f5; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3); } .action-buttons { display: flex; width: 90%; justify-content: space-between; margin-top: 30rpx; } .action-btn { flex: 1; height: 90rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 34rpx; font-weight: 500; margin: 0 15rpx; } .cancel-btn { background-color: #f0f0f0; color: #333; } .confirm-btn { background-color: #007aff; color: #fff; } /* 加载指示器 */ .loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9998; background-color: rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; align-items: center; justify-content: center; } .loading-spinner { width: 80rpx; height: 80rpx; border: 6rpx solid #f3f3f3; border-top: 6rpx solid #007aff; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .loading-text { color: #fff; font-size: 32rpx; margin-top: 20rpx; } .bottom-action-img{ position: fixed; top: 100; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; display: flex; justify-content: center; align-items: center; } .action-img{ width: 100%; height: 90rpx; background-color: #007aff; display: flex; } </style> 将app内截图变成长截图
最新发布
08-05
<think>我们计划实现长截图(滚动截图)功能。由于原代码中已经实现了单屏截图,现在需要扩展到长截图。 思路: 1. 获取整个WebView的内容高度(通过WebView通信让网页返回高度,或者通过plus.webview的API获取?) 2. 将WebView按照一定高度(比如一屏高度)分块,然后分别绘制每一块到同一个Bitmap上,最后保存。 3. 在绘制每一块之前,需要将WebView滚动到相应位置,并等待渲染完成。 步骤: 步骤一:与WebView通信,获取网页内容高度(如果网页内容高度大于WebView容器高度,则需要滚动截图) 步骤二:创建一个足够大的Bitmap(宽度为WebView宽度,高度为内容高度) 步骤三:循环截图,每次滚动一屏(注意:滚动量要减去重叠部分,避免内容缺失) 步骤四:将每次截取的图片绘制到总Bitmap的对应位置 步骤五:保存总Bitmap 但是,在App端,我们可以利用plus.webview.Webview对象的draw方法,它支持指定区域绘制。因此,我们可以这样: 1. 获取网页内容高度(通过evalJS执行网页脚本获取document.body.scrollHeight) 2. 创建一个与内容高度相同的大Bitmap 3. 设置每次绘制的区域(从0开始,每次绘制一屏,然后滚动,再绘制下一屏,直到绘制完整个高度) 然而,需要注意的是,在绘制过程中,WebView的滚动会导致页面内容变化,所以我们需要在绘制每一块时控制滚动位置,并等待页面稳定。 具体实现: 1. 获取网页内容高度 在onReady中,我们已经有ws.value(即WebView对象)。我们可以通过evalJS执行JS代码来获取高度。 2. 由于draw方法可以指定绘制的区域(clip区域)和绘制到目标Bitmap的位置,我们可以分块绘制。 3. 绘制完成后,保存整个Bitmap。 但是,plus.nativeObj.Bitmap的draw方法在绘制到同一个Bitmap时,需要确保每次绘制的区域不重叠(实际上可以重叠,但我们要避免内容重复)。 然而,实际测试发现,draw方法在绘制整个WebView时,只能绘制当前显示区域。所以我们需要: - 先滚动WebView到当前要截取的位置(通过evalJS设置scrollTop) - 等待一段时间(使用setTimeout)让页面渲染完成 - 然后调用draw方法绘制当前区域到总Bitmap的对应位置 4. 循环直到整个高度都绘制完成。 5. 保存总Bitmap。 由于这个操作可能比较耗时,我们需要显示一个loading提示。 代码修改: 我们将修改captureWebview函数,使其支持长截图。 注意:由于网页内容可能很长,我们分块截图时,每次截图的高度为当前WebView窗口的高度(或者指定一个固定值,比如2000px,但不要超过WebView的显示高度,否则可能截不到)。然后每次滚动这个高度,再截图下一块。 但是,为了避免滚动后页面异步加载内容导致截图不全,我们可以在每次滚动后等待一段时间(例如500ms),或者通过监听WebView的滚动完成事件(但这个事件在跨域网页中可能无法获取)。 因此,我们采用固定等待时间。 具体步骤: 1. 获取网页总高度(totalHeight)和当前屏幕高度(screenHeight,即每次截图的高度,但实际每次截图高度我们取WebView的可见高度,也就是webview的clientHeight) 2. 计算需要截图的次数(n = Math.ceil(totalHeight / screenHeight)) 3. 创建一个高度为totalHeight的Bitmap 4. 循环n次,每次: a. 滚动WebView到位置:i * screenHeight b. 等待500ms(让页面渲染和可能的异步内容加载) c. 调用draw方法,将当前WebView的可见区域绘制到总Bitmap的对应位置(从i*screenHeight开始,高度为screenHeight) 5. 保存总Bitmap。 但是,实际使用中,我们可能无法直接获取WebView的可见高度(因为WebView可能有导航栏等)。我们可以通过plus.webview.currentWebview().getStyle()获取WebView的尺寸。 另外,在滚动时,我们使用evalJS执行window.scrollTo(0, y)来滚动页面。 实现代码: 由于原代码已经使用了plus.nativeObj.Bitmap,我们可以在此基础上修改。 注意:在绘制过程中,用户可能会操作页面,所以最好在截图过程中禁止用户交互(但WebView无法禁止,所以只能尽量避免)。 修改captureWebview函数: 由于代码较长,我们将重新编写一个captureLongScreenshot函数,并在点击“立即取证”时调用它。 但是,原代码中已经有一个captureWebview函数,我们将其改名为captureLongScreenshot,并修改其内部实现。 步骤: 1. 获取WebView的内容高度(通过evalJS执行JS代码获取document.body.scrollHeight) 2. 获取当前WebView的可见区域高度(通过WebView的getStyle方法) 3. 计算需要滚动的次数 4. 创建总Bitmap 5. 循环滚动并截图 6. 保存总Bitmap 由于在APP端,我们使用plus.nativeObj.Bitmap,所以需要创建一个足够大的Bitmap,然后分块绘制。 注意:大Bitmap可能占用很大内存,特别是长网页,所以需要谨慎处理。 代码示例: 由于实际代码较长,我们只写关键步骤: 关键代码: ```javascript const captureLongScreenshot = async () => { uni.showLoading({ title: '正在生成长截图...', mask: true }); if (!ws.value) { uni.showToast({ title: '网页未加载完成,请稍后重试', icon: 'none' }); return; } // 获取网页内容高度 const totalHeight = await new Promise((resolve, reject) => { ws.value.evalJS('document.body.scrollHeight', (height) => { resolve(parseInt(height) || 0); }); }); if (totalHeight === 0) { uni.showToast({ title: '获取网页高度失败', icon: 'none' }); return; } // 获取WebView的可见高度(即一屏高度) const webviewStyle = ws.value.getStyle(); const screenHeight = parseInt(webviewStyle.height) || 0; if (screenHeight === 0) { uni.showToast({ title: '获取WebView高度失败', icon: 'none' }); return; } // 计算需要截图的次数 const chunks = Math.ceil(totalHeight / screenHeight); // 创建一个总高度为totalHeight的Bitmap const longBitmap = new plus.nativeObj.Bitmap('longScreenshot'); // 初始化一个足够大的Bitmap(宽度为WebView宽度,高度为totalHeight) // 注意:这里我们创建一个透明背景的Bitmap,然后分块绘制 longBitmap.load('', { width: webviewStyle.width, height: totalHeight }, () => { // 开始分块截图 let currentY = 0; let index = 0; const captureNextChunk = () => { if (index >= chunks) { // 全部截图完成,保存 const saveUrl = '_doc/' + Date.now() + '_long.jpg'; longBitmap.save(saveUrl, { format: 'jpg', quality: 90 }, (res) => { const previewPath = plus.io.convertLocalFileSystemURL(res.target); screenshotPath.value = previewPath; screenshotFilePath.value = res.target; longBitmap.clear(); // 释放内存 uni.hideLoading(); showPreview.value = true; }, (err) => { console.error('保存长截图失败:', err); uni.hideLoading(); uni.showToast({ title: '保存失败', icon: 'none' }); longBitmap.clear(); }); return; } // 滚动到当前块的位置 ws.value.evalJS(`window.scrollTo(0, ${currentY})`); // 等待滚动完成并渲染(500ms) setTimeout(() => { // 创建一个临时Bitmap用于绘制当前屏幕 const tempBitmap = new plus.nativeObj.Bitmap('temp'); // 绘制当前屏幕到临时Bitmap ws.value.draw(tempBitmap, () => { // 将临时Bitmap绘制到总Bitmap的对应位置 longBitmap.draw(tempBitmap, {}, { top: currentY, left: 0, width: webviewStyle.width, height: screenHeight }, () => { tempBitmap.clear(); // 清除临时Bitmap currentY += screenHeight; index++; captureNextChunk(); }, (err) => { tempBitmap.clear(); console.error('绘制分块失败:', err); uni.hideLoading(); uni.showToast({ title: '截图失败', icon: 'none' }); longBitmap.clear(); }); }, (err) => { tempBitmap.clear(); console.error('截图分块失败:', err); uni.hideLoading(); uni.showToast({ title: '截图失败', icon: 'none' }); longBitmap.clear(); }); }, 500); }; captureNextChunk(); }, (err) => { console.error('创建长截图Bitmap失败:', err); uni.hideLoading(); uni.showToast({ title: '初始化失败', icon: 'none' }); }); }; ``` 注意:上述代码中,我们使用递归函数captureNextChunk来依次处理每一块,避免并发问题。 但是,由于等待时间较长(500ms * 块数),如果网页很长,用户等待时间会很长。我们可以考虑优化等待时间,或者使用更智能的等待方式(例如监听WebView的滚动结束事件,但需要网页配合,所以这里使用固定等待)。 另外,由于递归调用,如果块数太多可能导致栈溢出?但实际块数不会太多(比如10000px高度,每屏2000px,也就5块),所以不用担心。 但是,在H5端,我们无法使用plus.nativeObj.Bitmap,所以H5端我们仍然使用html2canvas,并且html2canvas本身就支持长截图(它会自动滚动并拼接)。所以H5端我们可以直接使用html2canvas的配置参数(如scrollY)来实现长截图。 因此,我们需要区分平台: - APP端:使用上述分块绘制的方式 - H5端:使用html2canvas的scrollY等参数 由于原代码中H5端已经使用了html2canvas,我们修改H5端的代码,使其支持长截图: 在H5端,我们可以这样: ```javascript // #ifdef APP-H5 html2canvas(document.querySelector('.webview-wrapper'), { scrollY: true, // 启用垂直滚动捕获 windowHeight: document.documentElement.scrollHeight // 设置窗口高度为整个文档高度 }).then(canvas => { const dataUrl = canvas.toDataURL('image/jpeg'); screenshotPath.value = dataUrl; showPreview.value = true; uni.hideLoading(); }).catch(err => { console.error('H5截图失败:', err); uni.hideLoading(); uni.showToast({ title: '截图失败', icon: 'none' }); }); // #endif ``` 但是,注意:在H5端,我们无法直接获取到WebView的内容(因为跨域限制),所以如果网页是跨域的,html2canvas可能无法正确截图。所以H5端的长截图受跨域限制。 因此,我们主要解决APP端的长截图问题。 修改后的代码结构: 我们将原来的captureWebview函数改为captureLongScreenshot,并在其中区分APP和H5,分别实现长截图。 注意:在APP端,我们使用分块绘制的方式;在H5端,我们使用html2canvas的滚动截图参数。 由于原代码中H5端已经实现了截图,我们只需要修改其配置即可支持长截图。 但是,原代码中H5端是截取.webview-wrapper元素,这个元素的高度是100vh,所以我们需要修改html2canvas的配置,让它捕获整个网页内容。 修改H5端代码: ```javascript // #ifdef APP-H5 // 获取整个网页的内容高度 const contentHeight = document.documentElement.scrollHeight; html2canvas(document.body, { height: contentHeight, // 设置画布高度为整个文档高度 windowHeight: contentHeight, // 设置窗口高度 scrollY: 0, // 从顶部开始 scrollX: 0, useCORS: true // 尝试跨域 }).then(canvas => { // ... 同上 }); // #endif ``` 但是,由于html2canvas在H5端截取整个网页可能会很慢,并且内存占用高,所以对于很长的网页要谨慎。 综上所述,我们主要修改了APP端的长截图实现,并优化了H5端。 由于代码较长,我们只给出APP端的关键代码。在实际修改时,请将原captureWebview函数替换为captureLongScreenshot函数,并按照上述思路实现。 注意:在APP端,我们使用了递归分块截图,因此需要确保每一步都正确执行,否则可能导致内存泄漏(记得清除临时Bitmap)。 另外,在截图过程中,用户可能会离开页面,所以我们需要在页面卸载时取消截图(但这里没有提供取消机制,可以增加一个取消标志)。 由于时间关系,我们只提供基本实现。 最后,由于长截图可能很大,保存和上传需要更多时间,请在上传部分也做相应调整(原上传函数不变,因为我们已经保存了文件路径)。 修改后的captureLongScreenshot函数(APP端)已经在上面的代码中给出,请整合到原代码中。 注意:原代码中H5端和APP端已经通过条件编译分开,所以我们在APP端使用新的长截图代码,H5端使用修改后的html2canvas配置。 由于原问题要求将截图功能变成长截图,因此我们实现了长截图功能。 但是,原代码中有一个问题:在APP端,我们通过ws.value.evalJS获取高度,但是evalJS的执行是异步的,我们通过Promise封装等待。 另外,在获取高度时,我们执行的是`document.body.scrollHeight`,这通常能获取到整个网页的高度。 最后,在截图过程中,用户可能会看到页面滚动,这是正常现象。 由于代码较长,请根据实际情况调整。 如果遇到问题,可以分块调试,例如先确保获取高度正确,再测试单块截图,再测试拼接。 我们修改后的代码结构如下: 1. 在captureLongScreenshot函数中,先显示loading 2. 区分平台,APP端使用分块截图,H5端使用html2canvas滚动截图 3. 截图完成后,隐藏loading,显示预览 由于原代码中已经有一个captureWebview函数,我们将其重命名为captureLongScreenshot,并修改内容。 注意:原代码中APP端截图部分(非长截图)将被替换。 我们开始修改代码。</think>在 UniApp 中实现 WebView 长截图(滚动截图)功能需要结合 WebView 通信和分块绘制技术。以下是完整的实现方案: ### 完整实现方案 ```vue <script setup lang="ts"> import { onLoad, onReady } from '@dcloudio/uni-app'; import { ref } from 'vue'; declare const plus: any; const webviewPath = ref(''); const ws = ref<any>(null); const screenshotPath = ref(''); const showPreview = ref(false); const platform = ref(''); const screenshotFilePath = ref(''); const webviewContentHeight = ref(0); const isCapturing = ref(false); // 防止重复点击 onLoad((options: any) => { if (options.url) { webviewPath.value = decodeURIComponent(options.url); } const systemInfo = uni.getSystemInfoSync(); platform.value = systemInfo.platform; }); onReady(() => { const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentWebview = currentPage.$getAppWebview(); setTimeout(() => { if (currentWebview.children().length > 0) { ws.value = currentWebview.children()[0]; ws.value.setStyle({ height: 'auto' }); // 获取网页内容高度 setTimeout(() => { ws.value.evalJS('document.body.scrollHeight', (height: string) => { webviewContentHeight.value = parseInt(height) || 0; console.log('网页内容高度:', webviewContentHeight.value); }); }, 2000); } }, 1000); }); // 长截图核心函数 const captureLongScreenshot = async () => { if (!ws.value || isCapturing.value) return; isCapturing.value = true; uni.showLoading({ title: '正在生成长截图...', mask: true }); // 获取WebView可见区域高度 const webviewStyle = ws.value.getStyle(); const screenHeight = parseInt(webviewStyle.height) || 0; if (screenHeight === 0 || webviewContentHeight.value === 0) { uni.showToast({ title: '无法获取网页尺寸', icon: 'none' }); isCapturing.value = false; uni.hideLoading(); return; } // 创建最终的长截图Bitmap const longBitmap = new plus.nativeObj.Bitmap('longScreenshot'); const screenWidth = parseInt(webviewStyle.width) || 375; // 初始化长截图Bitmap longBitmap.load('', { width: screenWidth, height: webviewContentHeight.value }, () => { // 分块截图处理 captureChunks(longBitmap, screenHeight, screenWidth, 0, () => { // 所有分块截图完成 const saveUrl = `_doc/${Date.now()}_long.jpg`; longBitmap.save(saveUrl, { format: 'jpg', quality: 90 }, (res: any) => { const previewPath = plus.io.convertLocalFileSystemURL(res.target); screenshotPath.value = previewPath; screenshotFilePath.value = res.target; longBitmap.clear(); uni.hideLoading(); showPreview.value = true; isCapturing.value = false; }, (err: any) => { console.error('保存长截图失败:', err); longBitmap.clear(); uni.hideLoading(); isCapturing.value = false; uni.showToast({ title: '保存失败', icon: 'none' }); }); }); }, (err: any) => { console.error('创建长截图失败:', err); uni.hideLoading(); isCapturing.value = false; uni.showToast({ title: '初始化失败', icon: 'none' }); }); }; // 递归分块截图 const captureChunks = (longBitmap: any, chunkHeight: number, screenWidth: number, currentY: number, callback: Function) => { if (currentY >= webviewContentHeight.value) { callback(); return; } // 滚动到当前截取位置 ws.value.evalJS(`window.scrollTo(0, ${currentY})`); setTimeout(() => { // 创建临时Bitmap用于当前区块截图 const tempBitmap = new plus.nativeObj.Bitmap('temp'); // 绘制当前屏幕区域 ws.value.draw(tempBitmap, () => { // 将当前区块绘制到长截图中 longBitmap.draw( tempBitmap, {}, { top: currentY, left: 0, width: screenWidth, height: chunkHeight }, () => { tempBitmap.clear(); // 递归处理下一区块 captureChunks(longBitmap, chunkHeight, screenWidth, currentY + chunkHeight, callback); }, (err: any) => { tempBitmap.clear(); console.error('区块绘制失败:', err); uni.hideLoading(); isCapturing.value = false; } ); }, (err: any) => { tempBitmap.clear(); console.error('区块截图失败:', err); uni.hideLoading(); isCapturing.value = false; }); }, 500); // 等待滚动完成 }; // 其他函数保持不变(handleAction, uploadEvidence等) // ... </script> ``` ### 关键实现说明 1. **获取网页内容高度**: ```javascript ws.value.evalJS('document.body.scrollHeight', (height: string) => { webviewContentHeight.value = parseInt(height) || 0; }); ``` 2. **分块截图逻辑**: - 将长网页分成多个区块(每块高度 = 屏幕高度) - 每次滚动到相应位置后截图 - 将区块拼接到最终的长截图中 3. **滚动控制**: ```javascript ws.value.evalJS(`window.scrollTo(0, ${currentY})`); setTimeout(() => { // 截图代码... }, 500); // 等待滚动完成 ``` 4. **Bitmap操作**: ```javascript // 创建长截图画布 const longBitmap = new plus.nativeObj.Bitmap('longScreenshot'); longBitmap.load('', { width, height }); // 将区块添加到长截图 longBitmap.draw(tempBitmap, {}, { top: currentY, ... }); ``` ### 优化建议 1. **内存管理**: ```javascript // 及时清理临时Bitmap tempBitmap.clear(); ``` 2. **进度提示**: ```javascript const progress = Math.min(100, Math.round((currentY / webviewContentHeight.value) * 100)); uni.showLoading({ title: `生成中...${progress}%` }); ``` 3. **分块大小优化**: ```javascript // 根据设备性能调整分块大小 const chunkHeight = screenHeight > 2000 ? 1500 : screenHeight; ``` 4. **错误处理**: ```javascript try { // 截图代码... } catch (err) { console.error('截图异常:', err); uni.hideLoading(); isCapturing.value = false; } ``` ### 注意事项 1. **跨域问题**: - 在 `manifest.json` 中配置域名白名单 ```json "app-plus": { "webview": { "domStorage": true, "crossNetwork": true } } ``` 2. **性能考虑**: - 超过 5000px 的网页需要分块处理 - 添加加载提示防止用户重复点击 - 复杂页面适当增加等待时间(500ms → 800ms) 3. **平台差异**: - **APP 端**:使用上述 plus API 实现 - **H5 端**:使用 html2canvas 的 `scrollY` 参数 ```javascript // #ifdef H5 html2canvas(element, { scrollY: -window.scrollY, height: document.body.scrollHeight }) // #endif ``` 此方案通过分块截图和拼接的方式实现了 WebView 长截图功能,解决了单屏截图无法完整保存长网页内容的问题。实际使用中可根据网页复杂度调整分块大小和等待时间[^1][^3][^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值