Blazor元件提交全記錄: FullScreen 全屏按鈕/全屏服務 (BootstrapBlazor元件庫)

AlexChow發表於2022-04-07

Blazor 簡介

Blazor 是一個使用 .NET 生成的互動式客戶端 Web UI 的框架。和前端同學所熟知的 Vue、React、Angular 有巨大差異。

其最大的特色是使用 C# 程式碼(理論上可以是 .NET 生態的任何語言)代替 JavaScript 來實現邏輯。

  • 使用 C# 代替 JavaScript 來建立資訊豐富的互動式 UI。
  • 共享使用 .NET 編寫的伺服器端和客戶端應用邏輯。
  • 將 UI 呈現為 HTML 和 CSS,以支援眾多瀏覽器,其中包括移動瀏覽器。
  • 與新式託管平臺(如 Docker)整合。
  • 使用 .NET 和 Blazor 生成混合桌面和移動應用。

使用 .NET 進行客戶端 Web 開發可提供以下優勢:

  • 使用 C# 代替 JavaScript 來編寫程式碼。
  • 利用現有的 .NET 庫生態系統。
  • 在伺服器和客戶端之間共享應用邏輯。
  • 受益於 .NET 的效能、可靠性和安全性。
  • 使用開發環境(例如 Visual Studio 或 Visual Studio Code)保持 Windows、Linux 或 macOS 上的工作效率。
  • 以一組穩定、功能豐富且易用的通用語言、框架和工具為基礎來進行生成。

有兩種不同開發模式

Blazor WebAssembly, C# 程式碼執行在瀏覽器中。
Blazor Server,C# 程式碼在伺服器端執行,使用 SignalR 同步到瀏覽器進行更新。

Blazor 涉及技術

Blazor 是 apt.net core 生態的組成部分,所涉及到的技術也大部分和 .net 相關。

檢視層,使用 Razor 3 技術進行前端的編排渲染。Razor是一種標記語法,是 asp.net core 的預設檢視語法,最顯著的特點是強型別(C#、VB等)的程式碼可以和 HTML 寫在一個檔案中,當然也可以分開。在 razor 檔案中,@符號後面的都是強型別語言,可以是一行中的一部分,也可以是一整行,還可以是一個段落。在 asp.net core 中的大致做法是把 VB、C# 等強型別語言嵌入到網頁,當網頁被請求的時候,在伺服器端執行嵌入的程式碼,動態生成頁面。

以 Blazor WebAssembly 開發方式執行時,依賴 WebAssembly 4 技術,可以做成靜態頁面不依賴任何後端伺服器。

以 Blazor Server 方式開發執行時,依賴 SignalR 5 技術,並且需要後端伺服器端配合。

Bootstrap 風格的 Blazor UI 元件庫 - BootstrapBlazor

https://www.blazor.zone/index

基於 Bootstrap 樣式庫精心打造,並且額外增加了 100 多種常用的元件,為您快速開發專案帶來非一般的感覺.

Element.requestFullscreen()

參考資料 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/requestFullScreen

Element.requestFullscreen() 方法用於發出非同步請求使元素進入全屏模式。

呼叫此API並不能保證元素一定能夠進入全屏模式。如果元素被允許進入全螢幕模式,返回的Promise會resolve,並且該元素會收到一個fullscreenchange (en-US)事件,通知它已經進入全屏模式。如果全屏請求被拒絕,返回的promise會變成rejected並且該元素會收到一個fullscreenerror (en-US)事件。如果該元素已經從原來的文件中分離,那麼該文件將會收到這些事件。

早期的Fullscreen API實現總是會把這些事件傳送給document,而不是呼叫的元素,所以你自己可能需要處理這樣的情況。參考 Browser compatibility in [Page not yet written] 來得知哪些瀏覽器做了這個改動。

注意:這個方法只能在使用者互動或者裝置方向改變的時候呼叫,否則將會失敗。

語法

var Promise = Element.requestFullscreen(options);

引數

options 可選

一個FullscreenOptions (en-US)物件提供切換到全屏模式的控制選項。目前,唯一的選項是navigationUI (en-US),這控制了是否在元素處於全屏模式時顯示導航條UI。預設值是"auto",表明這將由瀏覽器來決定是否顯示導航條。

返回值

在完成切換全屏模式後,返回一個已經用值 undefined resolved的Promise

異常

requestFullscreen() 通過拒絕返回的 Promise來生成錯誤條件,而不是丟擲一個傳統的異常。拒絕控制器接收以下的某一個值:

TypeError

在以下幾種情況下,會丟擲TypeError:
文件中包含的元素未完全啟用,也就是說不是當前活動的元素。
元素不在文件之內。
因為功能策略限制配置或其他訪問控制,元素不被允許使用"fullscreen"功能。
元素和它的文件是同一個節點。

初步構建元件

1.建立js指令碼

        bb_Fullscreen: function (ele) {
            ele.requestFullscreen() ||
                ele.webkitRequestFullscreen ||
                ele.mozRequestFullScreen ||
                ele.msRequestFullscreen;
        },
        bb_ExitFullscreen: function () {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            }
            else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            }
            else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            }
            else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            }
        }
    });

