Blazor组件自做五: 使用JS隔离封装Google地图

本文档详细介绍了如何在Blazor项目中使用JS隔离封装谷歌地图API,包括异步加载地图、初始化地图、销毁地图以及处理错误的方法。通过创建组件和代码后置类,展示了如何在页面中调用并展示地图。同时,提供了添加地图功能的步骤,以及配置和使用示例。

运行截图

演示地址

正式开始

1. 谷歌地图API

谷歌开发文档

开始学习 Maps JavaScript API 的最简单方法是查看一个简单示例。以下示例显示以澳大利亚新南威尔士州悉尼为中心的地图。

异步加载例子

JS代码

let map;

function initMap() {
  map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: -34.397, lng: 150.644 },
    zoom: 8,
  });
}

HTML代码

<!DOCTYPE html>
<html>
  <head>
    <title>Simple Map</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script src="./index.js"></script>
  </head>
  <body>
    <div id="map"></div>

    <!-- Async script executes immediately and must be after any DOM elements used in callback. -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap&v=weekly"
      async
    ></script>
  </body>
</html>

同步加载例子。我们省略了加载 API async 的标签中的属性script,也省略了回调参数

JS代码

const map = new google.maps.Map(document.getElementById("map"), {
  center: { lat: -34.397, lng: 150.644 },
  zoom: 8,
});

HTML代码

<!DOCTYPE html>
<html>
  <head>
    <title>Synchronous Loading</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <link rel="stylesheet" type="text/css" href="./style.css" />
  </head>
  <body>
    <div id="map"></div>
    <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=weekly"></script>
    <script src="./index.js"></script>
  </body>
</html>

Polyfill 是一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。

比如说 polyfill 可以让 IE7 使用 Silverlight 插件来模拟 HTML Canvas 元素的功能,或模拟 CSS 实现 rem 单位的支持,或 text-shadow,或其他任何你想要的功能。

由于blazor能运行的浏览器都是比较新的,所以我们不需要运行此polyfill.min.js脚本.

2. 在文件夹wwwroot/lib,添加google子文件夹,添加map.js文件.

2.1 用代码方式异步加载API,脚本生成新的 head > script 元素添加到页面文档,使用异步加载回调 initGoogleMaps 方法初始化地图.
export function addScript(key, elementId, dotnetRef, backgroundColor, controlSize) {
    if (!key || !elementId) {
        return;
    }

    let url = "https://maps.googleapis.com/maps/api/js?key=";
    let scriptsIncluded = false;

    let scriptTags = document.querySelectorAll('head > script');
    scriptTags.forEach(scriptTag => {
        if (scriptTag) {
            let srcAttribute = scriptTag.getAttribute('src');
            if (srcAttribute && srcAttribute.startsWith(url)) {
                scriptsIncluded = true;
                return true;
            }
        }
    });

    if (scriptsIncluded) { //防止多次向页面添加 JS 脚本.Prevent adding JS scripts to page multiple times.
        if (window.google) {
            initMaps(elementId); //页面已导航. Page was navigated
        }
        return true;
    }

    url = url + key + "&callback=initGoogleMaps&libraries=&v=weekly";
    let script = document.createElement('script');
    script.src = url;
    script.defer = true;
    document.head.appendChild(script);
    return false;
}
2.2 方法初始化地图,以及dispose().
export function initMaps(elementId) {
    var latlng = new google.maps.LatLng(40.26982, -3.758269);
    var options = {
        zoom: 14, center: latlng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    var map = new google.maps.Map(elementId, options);
    console.log(map);
    return map;
}
//Remove elementId with data
function removeElementIdWithDotnetRef(dict, elementId) {
    for (let i = 0; i < dict.length; i++) {
        if (dict[i].key === elementId) {
            dict.splice(i, 1);
            break;
        }
    }
}
//Dispose
export function dispose(elementId) {
    if (elementId) {
        let mapWithDotnetRef = getElementIdWithDotnetRef(_mapsElementDict, elementId);
        mapWithDotnetRef.map = null;
        mapWithDotnetRef.ref = null;

        removeElementIdWithDotnetRef(_mapsElementDict, elementId);
    }
}

3. 打开Components文件夹 , 新建 Google文件夹,添加 Map.razor 文件.

将来也许会添加更多API封装,类似云盘/文档,所以建立文件夹备用.

由于不确定是否完成初始化,网络或者各种原因,故这里做了一个低级而万能的while循环检测map是否载入成功.如果各位小伙伴还有更优雅的方法,欢迎在底下留言,互通有无,cv万岁!

while (!(await Init()))
{
    await Task.Delay(500);
}

其中一个参数是 [Parameter] public string? Key { get; set; },也就是你的 GoogleKey 在开发者后台可以获取,可以组件形式调用的时候设置, 不设置,即为空则在 IConfiguration 服务获取 “GoogleKey” , 默认在 appsettings.json 文件配置 “GoogleKey”="xxxxxxx"即可.

页面使用调用注入的服务IConfiguration使用如下代码

@inject IConfiguration config

完整代码如下

@implements IAsyncDisposable
@inject IJSRuntime JS
@namespace Blazor100.Components
@inject IConfiguration config

<div @ref="map" style="@Style">
</div>
<button class="btn btn-primary" type="button" onclick="@(async()=>await OnBtnClick())">Reset</button>

@code{

    /// <summary>
    /// 获得/设置 错误回调方法
    /// </summary>
    [Parameter]
    public Func<string, Task>? OnError { get; set; }

    /// <summary>
    /// 获得/设置 GoogleKey<para></para>
    /// 为空则在 IConfiguration 服务获取 "GoogleKey" , 默认在 appsettings.json 文件配置
    /// </summary>
    [Parameter]
    public string? Key { get; set; }

    /// <summary>
    /// 获得/设置 style
    /// </summary>
    [Parameter]
    public string Style { get; set; } = "height:700px;width:100%;";

    ElementReference map { get; set; }

    private IJSObjectReference? module;
    private string key = String.Empty;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            key = Key ?? config["GoogleKey"];
            module = await JS.InvokeAsync<IJSObjectReference>("import", "./lib/google/map.js");
            while (!(await Init()))
            {
                await Task.Delay(500);
            }
        }
    }


    public async Task<bool> Init() => await module!.InvokeAsync<bool>("addScript", new object?[] { key, map, null, null, null });

    public async Task OnBtnClick() => await module!.InvokeVoidAsync("addScript", new object?[] { key, map, null, null, null });

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        if (module is not null)
        {
            //await module.InvokeVoidAsync("destroy", Options);
            await module.DisposeAsync();
        }
    }
}

