7. 使用JS隔离制作定位 持续定位组件 - densen2014/Blazor100 GitHub Wiki

Blazor组件自做七: 使用JS隔离制作定位/持续定位组件

1. 运行截图

演示地址

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

本组件主要是调用浏览器两个API实现基于浏览器的定位功能,现代桌面和移动端都支持.包括MAUI Blazor 😃

navigator.geolocation.getCurrentPosition
navigator.geolocation.watchPosition

2.1 获取定位

其中持续定位watchPosition方法会通过wrapper.invokeMethodAsync('UpdateWatchID', id)返回监听器ID到c#存起来,移除的监听器和注销时候用到.

js代码
export function getLocation(wrapper, getPosition = true) {
    console.log('start ' + (getPosition ? 'getLocation' : 'watchPosition'));
    var currentDistance = 0.0;
    var totalDistance = 0.0;
    var lastLat;
    var lastLong;
    var status;

    if (getPosition) getCurrentPosition(); else watchPosition();

    Number.prototype.toRadians = function () {
        return this * Math.PI / 180;
    }


    function distance(latitude1, longitude1, latitude2, longitude2) {
        // R is the radius of the earth in kilometers
        var R = 6371;

        var deltaLatitude = (latitude2 - latitude1).toRadians();
        var deltaLongitude = (longitude2 - longitude1).toRadians();
        latitude1 = latitude1.toRadians(), latitude2 = latitude2.toRadians();

        var a = Math.sin(deltaLatitude / 2) *
            Math.sin(deltaLatitude / 2) +
            Math.cos(latitude1) *
            Math.cos(latitude2) *
            Math.sin(deltaLongitude / 2) *
            Math.sin(deltaLongitude / 2);

        var c = 2 * Math.atan2(Math.sqrt(a),
            Math.sqrt(1 - a));
        var d = R * c;
        return d;
    }


    function updateStatus(message) {
        status = message;
        wrapper.invokeMethodAsync('UpdateStatus', message);
    }

    function watchPosition() {
        if (navigator.geolocation) {
            status = "HTML5 Geolocation is supported in your browser.";
            updateStatus(status);
            var id = navigator.geolocation.watchPosition(updateLocation,
                handleLocationError,
                { maximumAge: 20000 });
            wrapper.invokeMethodAsync('UpdateWatchID', id);
        }
    }

    function getCurrentPosition() {
        if (navigator.geolocation) {
            updateStatus("HTML5 Geolocation is supported in your browser.");
            navigator.geolocation.getCurrentPosition(updateLocation,
                handleLocationError);
        }
    }

    function updateLocation(position) {
        var latitude = position.coords.latitude;
        var longitude = position.coords.longitude;
        var accuracy = position.coords.accuracy;
        var timestamp = position.timestamp;

        // sanity test... don't calculate distance if accuracy
        // value too large
        if (accuracy >= 500) {
            updateStatus("Need more accurate values to calculate distance.");
        }

        // calculate distance
        currentDistance = 0.0;
        if ((lastLat != null) && (lastLong != null)) {
            currentDistance = distance(latitude, longitude, lastLat, lastLong);
            totalDistance += currentDistance;
        }


        lastLat = latitude;
        lastLong = longitude;

        updateStatus("Location successfully updated.");

        console.log("updateLocation end");
        var geolocationitem = {
            "Status": status,
            "Latitude": latitude,
            "Longitude": longitude,
            "Accuracy": accuracy,
            "Timestamp": timestamp,
            "CurrentDistance": currentDistance,
            "TotalDistance": totalDistance,
            "LastLat": lastLat,
            "LastLong": lastLong,
        };
        wrapper.invokeMethodAsync('GetResult', geolocationitem);
    }

    function handleLocationError(error) {
        switch (error.code) {
            case 0:
                updateStatus("There was an error while retrieving your location: " + error.message);
                break;
            case 1:
                updateStatus("The user prevented this page from retrieving a location.");
                break;
            case 2:
                updateStatus("The browser was unable to determine your location: " + error.message);
                break;
            case 3:
                updateStatus("The browser timed out before retrieving the location.");
                break;
        }
    }

}