2.建立Razor頁面測試

以下為簡化程式碼,執行測試一下功能是否達到需求.

<button @onclick="FullScreen">全屏</button>
<button @onclick="ExitFullScreen">退出全屏</button>

@code{
[Inject] IJSRuntime? JSRuntime{ get; set; }

  //進入全屏
  private Task FullScreen() => await JSRuntime.InvokeVoidAsync("bb_Fullscreen");

  //退出全屏
  private Task ExitFullScreen() => await JSRuntime.InvokeVoidAsync("bb_ExitFullscreen");
}

3.優化邏輯,新增單按鈕全屏切換邏輯,新增針對單獨元素的全屏邏輯

JS完整程式碼

(function ($) {
    $.extend({
        bb_toggleFullscreen: function (el, id) {
            var ele = el;
            if (!ele || ele === '') {
                if (id) {
                    ele = document.getElementById(id);
                }
                else {
                    ele = document.documentElement;
                }
            }
            if ($.bb_IsFullscreen()) {
                $.bb_ExitFullscreen();
                ele.classList.remove('fs-open');
            }
            else {
                $.bb_Fullscreen(ele);
                ele.classList.add('fs-open');
            }
        },
        bb_Fullscreen: function (ele) {
            ele.requestFullscreen() ||
                ele.webkitRequestFullscreen ||
                ele.mozRequestFullScreen ||
                ele.msRequestFullscreen;
        },
        bb_ExitFullscreen: function () {
            if (document.exitFullscreen) {
                document.exitFullscreen();
            }
            else if (document.mozCancelFullScreen) {
                document.mozCancelFullScreen();
            }
            else if (document.webkitExitFullscreen) {
                document.webkitExitFullscreen();
            }
            else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            }
        },
        bb_IsFullscreen: function () {
            return document.fullscreen ||
                document.webkitIsFullScreen ||
                document.webkitFullScreen ||
                document.mozFullScreen ||
                document.msFullScreent;
        }
    });
})(jQuery);

測試功能

<button @onclick="ToggleFullScreen">全屏</button>

@code{
[Inject] IJSRuntime? JSRuntime{ get; set; }

  //全屏方法,已經全屏時再次呼叫後退出全屏
  private Task ToggleFullScreen() => await JSRuntime.InvokeVoidAsync("bb_toggleFullscreen");
}

4.封裝為服務

再次進行思考,如果將一顆按鈕封裝為元件,那只有UI介面才能呼叫,而且式樣什麼的都不算最靈活,為何不做成一個服務,與UI分開解耦呢? 彆著急, 馬上開幹.

我作為一個blazor愛好者,每一個想法,轉化為一個元件後,是值得提交到例如BootstrapBlazor之類元件庫大家一起學習一起進步的,自從我2020-09-25把ZXingBlazor元件提交到BootstrapBlazor之後,從自嗨到團隊合作,真的學習到了很多知識和技巧,在學習BB的原始碼的過程中,深刻體會到了那句話的精髓:"每入一寸就有一寸的驚喜!".

專案負責人Argo作為一位微軟MVP和業內人士,對整個微軟技術棧有很深刻的認識和思考,對我本人更是幫助巨大,在此謝謝Argo, ?

最後版本程式碼已經提交為元件庫裡面的一個元件,所以有些程式碼繼承了元件庫的功能,如果執行跟預設Blazor工程有不一致的地方,大家可以Fork到自己倉庫去試驗,以下文章不再贅述.

