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
{

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

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

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

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

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

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

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

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

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

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


        /// 
        /// 
        /// 
        /// 
        /// 
        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

@if (ShowButtons) { if (WatchID == null) { } else { } }

3.2 Geolocation.razor.cs 组件代码

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

namespace Blazor100.Components;

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

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

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

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

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

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

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

    /// 
    /// 
    /// 
    protected ElementReference GeolocationElement { get; set; }

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

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

    private IJSObjectReference? module;
    private DotNetObjectReference? InstanceGeo { get; set; }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        try
        {
            if (firstRender)
            {
                module = await JS!.InvokeAsync("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();
        }
    }


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

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

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

    /// 
    /// 定位完成回调方法
    /// 
    /// 
    /// 
    [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);
        }
    }

    /// 
    /// 获得/设置 错误回调方法
    /// 
    [Parameter]
    public Func? OnError { get; set; }

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

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

}

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

4.1 GeolocationPage.razor

razor代码
@page "/geolocations"

定位/持续定位 Geolocation

@message

@status

纬度
经度
准确度(米)
时间戳
时间
@geolocations?.Latitude
@geolocations?.Longitude
@geolocations?.Accuracy
@geolocations?.Timestamp
@geolocations?.LastUpdateTime
移动距离
总移动距离
最后一次获取到的纬度
最后一次获取到的经度
状态
@geolocations?.CurrentDistance
@geolocations?.TotalDistance
@geolocations?.LastLat
@geolocations?.LastLong
@geolocations?.Status

4.2 GeolocationPage.razor.cs

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

using Blazor100.Components;

namespace Blazor100.Pages;

/// 
/// Geolocation 地理定位/移动距离追踪
/// 
/// 扩展阅读:Chrome中模拟定位信息,清除定位信息
/// https://blog.csdn.net/u010844189/article/details/81163438
/// 
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. 首页引用组件演示页 或者 Shared/NavMenu.razor 添加导航


7. F5运行程序

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

Blazor组件自做系列








项目源码 Github | Gitee