Giới thiệu
Lớp phủ là các đối tượng trên bản đồ được liên kết với toạ độ vĩ độ/kinh độ, vì vậy, chúng sẽ di chuyển khi bạn kéo hoặc thu phóng bản đồ. Để biết thông tin về các loại lớp phủ được xác định trước, hãy xem phần Vẽ trên bản đồ.
Maps JavaScript API cung cấp một lớp OverlayView
để tạo lớp phủ tuỳ chỉnh của riêng bạn. OverlayView
là một lớp cơ sở cung cấp một số phương thức mà bạn phải triển khai khi tạo lớp phủ. Lớp này cũng cung cấp một số phương thức giúp bạn có thể dịch giữa toạ độ màn hình và vị trí trên bản đồ.
Thêm lớp phủ tuỳ chỉnh
Dưới đây là phần tóm tắt các bước cần thiết để tạo lớp phủ tuỳ chỉnh:
- Đặt
prototype
của đối tượng lớp phủ tuỳ chỉnh thành một phiên bản mới củagoogle.maps.OverlayView()
. Trên thực tế, thao tác này sẽ tạo lớp con cho lớp lớp phủ. - Tạo một hàm khởi tạo cho lớp phủ tuỳ chỉnh và đặt mọi tham số khởi tạo.
- Triển khai một phương thức
onAdd()
trong nguyên mẫu của bạn và đính lớp phủ vào bản đồ.OverlayView.onAdd()
sẽ được gọi khi bản đồ sẵn sàng để lớp phủ được đính kèm. - Triển khai phương thức
draw()
trong nguyên mẫu của bạn và xử lý việc hiển thị trực quan đối tượng.OverlayView.draw()
sẽ được gọi khi đối tượng hiển thị lần đầu tiên. - Bạn cũng nên triển khai một phương thức
onRemove()
để dọn dẹp mọi phần tử mà bạn đã thêm vào lớp phủ.
Dưới đây là thông tin chi tiết hơn về từng bước. Bạn có thể xem mã ví dụ đầy đủ và đang hoạt động: xem mã ví dụ.
Tạo lớp con cho lớp phủ
Ví dụ dưới đây sử dụng OverlayView
để tạo một lớp phủ hình ảnh đơn giản.
Bây giờ, chúng ta sẽ tạo một hàm khởi tạo cho lớp USGSOverlay
và khởi tạo các tham số đã truyền dưới dạng thuộc tính của đối tượng mới.
TypeScript
/** * The custom USGSOverlay object contains the USGS image, * the bounds of the image, and a reference to the map. */ class USGSOverlay extends google.maps.OverlayView { private bounds: google.maps.LatLngBounds; private image: string; private div?: HTMLElement; constructor(bounds: google.maps.LatLngBounds, image: string) { super(); this.bounds = bounds; this.image = image; }
JavaScript
/** * The custom USGSOverlay object contains the USGS image, * the bounds of the image, and a reference to the map. */ class USGSOverlay extends google.maps.OverlayView { bounds; image; div; constructor(bounds, image) { super(); this.bounds = bounds; this.image = image; }
Chúng ta chưa thể đính kèm lớp phủ này vào bản đồ trong hàm dựng của lớp phủ. Trước tiên, chúng ta cần đảm bảo rằng tất cả các ngăn của bản đồ đều có sẵn, vì chúng chỉ định thứ tự hiển thị các đối tượng trên bản đồ. API này cung cấp một phương thức trợ giúp cho biết điều này đã xảy ra. Chúng ta sẽ xử lý phương thức đó trong phần tiếp theo.
Khởi chạy lớp phủ
Khi lớp phủ được khởi tạo lần đầu và sẵn sàng hiển thị, chúng ta cần đính kèm lớp phủ đó vào bản đồ thông qua DOM của trình duyệt. API này cho biết lớp phủ đã được thêm vào bản đồ bằng cách gọi phương thức onAdd()
của lớp phủ. Để xử lý phương thức này, chúng ta tạo một <div>
để giữ hình ảnh, thêm một phần tử <img>
, đính kèm phần tử đó vào <div>
, rồi đính kèm lớp phủ vào một trong các ngăn của bản đồ. Ngăn là một nút trong cây DOM.
Các ngăn, thuộc loại MapPanes
, chỉ định thứ tự xếp chồng cho các lớp khác nhau trên bản đồ. Các ngăn sau đây có sẵn và được liệt kê theo thứ tự xếp chồng từ dưới lên trên:
mapPane
là ngăn thấp nhất và nằm phía trên các ô. Có thể không nhận được các sự kiện DOM. (Ngăn 0).overlayLayer
chứa các đường nhiều đoạn, đa giác, lớp phủ mặt đất và lớp phủ lớp ô. Có thể không nhận được các sự kiện DOM. (Ngăn 1).markerLayer
có chứa điểm đánh dấu. Có thể không nhận được các sự kiện DOM. (Ngăn 2).overlayMouseTarget
chứa các phần tử nhận sự kiện DOM. (Ngăn 3).floatPane
chứa cửa sổ thông tin. Nó nằm phía trên tất cả các lớp phủ trên bản đồ. (Ngăn 4).
Vì hình ảnh của chúng ta là "lớp phủ mặt đất" nên chúng ta sẽ sử dụng ngăn overlayLayer
. Khi có ngăn đó, chúng ta sẽ đính kèm đối tượng của mình vào ngăn đó dưới dạng một phần tử con.
TypeScript
/** * onAdd is called when the map's panes are ready and the overlay has been * added to the map. */ onAdd() { this.div = document.createElement("div"); this.div.style.borderStyle = "none"; this.div.style.borderWidth = "0px"; this.div.style.position = "absolute"; // Create the img element and attach it to the div. const img = document.createElement("img"); img.src = this.image; img.style.width = "100%"; img.style.height = "100%"; img.style.position = "absolute"; this.div.appendChild(img); // Add the element to the "overlayLayer" pane. const panes = this.getPanes()!; panes.overlayLayer.appendChild(this.div); }
JavaScript
/** * onAdd is called when the map's panes are ready and the overlay has been * added to the map. */ onAdd() { this.div = document.createElement("div"); this.div.style.borderStyle = "none"; this.div.style.borderWidth = "0px"; this.div.style.position = "absolute"; // Create the img element and attach it to the div. const img = document.createElement("img"); img.src = this.image; img.style.width = "100%"; img.style.height = "100%"; img.style.position = "absolute"; this.div.appendChild(img); // Add the element to the "overlayLayer" pane. const panes = this.getPanes(); panes.overlayLayer.appendChild(this.div); }
Vẽ lớp phủ
Xin lưu ý rằng chúng ta chưa gọi bất kỳ màn hình hiển thị đặc biệt nào trong đoạn mã ở trên. API này sẽ gọi một phương thức draw()
riêng trên lớp phủ bất cứ khi nào cần vẽ lớp phủ trên bản đồ, kể cả khi lớp phủ được thêm lần đầu.
Do đó, chúng ta sẽ triển khai phương thức draw()
này, truy xuất MapCanvasProjection
của lớp phủ bằng cách sử dụng getProjection()
và tính toán toạ độ chính xác để cố định các điểm trên cùng bên phải và dưới cùng bên trái của đối tượng.
Sau đó, chúng ta có thể thay đổi kích thước <div>
. Đến lượt, thao tác này sẽ đổi kích thước hình ảnh cho phù hợp với ranh giới mà chúng ta đã chỉ định trong hàm khởi tạo của lớp phủ.
TypeScript
draw() { // We use the south-west and north-east // coordinates of the overlay to peg it to the correct position and size. // To do this, we need to retrieve the projection from the overlay. const overlayProjection = this.getProjection(); // Retrieve the south-west and north-east coordinates of this overlay // in LatLngs and convert them to pixel coordinates. // We'll use these coordinates to resize the div. const sw = overlayProjection.fromLatLngToDivPixel( this.bounds.getSouthWest() )!; const ne = overlayProjection.fromLatLngToDivPixel( this.bounds.getNorthEast() )!; // Resize the image's div to fit the indicated dimensions. if (this.div) { this.div.style.left = sw.x + "px"; this.div.style.top = ne.y + "px"; this.div.style.width = ne.x - sw.x + "px"; this.div.style.height = sw.y - ne.y + "px"; } }
JavaScript
draw() { // We use the south-west and north-east // coordinates of the overlay to peg it to the correct position and size. // To do this, we need to retrieve the projection from the overlay. const overlayProjection = this.getProjection(); // Retrieve the south-west and north-east coordinates of this overlay // in LatLngs and convert them to pixel coordinates. // We'll use these coordinates to resize the div. const sw = overlayProjection.fromLatLngToDivPixel( this.bounds.getSouthWest(), ); const ne = overlayProjection.fromLatLngToDivPixel( this.bounds.getNorthEast(), ); // Resize the image's div to fit the indicated dimensions. if (this.div) { this.div.style.left = sw.x + "px"; this.div.style.top = ne.y + "px"; this.div.style.width = ne.x - sw.x + "px"; this.div.style.height = sw.y - ne.y + "px"; } }
Xoá lớp phủ tuỳ chỉnh
Chúng ta cũng thêm một phương thức onRemove()
để xoá lớp phủ khỏi bản đồ một cách rõ ràng.
TypeScript
/** * The onRemove() method will be called automatically from the API if * we ever set the overlay's map property to 'null'. */ onRemove() { if (this.div) { (this.div.parentNode as HTMLElement).removeChild(this.div); delete this.div; } }
JavaScript
/** * The onRemove() method will be called automatically from the API if * we ever set the overlay's map property to 'null'. */ onRemove() { if (this.div) { this.div.parentNode.removeChild(this.div); delete this.div; } }
Ẩn và hiện lớp phủ tuỳ chỉnh
Nếu muốn ẩn hoặc hiện lớp phủ thay vì chỉ tạo hoặc xoá lớp phủ, bạn có thể triển khai các phương thức hide()
và show()
của riêng mình để điều chỉnh khả năng hiển thị của lớp phủ. Ngoài ra, bạn có thể tách lớp phủ khỏi DOM của bản đồ, mặc dù thao tác này tốn kém hơn một chút. Xin lưu ý rằng nếu sau đó bạn đính kèm lớp phủ vào DOM của bản đồ, thì lớp phủ sẽ gọi lại phương thức onAdd()
.
Ví dụ sau đây thêm các phương thức hide()
và show()
vào nguyên mẫu của lớp phủ. Các phương thức này sẽ bật/tắt chế độ hiển thị của vùng chứa <div>
. Ngoài ra, chúng ta sẽ thêm phương thức toggleDOM()
để đính kèm hoặc tách lớp phủ khỏi bản đồ.
TypeScript
/** * Set the visibility to 'hidden' or 'visible'. */ hide() { if (this.div) { this.div.style.visibility = "hidden"; } } show() { if (this.div) { this.div.style.visibility = "visible"; } } toggle() { if (this.div) { if (this.div.style.visibility === "hidden") { this.show(); } else { this.hide(); } } } toggleDOM(map: google.maps.Map) { if (this.getMap()) { this.setMap(null); } else { this.setMap(map); } }
JavaScript
/** * Set the visibility to 'hidden' or 'visible'. */ hide() { if (this.div) { this.div.style.visibility = "hidden"; } } show() { if (this.div) { this.div.style.visibility = "visible"; } } toggle() { if (this.div) { if (this.div.style.visibility === "hidden") { this.show(); } else { this.hide(); } } } toggleDOM(map) { if (this.getMap()) { this.setMap(null); } else { this.setMap(map); } }
Thêm các nút điều khiển
Để kích hoạt các phương thức toggle
và toggleDom
, các nút điều khiển sẽ được thêm vào bản đồ.
TypeScript
const toggleButton = document.createElement("button"); toggleButton.textContent = "Toggle"; toggleButton.classList.add("custom-map-control-button"); const toggleDOMButton = document.createElement("button"); toggleDOMButton.textContent = "Toggle DOM Attachment"; toggleDOMButton.classList.add("custom-map-control-button"); toggleButton.addEventListener("click", () => { overlay.toggle(); }); toggleDOMButton.addEventListener("click", () => { overlay.toggleDOM(map); }); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton);
JavaScript
const toggleButton = document.createElement("button"); toggleButton.textContent = "Toggle"; toggleButton.classList.add("custom-map-control-button"); const toggleDOMButton = document.createElement("button"); toggleDOMButton.textContent = "Toggle DOM Attachment"; toggleDOMButton.classList.add("custom-map-control-button"); toggleButton.addEventListener("click", () => { overlay.toggle(); }); toggleDOMButton.addEventListener("click", () => { overlay.toggleDOM(map); }); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton);
Mã mẫu hoàn chỉnh
Sau đây là mã mẫu hoàn chỉnh:
TypeScript
// This example adds hide() and show() methods to a custom overlay's prototype. // These methods toggle the visibility of the container <div>. // overlay to or from the map. function initMap(): void { const map = new google.maps.Map( document.getElementById("map") as HTMLElement, { zoom: 11, center: { lat: 62.323907, lng: -150.109291 }, mapTypeId: "satellite", } ); const bounds = new google.maps.LatLngBounds( new google.maps.LatLng(62.281819, -150.287132), new google.maps.LatLng(62.400471, -150.005608) ); // The photograph is courtesy of the U.S. Geological Survey. let image = "https://developers.google.com/maps/documentation/javascript/"; image += "examples/full/images/talkeetna.png"; /** * The custom USGSOverlay object contains the USGS image, * the bounds of the image, and a reference to the map. */ class USGSOverlay extends google.maps.OverlayView { private bounds: google.maps.LatLngBounds; private image: string; private div?: HTMLElement; constructor(bounds: google.maps.LatLngBounds, image: string) { super(); this.bounds = bounds; this.image = image; } /** * onAdd is called when the map's panes are ready and the overlay has been * added to the map. */ onAdd() { this.div = document.createElement("div"); this.div.style.borderStyle = "none"; this.div.style.borderWidth = "0px"; this.div.style.position = "absolute"; // Create the img element and attach it to the div. const img = document.createElement("img"); img.src = this.image; img.style.width = "100%"; img.style.height = "100%"; img.style.position = "absolute"; this.div.appendChild(img); // Add the element to the "overlayLayer" pane. const panes = this.getPanes()!; panes.overlayLayer.appendChild(this.div); } draw() { // We use the south-west and north-east // coordinates of the overlay to peg it to the correct position and size. // To do this, we need to retrieve the projection from the overlay. const overlayProjection = this.getProjection(); // Retrieve the south-west and north-east coordinates of this overlay // in LatLngs and convert them to pixel coordinates. // We'll use these coordinates to resize the div. const sw = overlayProjection.fromLatLngToDivPixel( this.bounds.getSouthWest() )!; const ne = overlayProjection.fromLatLngToDivPixel( this.bounds.getNorthEast() )!; // Resize the image's div to fit the indicated dimensions. if (this.div) { this.div.style.left = sw.x + "px"; this.div.style.top = ne.y + "px"; this.div.style.width = ne.x - sw.x + "px"; this.div.style.height = sw.y - ne.y + "px"; } } /** * The onRemove() method will be called automatically from the API if * we ever set the overlay's map property to 'null'. */ onRemove() { if (this.div) { (this.div.parentNode as HTMLElement).removeChild(this.div); delete this.div; } } /** * Set the visibility to 'hidden' or 'visible'. */ hide() { if (this.div) { this.div.style.visibility = "hidden"; } } show() { if (this.div) { this.div.style.visibility = "visible"; } } toggle() { if (this.div) { if (this.div.style.visibility === "hidden") { this.show(); } else { this.hide(); } } } toggleDOM(map: google.maps.Map) { if (this.getMap()) { this.setMap(null); } else { this.setMap(map); } } } const overlay: USGSOverlay = new USGSOverlay(bounds, image); overlay.setMap(map); const toggleButton = document.createElement("button"); toggleButton.textContent = "Toggle"; toggleButton.classList.add("custom-map-control-button"); const toggleDOMButton = document.createElement("button"); toggleDOMButton.textContent = "Toggle DOM Attachment"; toggleDOMButton.classList.add("custom-map-control-button"); toggleButton.addEventListener("click", () => { overlay.toggle(); }); toggleDOMButton.addEventListener("click", () => { overlay.toggleDOM(map); }); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton); } declare global { interface Window { initMap: () => void; } } window.initMap = initMap;
JavaScript
// This example adds hide() and show() methods to a custom overlay's prototype. // These methods toggle the visibility of the container <div>. // overlay to or from the map. function initMap() { const map = new google.maps.Map(document.getElementById("map"), { zoom: 11, center: { lat: 62.323907, lng: -150.109291 }, mapTypeId: "satellite", }); const bounds = new google.maps.LatLngBounds( new google.maps.LatLng(62.281819, -150.287132), new google.maps.LatLng(62.400471, -150.005608), ); // The photograph is courtesy of the U.S. Geological Survey. let image = "https://developers.google.com/maps/documentation/javascript/"; image += "examples/full/images/talkeetna.png"; /** * The custom USGSOverlay object contains the USGS image, * the bounds of the image, and a reference to the map. */ class USGSOverlay extends google.maps.OverlayView { bounds; image; div; constructor(bounds, image) { super(); this.bounds = bounds; this.image = image; } /** * onAdd is called when the map's panes are ready and the overlay has been * added to the map. */ onAdd() { this.div = document.createElement("div"); this.div.style.borderStyle = "none"; this.div.style.borderWidth = "0px"; this.div.style.position = "absolute"; // Create the img element and attach it to the div. const img = document.createElement("img"); img.src = this.image; img.style.width = "100%"; img.style.height = "100%"; img.style.position = "absolute"; this.div.appendChild(img); // Add the element to the "overlayLayer" pane. const panes = this.getPanes(); panes.overlayLayer.appendChild(this.div); } draw() { // We use the south-west and north-east // coordinates of the overlay to peg it to the correct position and size. // To do this, we need to retrieve the projection from the overlay. const overlayProjection = this.getProjection(); // Retrieve the south-west and north-east coordinates of this overlay // in LatLngs and convert them to pixel coordinates. // We'll use these coordinates to resize the div. const sw = overlayProjection.fromLatLngToDivPixel( this.bounds.getSouthWest(), ); const ne = overlayProjection.fromLatLngToDivPixel( this.bounds.getNorthEast(), ); // Resize the image's div to fit the indicated dimensions. if (this.div) { this.div.style.left = sw.x + "px"; this.div.style.top = ne.y + "px"; this.div.style.width = ne.x - sw.x + "px"; this.div.style.height = sw.y - ne.y + "px"; } } /** * The onRemove() method will be called automatically from the API if * we ever set the overlay's map property to 'null'. */ onRemove() { if (this.div) { this.div.parentNode.removeChild(this.div); delete this.div; } } /** * Set the visibility to 'hidden' or 'visible'. */ hide() { if (this.div) { this.div.style.visibility = "hidden"; } } show() { if (this.div) { this.div.style.visibility = "visible"; } } toggle() { if (this.div) { if (this.div.style.visibility === "hidden") { this.show(); } else { this.hide(); } } } toggleDOM(map) { if (this.getMap()) { this.setMap(null); } else { this.setMap(map); } } } const overlay = new USGSOverlay(bounds, image); overlay.setMap(map); const toggleButton = document.createElement("button"); toggleButton.textContent = "Toggle"; toggleButton.classList.add("custom-map-control-button"); const toggleDOMButton = document.createElement("button"); toggleDOMButton.textContent = "Toggle DOM Attachment"; toggleDOMButton.classList.add("custom-map-control-button"); toggleButton.addEventListener("click", () => { overlay.toggle(); }); toggleDOMButton.addEventListener("click", () => { overlay.toggleDOM(map); }); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton); map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton); } window.initMap = initMap;
CSS
/* * Always set the map height explicitly to define the size of the div element * that contains the map. */ #map { height: 100%; } /* * Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } .custom-map-control-button { background-color: #fff; border: 0; border-radius: 2px; box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3); margin: 10px; padding: 0 0.5em; font: 400 18px Roboto, Arial, sans-serif; overflow: hidden; height: 40px; cursor: pointer; } .custom-map-control-button:hover { background: rgb(235, 235, 235); }
HTML
<html> <head> <title>Showing/Hiding Overlays</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div id="map"></div> <!-- The `defer` attribute causes the script to execute after the full HTML document has been parsed. For non-blocking uses, avoiding race conditions, and consistent behavior across browsers, consider loading using Promises. See https://developers.google.com/maps/documentation/javascript/load-maps-js-api for more information. --> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly" defer ></script> </body> </html>