4. Pages文件夹添加MapsPage.razor文件,用于演示组件调用.
4.1 代码后置

代码后置是微软的一项技术,也是我们编写 .NET Core常用的编码方式。具体方式就像.razor和代码文件.cs两个文件相互关联构成一个页面。一般情况下,.razor文件中没有代码、只有组件和HTML代码,而在.cs文件中编写相关的代码。这样做的好处就是代码和页面内容分离,使代码更清晰。

阅读:分部类

现在尝试使用代码后置的写法,razor只写Html和路由

@page "/maps"

<h3>谷歌地图 Maps</h3>

<p>@message</p>

<Map OnError="@OnError" />

右键添加cs文件,命名为 MapsPage.razor.cs , 文件名命名方式为razor页面文件名全名后加.cs, 如果解决方案资源管理器默认开启了文件嵌套,这两个文件会合并在一起并且前面有三角符号,展开可看到后置的cs代码.

好了,现在终于可以正常愉快的写cs代码了,不用继续忍受VS2022的莫名其妙的红线和编辑razor文件带来的内存泄漏干扰, 😄 .

MapsPage.razor.cs 完整代码

using Blazor100.Components;

namespace Blazor100.Pages;

/// <summary>
/// 谷歌地图 Maps
/// </summary>
public sealed partial class MapsPage
{

    private string message;


    private Task OnError(string message)
    {
        this.message = message;
        StateHasChanged();
        return Task.CompletedTask;
    }

}

5. _Imports.razor加入一行引用组件的命名空间.

@using Blazor100.Components

6. 首页引用组件演示页 <MapsPage />或者 Shared/NavMenu.razor 添加导航

<div class="nav-item px-3">
    <NavLink class="nav-link" href="maps">
        谷歌地图
    </NavLink>
</div>

7. F5运行程序

8. 谷歌地图API还有若干的功能没有封装进来,此处只是抛砖引玉,后续版我会持续加进正式开源组件Densen.Component.Blazor中.

至此,使用JS隔离封装Google地图大功告成! Happy coding!

一个老外(西班牙)编写的控件,封装了全部google maps api ,使用在DELPHI中使用谷歌地图变得非常简单 GMLib - Google Maps Library Send suggestions to gmlib@cadetill.com Supported Delphi version: Delphi 6, 7, 2007, 2010, XE2, XE3 Tested Windows Versions: XP, 2003, Vista, 7 Change History january 14, 2013 - Google Maps Library v0.1.9 - Improvement: Compatible with FMX framework. - Improvement: About all Geometry Library coded. - bug fixed: Some bugs fixes. - Attempt to do compatible with DCEF components. October 05, 2012 - Google Maps Library v0.1.8 - Improvement: Compiled under XE3 - Improvement: new component added, the TGMElevation. - bug fixed: General -> fixed all memory leaks found - bug fixed: TGMDirection -> the OnDirectionsChanged event was not triggered - Improvement: TBasePolyline -> class TLinePoints and TLinePoint is disassociated from TPolyline and they are transferred to GMClasses - Improvement: TBasePolyline -> implements ILinePoint interface September 11, 2012 - Google Maps Library v0.1.7 - bug fixed: some memory leaks fixed (there is still some) (thanks Donovan) - Improvement: TGMCircle -> modified all Set and ShowElements methods to use the new method ChangeProperties inherited from TLinkedComponent - Improvement: GMFunctions -> added new functions of transformation types - Improvement: TGMGeoCode-> added boolean property PaintMarkerFound. To true, all markers are automatically generated (if a TGMMarker is linked) (by Luis Joaquin Sencion) - Improvement: TGMGeoCode-> generated URL is encoded in UTF8 to avoid problems with special characters (? accents, ....) - Improvement: TGMMap.TNonVisualProp -> added MapMarker property. True if Map Maker tiles should be used instead of regular tiles. - Improvement: TLatLngEvent -> the events of this type now have a new parametre, X and Y, of Real type, with information of point (X,Y) - Improvement: TLinkedComponent -> added ShowInfoWinMouseOver boolean property. If true, show the InfoWindows when mouse is over the object. Now
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Densen2014

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值