【HarmonyOS】普通组件与web组件长截屏方案:原则是利用Scroll内的组件可以使用componentSnapshot完整的截屏
【普通组件长截屏】
import { componentSnapshot, promptAction } from '@kit.ArkUI'
import { common } from '@kit.AbilityKit'
import { photoAccessHelper } from '@kit.MediaLibraryKit'
import fs from '@ohos.file.fs';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';@Entry
@Component
struct Page37 {@State lineHeight: number = 0 // 单行文本的高度@State pageHeight: number = 0 // 每页的最大高度@State totalContentHeight: number = 0 // 整个文本内容的高度@State textContent: string = " " // 文本内容,默认一个空格是为了计算单行文本的高度@State scrollOffset: number = 0 // 当前滚动偏移量@State totalPages: number = 1 // 总页数@State currentPage: number = 1 // 当前页数scroller: Scroller = new Scroller() // 滚动条实例resetMaxLineHeight() {if (this.lineHeight > 0 && this.pageHeight > 0 && this.totalContentHeight > 0) {this.pageHeight = (Math.floor(this.pageHeight / this.lineHeight)) * this.lineHeightthis.totalPages = Math.ceil(this.totalContentHeight / this.pageHeight) //向上取整得到总页数}}build() {Column() {SaveButton().onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {if (result === SaveButtonOnClickResult.SUCCESS) {const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;// 免去权限申请和权限请求等环节,获得临时授权,保存对应图片let helper = photoAccessHelper.getPhotoAccessHelper(context);try {// onClick触发后5秒内通过createAsset接口创建图片文件,5秒后createAsset权限收回。let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');// 使用uri打开文件,可以持续写入内容,写入过程不受时间限制let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);componentSnapshot.get("aaaa").then((pixelMap) => {let packOpts: image.PackingOption = { format: 'image/png', quality: 100 }const imagePacker: image.ImagePacker = image.createImagePacker();return imagePacker.packToFile(pixelMap, file.fd, packOpts).finally(() => {imagePacker.release(); //释放fs.close(file.fd);promptAction.showToast({message: '图片已保存至相册',duration: 2000});});})} catch (error) {const err: BusinessError = error as BusinessError;console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`);}} else {promptAction.showToast({message: '设置权限失败!',duration: 2000});}})Text('第一章').margin({ top: 10, bottom: 10 }).backgroundColor(Color.Pink).width('100%').textAlign(TextAlign.Center)Column() {Scroll(this.scroller) {Column() {Text(this.textContent).id('aaaa').backgroundColor(Color.Orange).fontSize(20).lineHeight(40).fontColor(Color.Black)// .textOverflow({ overflow: TextOverflow.Clip }).margin({ top: this.scrollOffset }).onAreaChange((oldArea: Area, newArea: Area) => {if (this.lineHeight == 0 && newArea.height > 0) {this.lineHeight = newArea.height as numberthis.resetMaxLineHeight()//添加数据测试let str = ""for (let i = 1; i <= 20; i++) {str += ` ${i}、荣誉和耻辱,是荣辱观中的一对基本范畴,是指社会对人们行为褒贬评价以及人们对这种评价的自我感受。知荣辱,是人性的标志,是人区别于动物、人之为人的重要标准。`}this.textContent = strreturn}if (this.totalContentHeight != newArea.height) {console.info(`newArea.height:${newArea.height}`)this.totalContentHeight = newArea.height as numberthis.resetMaxLineHeight()}})}.hitTestBehavior(HitTestMode.Block) //禁止滑动}.scrollBar(BarState.Off).constraintSize({ maxHeight: this.pageHeight == 0 ? 1000 : this.pageHeight })}.width('100%').layoutWeight(1).onAreaChange((oldArea: Area, newArea: Area) => {if (this.pageHeight == 0 && newArea.height > 0) {this.pageHeight = newArea.height as numberthis.resetMaxLineHeight()}})Row() {Button('上一页').onClick(() => {if (this.currentPage == 1) {promptAction.showToast({ message: "没有上一页了" })return;}this.scrollOffset += this.pageHeightthis.currentPage--;})Text(`${this.currentPage}/${this.totalPages}`)Button('下一页').onClick(() => {if (this.currentPage == this.totalPages) {promptAction.showToast({ message: "没有下一页了" })return;}this.scrollOffset -= this.pageHeightthis.currentPage++;})}.margin({ top: 10, bottom: 10 }).backgroundColor(Color.Pink).width('100%').justifyContent(FlexAlign.SpaceAround)}.width('100%').height('100%').backgroundColor(Color.Gray)}
}
【web组件长截屏】
src/main/resources/rawfile/test.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><script>//用于根据浏览器对 CSS.supports 和 env/constant 的支持情况,动态地调整视口元标签的内容,以达到最佳的页面显示效果。var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||CSS.supports('top: constant(a)'))document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +(coverSupport ? ', viewport-fit=cover' : '') + '" />')</script><title></title><!--用于设置浏览器页签上显示的小图标 start--><!-- <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" /> --><link rel="stylesheet" href="mycss.css" /><link rel="icon" href="./static/favicon.ico" /><!--用于设置浏览器页签上显示的小图标 end--><!--preload-links--><!--app-context-->
</head>
<body>
<div>测试测试</div>
<div>测试测试</div>
<div>测试测试</div>
<div>测试测试</div>
<div>测试测试</div>
<div>测试测试</div>
<div>测试测试</div>
<div>测试测试</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>哈哈哈哈</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>1111111</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>2222222</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>aaaaaaa</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>bbbbbbb</div>
<div>到底了</div>
<div id="webBottom"></div>
</body>
<script>//Android禁止微信调整字体大小(function() {if (typeof WeixinJSBridge == "object" && typeof WeixinJSBridge.invoke == "function") {handleFontSize();} else {if (document.addEventListener) {document.addEventListener("WeixinJSBridgeReady", handleFontSize, false);} else if (document.attachEvent) {document.attachEvent("WeixinJSBridgeReady", handleFontSize);document.attachEvent("onWeixinJSBridgeReady", handleFontSize);}}function handleFontSize() {WeixinJSBridge.invoke('setFontSizeCallback', {'fontSize': 0});WeixinJSBridge.on('menu:setfont', function() {WeixinJSBridge.invoke('setFontSizeCallback', {'fontSize': 0});});}})();function setWebHeight() {window.hm.setWebHeight(document.getElementById('webBottom').offsetTop);}// 在文档加载完成后执行 setWebHeight 函数window.onload = function() {setWebHeight();};
</script></html>
src/main/ets/pages/Page42.ets
import { webview } from '@kit.ArkWeb';
import web_webview from '@ohos.web.webview';
import dataPreferences from '@ohos.data.preferences';
import { common } from '@kit.AbilityKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { componentSnapshot, promptAction } from '@kit.ArkUI';
import fs from '@ohos.file.fs';
import { image } from '@kit.ImageKit';
import { BusinessError } from '@kit.BasicServicesKit';class WebService {setWebHeight = (height: string) => {console.info('web高度:', height);getContext().eventHub.emit("设置web高度",height)}
}@Entry
@Component
struct Page42 {controller: webview.WebviewController = new webview.WebviewController();webService: WebService = new WebService( );methodList: Array<string> = []@State isShort: boolean = true@State webHeight: number | undefined = undefinedaboutToAppear(): void {this.methodList.splice(0) //清空原数组console.info('====this.testObjtest', JSON.stringify(this.webService))Object.keys(this.webService).forEach((key) => {this.methodList.push(key)console.info('====key', key)});getContext().eventHub.on("设置web高度",(height:number)=>{this.webHeight = height})}build() {Scroll() {Column() {SaveButton().onClick(async (event: ClickEvent, result: SaveButtonOnClickResult) => {if (result === SaveButtonOnClickResult.SUCCESS) {const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;// 免去权限申请和权限请求等环节,获得临时授权,保存对应图片let helper = photoAccessHelper.getPhotoAccessHelper(context);try {// onClick触发后5秒内通过createAsset接口创建图片文件,5秒后createAsset权限收回。let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg');// 使用uri打开文件,可以持续写入内容,写入过程不受时间限制let file = await fs.open(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);componentSnapshot.get("aaaa").then((pixelMap) => {let packOpts: image.PackingOption = { format: 'image/png', quality: 100 }const imagePacker: image.ImagePacker = image.createImagePacker();return imagePacker.packToFile(pixelMap, file.fd, packOpts).finally(() => {imagePacker.release(); //释放fs.close(file.fd);promptAction.showToast({message: '图片已保存至相册',duration: 2000});});})} catch (error) {const err: BusinessError = error as BusinessError;console.error(`Failed to save photo. Code is ${err.code}, message is ${err.message}`);}} else {promptAction.showToast({message: '设置权限失败!',duration: 2000});}})Text('1234测试顶部').backgroundColor(Color.Red).width('100%').height('800lpx')Web({ src: $rawfile('test.html'), controller: this.controller, renderMode: RenderMode.SYNC_RENDER }).width('100%').height(this.webHeight).layoutMode(WebLayoutMode.FIT_CONTENT).javaScriptAccess(true)//设置是否允许执行JavaScript脚本,默认允许执行。.mixedMode(MixedMode.All)//HTTP和HTTPS混合.javaScriptProxy({name: "hm",object: this.webService,methodList: this.methodList,controller: this.controller,}).id("aaaa")Text('测试底部').backgroundColor(Color.Blue).width('100%').height('800lpx')}}.width('100%').height('100%').align(Alignment.Top)}
}