2.2 组件页面点击停止持续定位,把监听器ID传入移除的监听器.

export function clearWatchLocation(wrapper,id) {
    //扩展阅读:移除的监听器
    //id = navigator.geolocation.watchPosition(success, error, options);
    console.log('clearWatch ' + id);
    navigator.geolocation.clearWatch(id);
    wrapper.invokeMethodAsync('UpdateStatus', 'clearWatch ok');
}

3. 打开Components文件夹 , 新建Geolocation文件夹,新建三个文件

3.1 GeolocationItem.cs 定位数据类

cs代码
using System;
using System.ComponentModel;

namespace Blazor100.Components
{

    /// <summary>
    /// 定位数据类
    /// </summary>
    public class Geolocationitem
    {
        /// <summary>
        /// 状态
        /// </summary>
        /// <returns></returns>
        [DisplayName("状态")]
        public string? Status { get; set; }

        /// <summary>
        /// 纬度
        /// </summary>
        /// <returns></returns>
        [DisplayName("纬度")]
        public decimal Latitude { get; set; }

        /// <summary>
        /// 经度
        /// </summary>
        /// <returns></returns>
        [DisplayName("经度")]
        public decimal Longitude { get; set; }

        /// <summary>
        /// 准确度(米)<para></para>
        /// 将以m指定维度和经度值与实际位置的差距,置信度为95%.
        /// </summary>
        [DisplayName("准确度(米)")]
        public decimal Accuracy { get; set; }

        /// <summary>
        /// 时间戳
        /// </summary>
        [DisplayName("时间戳")]
        public long Timestamp { get; set; }

        /// <summary>
        /// 时间
        /// </summary>
        [DisplayName("时间")]
        public DateTime LastUpdateTime { get => UnixTimeStampToDateTime(Timestamp); }

        /// <summary>
        /// 移动距离
        /// </summary>
        [DisplayName("移动距离")]
        public decimal CurrentDistance { get; set; } = 0.0M;

        /// <summary>
        /// 总移动距离
        /// </summary>
        [DisplayName("总移动距离")]
        public decimal TotalDistance { get; set; } = 0.0M;

        /// <summary>
        /// 最后一次获取到的纬度
        /// </summary>
        [DisplayName("最后一次获取到的纬度")]
        public decimal LastLat { get; set; }

        /// <summary>
        /// 最后一次获取到的经度
        /// </summary>
        [DisplayName("最后一次获取到的经度")]
        public decimal LastLong { get; set; }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="unixTimeStamp"></param>
        /// <returns></returns>
        public static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
        {
            // Unix timestamp is seconds past epoch
            System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
            dtDateTime = dtDateTime.AddMilliseconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

    }
}

3.1 Geolocation.razor 组件razor

@implements IAsyncDisposable
@namespace Blazor100.Components

<div @ref="GeolocationElement">
    @if (ShowButtons)
    {
        if (WatchID == null)
        {
            <button class="btn btn-primary" type="button" onclick="@GetLocation">@GetLocationButtonText</button>
            <button class="btn btn-primary" type="button" onclick="@WatchPosition">@WatchPositionButtonText</button>
        }
        else
        {
            <button class="btn btn-primary" type="button" onclick="@ClearWatch">@ClearWatchPositionButtonText</button>}
    }
</div>

3.2 Geolocation.razor.cs 组件代码

cs代码
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Diagnostics.CodeAnalysis;

namespace Blazor100.Components;

/// <summary>
/// Geolocation 组件基类
/// <para></para>
/// 扩展阅读:Chrome中模拟定位信息,清除定位信息<para></para>
/// https://blog.csdn.net/u010844189/article/details/81163438
/// </summary>
public partial class Geolocation
{
    [Inject] IJSRuntime? JS { get; set; }

