Lớp phủ tuỳ chỉnh

Chọn nền tảng: Android iOS JavaScript

Giới thiệu

Lớp phủ là những đối tượng trên bản đồ được liên kết với toạ độ vĩ độ/kinh độ, để chúng di chuyển khi bạn kéo hoặc thu phóng bản đồ. Để biết thông tin về quy tắc được xác định trước loại lớp phủ, xem Vẽ trên bản đồ.

API JavaScript của Maps cung cấp Lớp OverlayView cho 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 pháp bạn phải triển khai khi tạo lớp phủ. Chiến lược phát hành đĩa đơn cũng cung cấp một vài phương thức giúp có thể dịch giữa toạ độ màn hình và các vị trí trên bản đồ.

Thêm lớp phủ tuỳ chỉnh

Dưới đây là bả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 thực thể mới của google.maps.OverlayView() Trên thực tế, việc này sẽ tạo lớp phủ con .
  • Tạo một hàm khởi tạo cho lớp phủ tuỳ chỉnh và đặt hoạt động khởi tạo tham số.
  • Triển khai phương thức onAdd() trong nguyên mẫu và đính kèm lớp phủ vào bản đồ. OverlayView.onAdd() sẽ được gọi khi bản đồ sẵn sàng cho lớp phủ cần đính kèm.
  • Triển khai phương thức draw() trong nguyên mẫu và xử lý hình ảnh hiển thị đối tượng của bạn. OverlayView.draw() sẽ được gọi khi đối tượng được hiển thị đầu tiên.
  • Bạn cũng nên triển khai phương thức onRemove() để dọn dẹp mọi phần tử mà bạn đã thêm bên trong lớp phủ.

Dưới đây là thông tin chi tiết về từng bước. Bạn có thể xem ví dụ đầy đủ hoạt động mã: xem mã ví dụ.

Lớp con của lớp phủ

Ví dụ bên dưới sử dụng OverlayView để tạo một lớp ả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 đã truyền tham số 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 tôi chưa thể đính kèm lớp phủ này vào bản đồ trong hàm khởi tạo của lớp phủ. Đầu tiên, chúng ta cần phải đảm bảo rằng tất cả các ngăn của bản đồ đều sẵn có, vì chúng chỉ định thứ tự các đối tượng được hiển thị trên bản đồ. API này cung cấp phương thức trợ giúp cho biết điều này đã xảy ra. Chúng tôi 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 tạo thực thể lần đầu và sẵn sàng hiển thị, chúng ta cần đính kèm nó lên bản đồ qua DOM của trình duyệt. API này cho biết rằng lớp phủ đã được đượ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ý vấn đề này phương thức chúng ta tạo một <div> để lưu giữ hình ảnh, thêm một phần tử <img>, đính kèm phần tử đó vào <div>, sau đó đí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.

Loại ngăn 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 là có sẵn và được liệt kê theo thứ tự xếp chồng từ dưới lên trên lên đầu:

  • mapPane là ngăn thấp nhất và nằm phía trên các ô. Có thể không nhận được DOM sự kiện. (Pane 0).
  • overlayLayer chứa hình nhiều đường, đa giác, lớp phủ mặt đất và lớp ô lớp phủ. Có thể không nhận được các sự kiện DOM. (Pane 1).
  • markerLayer chứa điểm đánh dấu. Có thể không nhận được các sự kiện DOM. (Pane 2).
  • overlayMouseTarget chứa các phần tử nhận sự kiện DOM. (Pane 3).
  • floatPane chứa cửa sổ thông tin. Nó ở trên tất cả các lớp phủ bản đồ. (Pane) 4).

Vì hình ảnh của chúng tôi là "lớp phủ mặt đất" chúng ta sẽ dùng ngăn overlayLayer. Thời gian chúng ta 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 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ủ

Lưu ý rằng chúng ta chưa gọi ra bất kỳ hình ảnh hiển thị đặc biệt nào trong mã ở trên. Chiến lược phát hành đĩa đơn API sẽ gọi một phương thức draw() riêng biệt trên lớp phủ bất cứ khi nào lớp phủ cần vẽ lớp phủ trên bản đồ, kể cả khi được thêm lần đầu tiên.

Do đó, chúng ta sẽ triển khai phương thức draw() này, truy xuất MapCanvasProjection bằng getProjection() rồi tính chính xác toạ độ nơi neo 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ể đổi kích thước <div>. Đổi lại, thao tác này sẽ đổi kích thước hình ảnh cho phù hợp với giới hạn 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á sạch lớp phủ khỏi bản đồ.

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 thị lớp phủ tuỳ chỉnh

Nếu bạn muốn ẩn hoặc hiển thị 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()show() của riêng mình để điều chỉnh lớp phủ khả năng hiển thị. 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 sẽ tốn kém hơn một chút. Lưu ý rằng nếu sau đó bạn đính kèm lại vào DOM của bản đồ, nó sẽ gọi lại phương thức onAdd() của lớp phủ.

Ví dụ sau đây sẽ thêm các phương thức hide()show() vào lớp phủ nguyên mẫu bật/tắt chế độ hiển thị của vùng chứa <div>. Ngoài ra, chúng tôi thêm phương thức toggleDOM(), phương thức này sẽ đính kèm hoặc tách lớp phủ vào/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 chế độ điều khiển nút

Để kích hoạt phương thức toggletoggleDom, các nút điều khiển đượ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);

Hoàn tất mã mẫu

Dưới đâ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>

Thử dùng mẫu