構建服務 FullScreenService.cs

using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Components;

/// <summary>
/// FullScreen 服務
/// </summary>
public class FullScreenService : BootstrapServiceBase<FullScreenOption>
{
    /// <summary>
    /// 全屏方法,已經全屏時再次呼叫後退出全屏
    /// </summary>
    /// <param name="option"></param>
    /// <returns></returns>
    public Task Toggle(FullScreenOption? option = null) => Invoke(option ?? new());

    /// <summary>
    /// 通過 ElementReference 將指定元素進行全屏
    /// </summary>
    /// <param name="element"></param>
    /// <returns></returns>
    public Task ToggleByElement(ElementReference element) => Invoke(new() { Element = element });

    /// <summary>
    /// 通過元素 Id 將指定元素進行全屏
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public Task ToggleById(string id) => Invoke(new() { Id = id });
}

全屏服務類 FullScreenOption.cs

using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Components;

/// <summary>
/// FullScreen 配置類
/// </summary>
public class FullScreenOption
{
    /// <summary>
    /// 
    /// </summary>
    public ElementReference Element { get; set; }

    /// <summary>
    /// 
    /// </summary>
    public string? Id { get; set; }
}

註冊服務

 services.TryAddScoped<FullScreenService>();

FullScreen.cs

using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Components;

/// <summary>
/// FullScreen 元件部分類
/// </summary>
public class FullScreen : BootstrapComponentBase, IDisposable
{
    /// <summary>
    /// DialogServices 服務例項
    /// </summary>
    [Inject]
    [NotNull]
    private FullScreenService? FullScreenService { get; set; }

    /// <summary>
    /// OnInitialized 方法
    /// </summary>
    protected override void OnInitialized()
    {
        base.OnInitialized();

        // 註冊 FullScreen 彈窗事件
        FullScreenService.Register(this, Show);
    }

    /// <summary>
    /// OnAfterRenderAsync 方法
    /// </summary>
    /// <param name="firstRender"></param>
    /// <returns></returns>
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (Option != null)
        {
            await JSRuntime.InvokeVoidAsync(Option.Element.Context != null ? Option.Element : "", "bb_toggleFullscreen", Option.Id ?? "");
            Option = null;
        }
    }

    private FullScreenOption? Option { get; set; }

    private Task Show(FullScreenOption option)
    {
        Option = option;
        StateHasChanged();
        return Task.CompletedTask;
    }

    /// <summary>
    /// Dispose 方法
    /// </summary>
    /// <param name="disposing"></param>
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            FullScreenService.UnRegister(this);
        }
    }

    /// <summary>
    /// Dispose 方法
    /// </summary>
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }
}

元件方式呼叫 [簡化版] FullScreenButton.Razor

@namespace BootstrapBlazor.Components
@inherits TooltipComponentBase

<a @attributes="AdditionalAttributes" id="@Id" class="@ClassString" @onclick="ToggleFullScreen">
    <i class="@ButtonIconString"></i>
    <i class="@FullScreenIconString"></i>
</a>

<CascadingValue Value="this" IsFixed="true">
    <Tooltip Title="@Title" />
</CascadingValue>

@code{ 

    [Inject]
    [NotNull]
    private FullScreenService? FullScrenService { get; set; }

    private Task ToggleFullScreen() => FullScrenService.Toggle();
}

5.FullScreens 全屏示例程式碼

Razor

@page "/fullscreens"
@inject IStringLocalizer<FullScreens> Localizer

<h3>@Localizer["Title"]</h3>

<h4>@((MarkupString)Localizer["H1"].Value)</h4>

<DemoBlock Title="@Localizer["Block1Title"]" Introduction="@Localizer["Block1Intro"]" Name="Normal">
    <Button Text="@Localizer["ButtonText1"]" OnClick="ToggleFullScreen"></Button>
</DemoBlock>

<DemoBlock Title="@Localizer["Block2Title"]" Introduction="@Localizer["Block2Intro"]" Name="Title">
    <ul class="ul-demo mb-3">
        <li>@((MarkupString)Localizer["Li1"].Value)</li>
        <li>@((MarkupString)Localizer["Li2"].Value)</li>
    </ul>
    <FullScreenButton Title="@Localizer["Button1Text"]" FullScreenIcon="fa fa-fa" />
    <Pre class="mt-3">&lt;@Localizer["Pre"]" /&gt;</Pre>