    /// <summary>
    /// 获得/设置 定位
    /// </summary>
    [Parameter]
    [NotNull]
    public string? GeolocationInfo { get; set; }

    /// <summary>
    /// 获得/设置 获取位置按钮文字 默认为 获取位置
    /// </summary>
    [Parameter]
    [NotNull]
    public string? GetLocationButtonText { get; set; } = "获取位置";

    /// <summary>
    /// 获得/设置 获取持续定位监听器ID
    /// </summary>
    [Parameter]
    public long? WatchID { get; set; }

    /// <summary>
    /// 获得/设置 获取移动距离追踪按钮文字 默认为 移动距离追踪
    /// </summary>
    [Parameter]
    [NotNull]
    public string? WatchPositionButtonText { get; set; } = "移动距离追踪";

    /// <summary>
    /// 获得/设置 获取停止追踪按钮文字 默认为 停止追踪
    /// </summary>
    [Parameter]
    [NotNull]
    public string? ClearWatchPositionButtonText { get; set; } = "停止追踪";

    /// <summary>
    /// 获得/设置 是否显示默认按钮界面
    /// </summary>
    [Parameter]
    public bool ShowButtons { get; set; } = true;

    /// <summary>
    /// 
    /// </summary>
    protected ElementReference GeolocationElement { get; set; }

    /// <summary>
    /// 获得/设置 定位结果回调方法
    /// </summary>
    [Parameter]
    public Func<Geolocationitem, Task>? OnResult { get; set; }

    /// <summary>
    /// 获得/设置 状态更新回调方法
    /// </summary>
    [Parameter]
    public Func<string, Task>? OnUpdateStatus { get; set; }

    private IJSObjectReference? module;
    private DotNetObjectReference<Geolocation>? InstanceGeo { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        try
        {
            if (firstRender)
            {
                module = await JS!.InvokeAsync<IJSObjectReference>("import", "./lib/geolocation/geolocation.js");
                InstanceGeo = DotNetObjectReference.Create(this);
            }
        }
        catch (Exception e)
        {
            if (OnError != null) await OnError.Invoke(e.Message);
        }
    }

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


    /// <summary>
    /// 获取定位
    /// </summary>
    public virtual async Task GetLocation()
    {
        try
        {
            await module!.InvokeVoidAsync("getLocation", InstanceGeo);
        }
        catch (Exception e)
        {
            if (OnError != null) await OnError.Invoke(e.Message);
        }
    }

    /// <summary>
    /// 持续定位
    /// </summary>
    public virtual async Task WatchPosition()
    {
        try
        {
            await module!.InvokeVoidAsync("getLocation", InstanceGeo, false);
        }
        catch (Exception e)
        {
            if (OnError != null) await OnError.Invoke(e.Message);
        }
    }

    /// <summary>
    /// 持续定位
    /// </summary>
    public virtual async Task ClearWatch()
    {
        await module!.InvokeVoidAsync("clearWatchLocation", InstanceGeo, WatchID);
        WatchID = null;
    }

    /// <summary>
    /// 定位完成回调方法
    /// </summary>
    /// <param name="geolocations"></param>
    /// <returns></returns>
    [JSInvokable]
    public async Task GetResult(Geolocationitem geolocations)
    {
        try
        {
            if (OnResult != null) await OnResult.Invoke(geolocations);
        }
        catch (Exception e)
        {
            if (OnError != null) await OnError.Invoke(e.Message);
        }
    }

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

    /// <summary>
    /// 状态更新回调方法
    /// </summary>
    /// <param name="status"></param>
    /// <returns></returns>
    [JSInvokable]
    public async Task UpdateStatus(string status)
    {
        if (OnUpdateStatus != null) await OnUpdateStatus.Invoke(status);
    }

