1# Using Web Menus 2Menus are key components for user interaction. They help build a clear navigation system and display feature entries in a structured layout, enabling users to quickly find target content or perform operations. As an important hub for human-machine interaction, menus significantly improve the accessibility and user experience of **Web** components and are an indispensable part of application design. The following table lists the menu types of **Web** components. You can select a menu type for your application as required. 3|Menu Type|Target Element|Response Conditions|Customization Support| 4|----|----|----|----| 5|[Text Selection Menu](./web_menu.md#text-selection-menu)|Text|Long press|The menu items can be added or deleted while the menu style cannot be customized.| 6|[Context Menu](./web_menu.md#context-menu)|Hyperlink, image, and text|Long press and right-click|Customization can be implemented using menu components.| 7|[Custom Menu](./web_menu.md#custom-menu)|Image|Long press|Customization can be implemented using menu components.| 8## Text Selection Menu 9The text selection menu of the **Web** component is a context interaction component implemented by custom elements. It is dynamically displayed when a user selects text and provides operations such as copy, share, and mark. It has standard features and scalability and is the core feature of text operations on mobile devices. The text selection menu is displayed when a user long-presses the selected text or long-presses the single handle in the editing state. The menu items are horizontally arranged. The system provides the default menu implementation. You can use the [editMenuOptions](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#editmenuoptions12) API to customize the text selection menu. 101. Use the **onCreateMenu** method to customize menu items. Operate **Array<[TextMenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitem.md)>** to add or delete menu items. Define the menu item name, icon, and ID in [TextMenuItem](../reference/apis-arkui/arkui-ts/ts-basic-components-menuitem.md). 112. Use the **onMenuItemClick** method to process the menu item click event. When false is returned, the default system logic is executed. 123. Create an [EditMenuOptions](../reference/apis-arkui/arkui-ts/ts-text-common.md#editmenuoptions) object, which contains the **onCreateMenu** and **onMenuItemClick** methods, and bind the object to the **Web** component using the [editMenuOptions](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#editmenuoptions12) method of the **Web** component. 13 14 ```ts 15 // xxx.ets 16 import { webview } from '@kit.ArkWeb'; 17 @Entry 18 @Component 19 struct WebComponent { 20 controller: webview.WebviewController = new webview.WebviewController(); 21 22 onCreateMenu(menuItems: Array<TextMenuItem>): Array<TextMenuItem> { 23 let items = menuItems.filter((menuItem) => { 24 // Filter the menu items as required. 25 return ( 26 menuItem.id.equals(TextMenuItemId.CUT) || 27 menuItem.id.equals(TextMenuItemId.COPY) || 28 menuItem.id.equals((TextMenuItemId.PASTE)) 29 ) 30 }); 31 let customItem1: TextMenuItem = { 32 content: 'customItem1', 33 id: TextMenuItemId.of('customItem1'), 34 icon: $r('app.media.startIcon') 35 }; 36 let customItem2: TextMenuItem = { 37 content: $r('app.string.EntryAbility_label'), 38 id: TextMenuItemId.of('customItem2'), 39 icon: $r('app.media.startIcon') 40 }; 41 items.push(customItem1);// Add an item to the end of the item list. 42 items.unshift(customItem2);// Add an item to the beginning of the item list. 43 items.push(customItem1); 44 items.push(customItem1); 45 items.push(customItem1); 46 items.push(customItem1); 47 items.push(customItem1); 48 return items; 49 } 50 51 onMenuItemClick(menuItem: TextMenuItem, textRange: TextRange): boolean { 52 if (menuItem.id.equals(TextMenuItemId.CUT)) { 53 // Custom behavior 54 console.log("Intercept ID: CUT") 55 return true; // Return true to not execute the system callback. 56 } else if (menuItem.id.equals(TextMenuItemId.COPY)) { 57 // Custom behavior 58 console.log("Not intercept ID: COPY") 59 return false; // Return false to execute the system callback. 60 } else if (menuItem.id.equals(TextMenuItemId.of('customItem1'))) { 61 // Custom behavior 62 console.log("Intercept ID: customItem1") 63 return true;// Custom menu item. If true is returned, the menu is not closed after being clicked. If false is returned, the menu is closed. 64 } else if (menuItem.id.equals((TextMenuItemId.of($r('app.string.EntryAbility_label'))))){ 65 // Custom behavior 66 console.log("Intercept ID: app.string.customItem2") 67 return true; 68 } 69 return false;// Return the default value false. 70 } 71 72 @State EditMenuOptions: EditMenuOptions = { onCreateMenu: this.onCreateMenu, onMenuItemClick: this.onMenuItemClick } 73 74 build() { 75 Column() { 76 Web({ src: $rawfile("index.html"), controller: this.controller }) 77 .editMenuOptions(this.EditMenuOptions) 78 } 79 } 80 } 81 ``` 82 83 ```html 84 <!--index.html--> 85 <!DOCTYPE html> 86 <html> 87 <head> 88 <title>Test Web Page</title> 89 </head> 90 <body> 91 <h1>editMenuOptions Demo</h1> 92 <span>edit menu options</span> 93 </body> 94 </html> 95 ``` 96 97## Context Menu 98A context menu is a shortcut menu triggered by a specific operation (such as right-clicking or holding down the rich text) to provide feature items related to the current operation object or UI element. Items in a context menu are arranged vertically. This menu is not provided by default. If it is not implemented in the application, the context menu is not displayed. To implement a context menu, the application needs to use the [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md#menu) component to create a menu child window and bind it to the **Web** component. When the menu is displayed, use the [onContextMenuShow](../reference/apis-arkweb/arkts-basic-components-web-events.md#oncontextmenushow9) API to obtain the context menu details, including the click location and HTML element information. 99 1001. The [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md#menu) component contains the behaviors and styles of all menu items. 1012. Use the **bindPopup** method to bind the **Menu** component to the **Web** component. When the context menu is displayed, the created **Menu** component is displayed. 1023. Obtain the context menu event information [onContextMenuShowEvent](../reference/apis-arkweb/arkts-basic-components-web-i.md#oncontextmenushowevent12) in the **onContextMenuShow** callback. The **param** parameter is of the [WebContextMenuParam](../reference/apis-arkweb/arkts-basic-components-web-WebContextMenuParam.md) type, which contains the HTML element information and location information corresponding to the click position. The **result** parameter is of the [WebContextMenuResult](../reference/apis-arkweb/arkts-basic-components-web-WebContextMenuResult.md) type, which provides common menu capabilities. 103 104```ts 105// xxx.ets 106import { webview } from '@kit.ArkWeb'; 107import { pasteboard } from '@kit.BasicServicesKit'; 108 109const TAG = 'ContextMenu'; 110 111@Entry 112@Component 113struct WebComponent { 114 controller: webview.WebviewController = new webview.WebviewController(); 115 private result: WebContextMenuResult | undefined = undefined; 116 @State linkUrl: string = ''; 117 @State offsetX: number = 0; 118 @State offsetY: number = 0; 119 @State showMenu: boolean = false; 120 uiContext: UIContext = this.getUIContext(); 121 122 @Builder 123 // Build and trigger a custom menu. 124 MenuBuilder() { 125 // A component that is used to present a vertical list of items to the user. 126 Menu() { 127 // A component that is used to represent an item in a menu. 128 MenuItem({ 129 content: 'Copy Image', 130 }) 131 .width(100) 132 .height(50) 133 .onClick(() => { 134 this.result?.copyImage(); 135 this.showMenu = false; 136 }) 137 MenuItem({ 138 content: 'Cut', 139 }) 140 .width(100) 141 .height(50) 142 .onClick(() => { 143 this.result?.cut(); 144 this.showMenu = false; 145 }) 146 MenuItem({ 147 content: 'Copy', 148 }) 149 .width(100) 150 .height(50) 151 .onClick(() => { 152 this.result?.copy(); 153 this.showMenu = false; 154 }) 155 MenuItem({ 156 content: 'Paste', 157 }) 158 .width(100) 159 .height(50) 160 .onClick(() => { 161 this.result?.paste(); 162 this.showMenu = false; 163 }) 164 MenuItem({ 165 content: 'Copy Link', 166 }) 167 .width(100) 168 .height(50) 169 .onClick(() => { 170 let pasteData = pasteboard.createData('text/plain', this.linkUrl); 171 pasteboard.getSystemPasteboard().setData(pasteData, (error) => { 172 if (error) { 173 return; 174 } 175 }) 176 this.showMenu = false; 177 }) 178 MenuItem({ 179 content: 'Select All', 180 }) 181 .width(100) 182 .height(50) 183 .onClick(() => { 184 this.result?.selectAll(); 185 this.showMenu = false; 186 }) 187 } 188 .width(150) 189 .height(300) 190 } 191 192 build() { 193 Column() { 194 Web({ src: $rawfile("index.html"), controller: this.controller }) 195 // Trigger a custom dialog box. 196 .onContextMenuShow((event) => { 197 if (event) { 198 this.result = event.result 199 console.info("x coord = " + event.param.x()); 200 console.info("link url = " + event.param.getLinkUrl()); 201 this.linkUrl = event.param.getLinkUrl(); 202 } 203 console.info(TAG, `x: ${this.offsetX}, y: ${this.offsetY}`); 204 this.showMenu = true; 205 this.offsetX = 0; 206 this.offsetY = Math.max(this.uiContext!.px2vp(event?.param.y() ?? 0) - 0, 0); 207 return true; 208 }) 209 .bindPopup(this.showMenu, 210 { 211 builder: this.MenuBuilder(), 212 enableArrow: false, 213 placement: Placement.LeftTop, 214 offset: { x: this.offsetX, y: this.offsetY }, 215 mask: false, 216 onStateChange: (e) => { 217 if (!e.isVisible) { 218 this.showMenu = false; 219 this.result!.closeContextMenu(); 220 } 221 } 222 }) 223 } 224 } 225} 226``` 227```html 228<!-- index.html --> 229<!DOCTYPE html> 230<html lang="en"> 231<body> 232 <h1>onContextMenuShow</h1> 233 <a href="http://www.example.com" style="font-size:27px">Hyperlink www.example.com</a> 234 <div><img src="example.png"></div> 235 <p>Right-click text to display the context menu</p> 236</body> 237</html> 238``` 239 240## Custom Menu 241Custom menus enable you to adjust menu triggering timing and visual display, so that your application can dynamically match feature entries based on user operation scenarios. This simplifies UI adaptation in the development process and makes application interaction more intuitive. Custom menus allow an application to display a custom menu by calling [bindSelectionMenu](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#bindselectionmenu13) based on the event type and element type. Currently, the long-press event of an image is supported. 2421. Create a [Menu](../reference/apis-arkui/arkui-ts/ts-basic-components-menu.md#menu) component as the menu pop-up window. 2432. Use the [bindSelectionMenu](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#bindselectionmenu13) method of the **Web** component to bind the **MenuBuilder** pop-up window. Set [WebElementType](../reference/apis-arkweb/arkts-basic-components-web-e.md#webelementtype13) to **WebElementType.IMAGE** and [responseType](../reference/apis-arkweb/arkts-basic-components-web-e.md#webresponsetype13) to **WebResponseType.LONG_PRESS**, so that the menu is displayed when the image is pressed for a long time. Define **onAppear**, **onDisappear**, **preview**, and **menuType** in [options](../reference/apis-arkweb/arkts-basic-components-web-i.md#selectionmenuoptionsext13). 244```ts 245// xxx.ets 246import { webview } from '@kit.ArkWeb'; 247 248interface PreviewBuilderParam { 249 previewImage: Resource | string | undefined; 250 width: number; 251 height: number; 252} 253 254@Builder function PreviewBuilderGlobal($$: PreviewBuilderParam) { 255 Column() { 256 Image($$.previewImage) 257 .objectFit(ImageFit.Fill) 258 .autoResize(true) 259 }.width($$.width).height($$.height) 260} 261 262@Entry 263@Component 264struct WebComponent { 265 controller: webview.WebviewController = new webview.WebviewController(); 266 267 private result: WebContextMenuResult | undefined = undefined; 268 @State previewImage: Resource | string | undefined = undefined; 269 @State previewWidth: number = 0; 270 @State previewHeight: number = 0; 271 uiContext: UIContext = this.getUIContext(); 272 273 @Builder 274 MenuBuilder() { 275 Menu() { 276 MenuItem({content:'Copy',}) 277 .onClick(() => { 278 this.result?.copy(); 279 this.result?.closeContextMenu(); 280 }) 281 MenuItem({content:'Select All',}) 282 .onClick(() => { 283 this.result?.selectAll(); 284 this.result?.closeContextMenu(); 285 }) 286 } 287 } 288 build() { 289 Column() { 290 Web({ src: $rawfile("index.html"), controller: this.controller }) 291 .bindSelectionMenu(WebElementType.IMAGE, this.MenuBuilder, WebResponseType.LONG_PRESS, 292 { 293 onAppear: () => {}, 294 onDisappear: () => { 295 this.result?.closeContextMenu(); 296 }, 297 preview: PreviewBuilderGlobal({ 298 previewImage: this.previewImage, 299 width: this.previewWidth, 300 height: this.previewHeight 301 }), 302 menuType: MenuType.PREVIEW_MENU 303 }) 304 .onContextMenuShow((event) => { 305 if (event) { 306 this.result = event.result; 307 if (event.param.getLinkUrl()) { 308 return false; 309 } 310 this.previewWidth = this.uiContext!.px2vp(event.param.getPreviewWidth()); 311 this.previewHeight = this.uiContext!.px2vp(event.param.getPreviewHeight()); 312 if (event.param.getSourceUrl().indexOf("resource://rawfile/") == 0) { 313 this.previewImage = $rawfile(event.param.getSourceUrl().substr(19)); 314 } else { 315 this.previewImage = event.param.getSourceUrl(); 316 } 317 return true; 318 } 319 return false; 320 }) 321 } 322 } 323} 324``` 325```html 326<!--index.html--> 327<!DOCTYPE html> 328<html> 329 <head> 330 <title>Test Web Page</title> 331 </head> 332 <body> 333 <h1>bindSelectionMenu Demo</h1> 334 <img src="./img.png" > 335 </body> 336</html> 337``` 338 339 340Since API version 20, the hyperlink menu triggered by long-pressing can be bound. You can bind different custom menus to images and links. 341 342The following example uses **PreviewBuilder** to define the content of the pop-up menu corresponding to the hyperlink, loads the hyperlink content using the **Web** component, and displays the loading progress using [the Progress component](../ui/arkts-common-components-progress-indicator.md). 343 344```ts 345import { webview } from '@kit.ArkWeb'; 346import { pasteboard } from '@kit.BasicServicesKit'; 347 348interface PreviewBuilderParam { 349 width: number; 350 height: number; 351 url:Resource | string | undefined; 352} 353 354interface PreviewBuilderParamForImage { 355 previewImage: Resource | string | undefined; 356 width: number; 357 height: number; 358} 359 360 361@Builder function PreviewBuilderGlobalForImage($$: PreviewBuilderParamForImage) { 362 Column() { 363 Image($$.previewImage) 364 .objectFit(ImageFit.Fill) 365 .autoResize(true) 366 }.width($$.width).height($$.height) 367} 368 369@Entry 370@Component 371struct SelectionMenuLongPress { 372 controller: webview.WebviewController = new webview.WebviewController(); 373 previewController: webview.WebviewController = new webview.WebviewController(); 374 @Builder PreviewBuilder($$: PreviewBuilderParam){ 375 Column() { 376 Stack(){ 377 Text("") // Select whether to display the URL. 378 .padding(5) 379 .width('100%') 380 .textAlign(TextAlign.Start) 381 .backgroundColor(Color.White) 382 .copyOption(CopyOptions.LocalDevice) 383 .maxLines(1) 384 .textOverflow({overflow:TextOverflow.Ellipsis}) 385 Progress({ value: this.progressValue, total: 100, type: ProgressType.Linear }) // Display the progress bar. 386 .style({ strokeWidth: 3, enableSmoothEffect: true }) 387 .backgroundColor(Color.White) 388 .opacity(this.progressVisible?1:0) 389 .backgroundColor(Color.White) 390 }.alignContent(Alignment.Bottom) 391 Web({src:$$.url,controller: new webview.WebviewController()}) 392 .javaScriptAccess(true) 393 .fileAccess(true) 394 .onlineImageAccess(true) 395 .imageAccess(true) 396 .domStorageAccess(true) 397 .onPageBegin(()=>{ 398 this.progressValue = 0; 399 this.progressVisible = true; 400 }) 401 .onProgressChange((event)=>{ 402 this.progressValue = event.newProgress; 403 }) 404 .onPageEnd(()=>{ 405 this.progressVisible = false; 406 }) 407 .hitTestBehavior(HitTestMode.None) // Disable the gesture response during web page preview. 408 }.width($$.width).height ($$.height) // Set the preview width and height. 409 } 410 411 private result: WebContextMenuResult | undefined = undefined; 412 @State previewImage: Resource | string | undefined = undefined; 413 @State previewWidth: number = 1; 414 @State previewHeight: number = 1; 415 @State previewWidthImage: number = 1; 416 @State previewHeightImage: number = 1; 417 @State linkURL:string = ""; 418 @State progressValue:number = 0; 419 @State progressVisible:boolean = true; 420 uiContext: UIContext = this.getUIContext(); 421 422 @Builder 423 LinkMenuBuilder() { 424 Menu() { 425 MenuItem({ content: 'Copy Link', }) 426 .onClick(() => { 427 const pasteboardData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.linkURL); 428 const systemPasteboard = pasteboard.getSystemPasteboard(); 429 systemPasteboard.setData(pasteboardData); 430 }) 431 MenuItem({content:'Open Link'}) 432 .onClick(()=>{ 433 this.controller.loadUrl(this.linkURL); 434 }) 435 } 436 } 437 @Builder 438 ImageMenuBuilder() { 439 Menu() { 440 MenuItem({ content: 'Copy Image', }) 441 .onClick(() => { 442 this.result?.copyImage(); 443 this.result?.closeContextMenu(); 444 }) 445 } 446 } 447 build() { 448 Column() { 449 Web({ src: $rawfile("index.html"), controller: this.controller }) 450 .javaScriptAccess(true) 451 .fileAccess(true) 452 .onlineImageAccess(true) 453 .imageAccess(true) 454 .domStorageAccess(true) 455 .bindSelectionMenu(WebElementType.LINK, this.LinkMenuBuilder, WebResponseType.LONG_PRESS, 456 { 457 onAppear: () => {}, 458 onDisappear: () => { 459 this.result?.closeContextMenu(); 460 }, 461 preview: this.PreviewBuilder({ 462 width: 500, 463 height: 400, 464 url:this.linkURL 465 }), 466 menuType: MenuType.PREVIEW_MENU, 467 }) 468 .bindSelectionMenu(WebElementType.IMAGE, this.ImageMenuBuilder, WebResponseType.LONG_PRESS, 469 { 470 onAppear: () => {}, 471 onDisappear: () => { 472 this.result?.closeContextMenu(); 473 }, 474 preview: PreviewBuilderGlobalForImage({ 475 previewImage: this.previewImage, 476 width: this.previewWidthImage, 477 height: this.previewHeightImage, 478 }), 479 menuType: MenuType.PREVIEW_MENU, 480 }) 481 .zoomAccess(true) 482 .onContextMenuShow((event) => { 483 if (event) { 484 this.result = event.result; 485 this.previewWidthImage = this.uiContext!.px2vp(event.param.getPreviewWidth()); 486 this.previewHeightImage = this.uiContext!.px2vp(event.param.getPreviewHeight()); 487 if (event.param.getSourceUrl().indexOf("resource://rawfile/") == 0) { 488 this.previewImage = $rawfile(event.param.getSourceUrl().substring(19)); 489 } else { 490 this.previewImage = event.param.getSourceUrl(); 491 } 492 this.linkURL = event.param.getLinkUrl() 493 return true; 494 } 495 return false; 496 }) 497 } 498 499 } 500 // Swipe back 501 onBackPress(): boolean | void { 502 if (this.controller.accessStep(-1)) { 503 this.controller.backward(); 504 return true; 505 } else { 506 return false; 507 } 508 } 509} 510``` 511HTML example: 512```html 513<html lang="zh-CN"><head> 514 <meta charset="UTF-8"> 515 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 516 <title>Information Page</title> 517</head> 518<body> 519 <div> 520 <section> 521 <a href="https://www.example1.com/">EMAPLE1</a> 522 <a href="https://www.example.com">EXAMPLE</a> 523 </section> 524 </div> 525 <footer> 526 <p>Note that all the above URLs are for reference only.</p> 527 </footer> 528</body></html> 529``` 530 531 532## Saving Images by Using the Web Menu 5331. Create the **MenuBuilder** component as the menu pop-up window, use the [SaveButton](../reference/apis-arkui/arkui-ts/ts-security-components-savebutton.md) component to save images, and bind **MenuBuilder** to the **Web** component using **bindContextMenu**. 5342. Obtain the image URL in **onContextMenuShow** and save the image to the application sandbox by calling **copyLocalPicToDir** or **copyUrlPicToDir**. 5353. Save images in the application sandbox to the gallery by using **photoAccessHelper**. 536 537 ```ts 538import { webview } from '@kit.ArkWeb'; 539import { common } from '@kit.AbilityKit'; 540import { fileIo as fs, ReadOptions, WriteOptions } from '@kit.CoreFileKit'; 541import { systemDateTime } from '@kit.BasicServicesKit'; 542import { http } from '@kit.NetworkKit'; 543import { photoAccessHelper } from '@kit.MediaLibraryKit'; 544 545@Entry 546@Component 547struct WebComponent { 548 saveButtonOptions: SaveButtonOptions = { 549 icon: SaveIconStyle.FULL_FILLED, 550 text: SaveDescription.SAVE_IMAGE, 551 buttonType: ButtonType.Capsule 552 } 553 controller: webview.WebviewController = new webview.WebviewController(); 554 private result: WebContextMenuResult | undefined = undefined; 555 @State showMenu: boolean = false; 556 @State imgUrl: string = ''; 557 context = this.getUIContext().getHostContext() as common.UIAbilityContext; 558 559 copyLocalPicToDir(rawfilePath: string, newFileName: string): string { 560 let srcFileDes = this.context.resourceManager.getRawFdSync(rawfilePath); 561 let dstPath = this.context.filesDir + "/" +newFileName; 562 let dest: fs.File = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE); 563 let bufsize = 4096; 564 let buf = new ArrayBuffer(bufsize); 565 let off = 0, len = 0, readedLen = 0; 566 while (len = fs.readSync(srcFileDes.fd, buf, { offset: srcFileDes.offset + off, length: bufsize })) { 567 readedLen += len; 568 fs.writeSync(dest.fd, buf, { offset: off, length: len }); 569 off = off + len; 570 if ((srcFileDes.length - readedLen) < bufsize) { 571 bufsize = srcFileDes.length - readedLen; 572 } 573 } 574 fs.close(dest.fd); 575 return dest.path; 576 } 577 578 async copyUrlPicToDir(picUrl: string, newFileName: string): Promise<string> { 579 let uri = ''; 580 let httpRequest = http.createHttp(); 581 let data: http.HttpResponse = await(httpRequest.request(picUrl) as Promise<http.HttpResponse>); 582 if (data?.responseCode == http.ResponseCode.OK) { 583 let dstPath = this.context.filesDir + "/" + newFileName; 584 let dest: fs.File = fs.openSync(dstPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE); 585 let writeLen: number = fs.writeSync(dest.fd, data.result as ArrayBuffer); 586 uri = dest.path; 587 } 588 return uri; 589 } 590 591 @Builder 592 MenuBuilder() { 593 Column() { 594 Row() { 595 SaveButton(this.saveButtonOptions) 596 .onClick(async (event, result: SaveButtonOnClickResult) => { 597 if (result == SaveButtonOnClickResult.SUCCESS) { 598 try { 599 let context = this.context; 600 let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 601 let uri = ''; 602 if (this.imgUrl?.includes('rawfile')) { 603 let rawFileName: string = this.imgUrl.substring(this.imgUrl.lastIndexOf('/') + 1); 604 uri = this.copyLocalPicToDir(rawFileName, 'copyFile.png'); 605 } else if (this.imgUrl?.includes('http') || this.imgUrl?.includes('https')) { 606 uri = await this.copyUrlPicToDir(this.imgUrl, `onlinePic${systemDateTime.getTime()}.png`); 607 } 608 let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createImageAssetRequest(context, uri); 609 await phAccessHelper.applyChanges(assetChangeRequest); 610 } 611 catch (err) { 612 console.error(`create asset failed with error: ${err.code}}, ${err.message}}`); 613 } 614 } else { 615 console.error(`SaveButtonOnClickResult create asset failed`); 616 } 617 this.showMenu = false; 618 }) 619 } 620 .margin({ top: 20, bottom: 20 }) 621 .justifyContent(FlexAlign.Center) 622 } 623 .width('80') 624 .backgroundColor(Color.White) 625 .borderRadius(10) 626 } 627 628 build() { 629 Column() { 630 Web({src: $rawfile("index.html"), controller: this.controller}) 631 .onContextMenuShow((event) => { 632 if (event) { 633 let hitValue = this.controller.getLastHitTest(); 634 this.imgUrl = hitValue.extra; 635 } 636 this.showMenu = true; 637 return true; 638 }) 639 .bindContextMenu(this.MenuBuilder, ResponseType.LongPress) 640 .fileAccess(true) 641 .javaScriptAccess(true) 642 .domStorageAccess(true) 643 } 644 } 645} 646 ``` 647 ```html 648<!--index.html--> 649<!DOCTYPE html> 650<html> 651<head> 652 <title>SavePicture</title> 653</head> 654<body> 655<h1>SavePicture</h1> 656<br> 657<br> 658<br> 659<br> 660<br> 661<img src="./startIcon.png"> 662</body> 663</html> 664 ``` 665 666## FAQs 667### How do I disable the menu triggered by a long press? 668You can use the [editMenuOptions](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#editmenuoptions12) API to filter out all default menus. Then, default menus are not displayed if there is no menu item. 669 ```ts 670// xxx.ets 671import { webview } from '@kit.ArkWeb'; 672@Entry 673@Component 674struct WebComponent { 675 controller: webview.WebviewController = new webview.WebviewController(); 676 677 onCreateMenu(menuItems: Array<TextMenuItem>): Array<TextMenuItem> { 678 let items = menuItems.filter((menuItem) => { 679 // Filter the menu items as required. 680 return false; 681 }); 682 return items; 683 } 684 685 onMenuItemClick(menuItem: TextMenuItem, textRange: TextRange): boolean { 686 return false;// Return the default value false. 687 } 688 689 @State EditMenuOptions: EditMenuOptions = { onCreateMenu: this.onCreateMenu, onMenuItemClick: this.onMenuItemClick } 690 691 build() { 692 Column() { 693 Web({ src: $rawfile("index.html"), controller: this.controller }) 694 .editMenuOptions(this.EditMenuOptions) 695 } 696 } 697} 698 ``` 699 ```html 700<!--index.html--> 701<!DOCTYPE html> 702<html> 703 <head> 704 <title>Test Web Page</title> 705 </head> 706 <body> 707 <h1>editMenuOptions Demo</h1> 708 <span>edit menu options</span> 709 </body> 710</html> 711 ``` 712 713 714### What should I do if the menu with a selection handle is not displayed when the selection area is displayed? 715Check whether the selection area is operated using the [selection API](https://www.w3.org/TR/selection-api/) of the JavaScript. If yes, the menu with a selection handle is not displayed. 716