</DemoBlock>

cs程式碼

using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Shared.Samples;

/// <summary>
/// FullScreens 全屏示例程式碼
/// </summary>
public partial class FullScreens
{
    [Inject]
    [NotNull]
    private FullScreenService? FullScreenService { get; set; }

    private async Task ToggleFullScreen()
    {
        await FullScreenService.Toggle();
    }
}

BootstrapBlazor提交元件簡單步驟

示例文件

  1. 新增對應元件中文資源到 BootstrapBlazor.Shared/Locales/zh.json 檔案
"BootstrapBlazor.Shared.Pages.Coms": {
  ...
  "FullScreenText": "全屏元件 FullScreen",
  ...
},
"BootstrapBlazor.Shared.Samples.FullScreens": {
  "Title": "FullScreen 全屏",
  "Block1Title": "基本用法",
  ...
}
  1. 新增對應元件中文資源到 BootstrapBlazor.Shared/Locales/en.json 檔案
"BootstrapBlazor.Shared.Pages.Coms": {
  ...
  "FullScreenText": "FullScreen",
  ...
},
"BootstrapBlazor.Shared.Samples.FullScreens": {
  "Title": "FullScreen",
  "Block1Title": "Basic usage",
  ...
}
  1. 新增示例到"元件" 頁面

    BootstrapBlazor.Shared/Pages/Coms.razor檔案,找到某個元件大類別,例如導航元件 <ComponentCategory Text="@Localizer["Text2"]">

<ComponentCategory Text="@Localizer["Text2"]">
  ...
  <ComponentCard Text="@Localizer["FullScreenText"]" Image="FullScreen.jpg" Url="fullscreens"></ComponentCard>
  ...
</ComponentCategory>
  1. BootstrapBlazor.Shared/docs.json新增
"fullscreens": "FullScreens",
  1. NavMenu.razor
    private void AddNavigation(DemoMenuItem item)
    {
        item.Items = new List<DemoMenuItem>
        {
            ...
            new()
            {
                IsNew = true,
                Text = Localizer["FullScreen"],
                Url = "fullscreens"
            },
            ...
        };

        AddBadge(item);
    }
  1. 示例檔案 BootstrapBlazor.Shared/Samples/FullScreens.razor
@page "/fullscreens"
@inject IStringLocalizer<FullScreens> Localizer

<h3>@Localizer["Title"]</h3>

<DemoBlock Title="@Localizer["Block1Title"]" Introduction="@Localizer["Block1Intro"]" Name="Normal">
    <Button Text="@Localizer["ButtonText1"]" OnClick="ToggleFullScreen"></Button>
</DemoBlock>

<DemoBlock Title="@Localizer["Block2Title"]" Introduction="@Localizer["Block2Intro"]" Name="Title">
    <FullScreenButton Title="@Localizer["Button1Text"]" FullScreenIcon="fa fa-fa" />
</DemoBlock>
  1. 示例檔案 BootstrapBlazor.Shared/Samples/FullScreens.razor.cs
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Shared.Samples;

/// <summary>
/// FullScreens 全屏示例程式碼
/// </summary>
public partial class FullScreens
{
    [Inject]
    [NotNull]
    private FullScreenService? FullScreenService { get; set; }

    private async Task ToggleFullScreen()
    {
        await FullScreenService.Toggle();
    }
}

參考資料

ASP.NET Core Blazor https://docs.microsoft.com/zh-cn/aspnet/core/blazor/?view=aspnetcore-6.0
五分鐘瞭解 Blazor https://segmentfault.com/a/1190000040800253
Element.requestFullscreen() https://developer.mozilla.org/zh-CN/docs/Web/API/Element/requestFullScreen
Bootstrap 風格的 Blazor UI 元件庫 https://www.blazor.zone/index
!1821 feat(#I48WXD): add FullScreen component https://gitee.com/LongbowEnterprise/BootstrapBlazor/commit/30caa995eba38e91d15b8a5465c6c9c738db068f

專案原始碼

Github | Gitee

關聯專案

BA & Blazor QQ群:795206915、675147445

知識共享許可協議

本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名AlexChow(包含連結: https://github.com/densen2014 ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯絡

AlexChow

今日頭條 | 部落格園 | 知乎 | Gitee | GitHub

相關文章