    /// <summary>
    /// 监听器ID回调方法
    /// </summary>
    /// <param name="watchID"></param>
    /// <returns></returns>
    [JSInvokable]
    public Task UpdateWatchID(long watchID)
    {
        this.WatchID = watchID;
        return Task.CompletedTask;
    }

}

4. Pages文件夹添加GeolocationPage.razor文件,用于演示组件调用.

4.1 GeolocationPage.razor

razor代码
@page "/geolocations"

<h3>定位/持续定位 Geolocation</h3>

<p>@message</p>

<Blazor100.Components.Geolocation OnResult="@OnResult" OnUpdateStatus="@OnUpdateStatus" OnError="@OnError" />

<p>@status</p>

<div class="table-container">

    <div class="table-toolbar">
    </div>

    <div class="table-wrapper">
        <table class="table is-single table-demo">
            <colgroup>
                <col>
                <col>
                <col>
            </colgroup>
            <thead>
                <tr>
                    <th><div class="table-cell"><span class="table-text">纬度</span></div></th>
                    <th><div class="table-cell"><span class="table-text">经度</span></div></th>
                    <th><div class="table-cell"><span class="table-text">准确度(米)</span></div></th>
                    <th><div class="table-cell"><span class="table-text">时间戳</span></div></th>
                    <th><div class="table-cell"><span class="table-text">时间</span></div></th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><div class="table-cell">@geolocations?.Latitude</div></td>
                    <td><div class="table-cell">@geolocations?.Longitude</div></td>
                    <td><div class="table-cell">@geolocations?.Accuracy</div></td>
                    <td><div class="table-cell">@geolocations?.Timestamp</div></td>
                    <td><div class="table-cell">@geolocations?.LastUpdateTime</div></td>
                </tr>
            </tbody>
            <thead>
                <tr>
                    <th><div class="table-cell"><span class="table-text">移动距离</span></div></th>
                    <th><div class="table-cell"><span class="table-text">总移动距离</span></div></th>
                    <th><div class="table-cell"><span class="table-text">最后一次获取到的纬度</span></div></th>
                    <th><div class="table-cell"><span class="table-text">最后一次获取到的经度</span></div></th>
                    <th><div class="table-cell"><span class="table-text">状态</span></div></th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><div class="table-cell">@geolocations?.CurrentDistance</div></td>
                    <td><div class="table-cell">@geolocations?.TotalDistance</div></td>
                    <td><div class="table-cell">@geolocations?.LastLat</div></td>
                    <td><div class="table-cell">@geolocations?.LastLong</div></td>
                    <td><div class="table-cell">@geolocations?.Status</div></td>
                </tr>
            </tbody>
        </table>
    </div>
</div> 

4.2 GeolocationPage.razor.cs

扩展阅读:Chrome中模拟定位信息,清除定位信息

using Blazor100.Components;

namespace Blazor100.Pages;

/// <summary>
/// Geolocation 地理定位/移动距离追踪
/// <para></para>
/// 扩展阅读:Chrome中模拟定位信息,清除定位信息<para></para>
/// https://blog.csdn.net/u010844189/article/details/81163438
/// </summary>
public sealed partial class GeolocationPage
{

    private string? status { get; set; }
    private Geolocationitem? geolocations { get; set; }
    private string message;

    private Task OnResult(Geolocationitem geolocations)
    {
        this.geolocations = geolocations;
        StateHasChanged();
        return Task.CompletedTask;
    }

    private Task OnUpdateStatus(string status)
    {
        this.status = status;
        StateHasChanged();
        return Task.CompletedTask;
    }

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

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

@using Blazor100.Components

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

<div class="nav-item px-3">
    <NavLink class="nav-link" href="geolocations">
        定位
    </NavLink>
</div>

7. F5运行程序

至此,使用JS隔离制作定位/持续定位组件大功告成! Happy coding!

⚠️ **GitHub.com Fallback** ⚠️