Blazor如何實現類似於微信的Tab切換?

2023-01-23 15:00:18

是否有小夥伴在使用tab的時候想進行滑動切換Tab?

並且有滑動左出左進,右出右進的效果 ,本文將講解怎麼在Blazor中去通過滑動切換Tab

本文中的UI元件使用的是MASA Blazor,您也可以是其他的UI框架,這個並不影響實際的執行效果,本文案例是相容PC和Android的,演示效果是android中執行的,在PC中執行效果依然有效(親測)

首先安裝MASA Blazor 根據 MASA Blazor安裝MASA Blazor

準備工作

  1. 建立 AppBar.razor檔案

  2. 修改MainLayout.razor檔案程式碼

@inherits LayoutComponentBase

<MApp>
    <AppBar>
        <div class="body">
            @Body
        </div>
    </AppBar>
</MApp>

<style>
.body {
    /*設定內容高度 需要減去導航欄的高度*/
    height: calc(100vh - 48px);
    max-height: calc(100vh - 48px);
}
</style>
  1. 建立 AppBar.razor.css 檔案並且新增相關程式碼 ,以下程式碼是為了實現切換的時候有一個出入效果,具體程式碼案例來自Animista - On-Demand CSS Animations Library

    
    /*左邊滑動出*/
    .slide-out-left {
        -webkit-animation: slide-out-left 0.5s;
        animation: slide-out-left 0.5s;
    }
    
    /*右邊滑動出*/
    .slide-out-right {
        -webkit-animation: slide-out-right 0.5s;
        animation: slide-out-right 0.5s;
    }
    
    /*右邊滑動進*/
    .slide-in-right {
        -webkit-animation: slide-in-right 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
        animation: slide-in-right 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
    }
    
    /*左邊滑動進*/
    .slide-in-left {
        -webkit-animation: slide-in-left 0.5s;
        animation: slide-in-left 0.5s;
    }
    
    @-webkit-keyframes slide-out-left {
        0% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    
        100% {
            -webkit-transform: translateX(-1000px);
            transform: translateX(-1000px);
            opacity: 0;
        }
    }
    
    @keyframes slide-out-left {
        0% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    
        100% {
            -webkit-transform: translateX(-1000px);
            transform: translateX(-1000px);
            opacity: 0;
        }
    }
    
    
    @-webkit-keyframes slide-out-right {
        0% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    
        100% {
            -webkit-transform: translateX(1000px);
            transform: translateX(1000px);
            opacity: 0;
        }
    }
    
    @keyframes slide-out-right {
        0% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    
        100% {
            -webkit-transform: translateX(1000px);
            transform: translateX(1000px);
            opacity: 0;
        }
    }
    
    
    @-webkit-keyframes slide-in-left {
        0% {
            -webkit-transform: translateX(-1000px);
            transform: translateX(-1000px);
            opacity: 0;
        }
    
        100% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    }
    
    @keyframes slide-in-left {
        0% {
            -webkit-transform: translateX(-1000px);
            transform: translateX(-1000px);
            opacity: 0;
        }
    
        100% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    }
    @-webkit-keyframes slide-in-right {
        0% {
            -webkit-transform: translateX(1000px);
            transform: translateX(1000px);
            opacity: 0;
        }
    
        100% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    }
    
    @keyframes slide-in-right {
        0% {
            -webkit-transform: translateX(1000px);
            transform: translateX(1000px);
            opacity: 0;
        }
    
        100% {
            -webkit-transform: translateX(0);
            transform: translateX(0);
            opacity: 1;
        }
    }
    
    
  2. 建立AppBar的模型用於動態新增導航欄, 建立AppBarDto.cs檔案並新增相關程式碼

public class AppBarDto
{
    public string Key { get; set; }

    /// <summary>
    /// 標題
    /// </summary>
    public string Title { get; init; }

    /// <summary>
    /// 圖示
    /// </summary>
    public string? Icon { get; set; }

    /// <summary>
    /// 路由
    /// </summary>
    public string Href { get; init; }

    public AppBarDto(string title, string href, string? icon = null)
    {
        Title = title;
        Icon = icon;
        Href = href;
        Key = Guid.NewGuid().ToString("N");
    }
}
  1. 新增相關頁面,在Pages資料夾下,分別建立Index.razor,Feature.razor,Friend.razor,PersonalCenter.razor

檔案相關程式碼:
Index.razor

@page "/"
<h3>Index</h3>

Feature.razor

@page "/feature"
<h3>Feature</h3>

Friend.razor

@page "/friend"
<h3>Friend</h3>

PersonalCenter.razor

@page "/personal-center"
<h3>PersonalCenter</h3>
  1. 修改AppBar.razor程式碼


<div class="@Class" @ontouchstart="TouchStart" @ontouchend="TouchEnd" @onmousedown="Mousedown" @onmouseup="Mouseup" style="height: 100%">
    @ChildContent
</div>

 @*這裡也可以是其他元件的Tab,其實只是記錄當前的導航的資料*@
<MTabs Centered
       BackgroundColor="indigo"
       ShowArrows="false"
       Value="selectModel.Key"
       Dark>
    @foreach (var i in AppBars)
    {
        <MTab Value="i.Key" OnClick="()=>GoHref(i)">
            @if (!string.IsNullOrEmpty(i.Icon))
            {
                <MIcon Dark>@i.Icon</MIcon>
            }
            @i.Title
        </MTab>
    }
</MTabs>

  1. 建立AppBar.razor.cs 新增以下程式碼

public partial class AppBar
{
    #region Inject

    [Inject]
    public required NavigationManager NavigationManager { get; set; }

    #endregion

    private readonly List<AppBarDto> AppBars = new();

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private AppBarDto selectModel;

    private string Class { get; set; }

    protected override void OnInitialized()
    {
        AppBars.Add(new AppBarDto("首頁", "/", "home"));
        AppBars.Add(new AppBarDto("好友", "/personal-center", "mdi-account-group-outline"));
        AppBars.Add(new AppBarDto("功能", "/feature", "mdi-wrench"));
        AppBars.Add(new AppBarDto("個人中心", "/personal-center", "mdi-badge-account-alert"));

        // 預設選擇的導航標籤
        selectModel = AppBars[0];

        base.OnInitialized();
    }

    /// <summary>
    /// 導航欄跳轉
    /// </summary>
    /// <param name="appBar"></param>
    private void GoHref(AppBarDto appBar)
    {
        // 防止重複點選
        if(appBar == selectModel)
        {
            return;
        }

        // 當點選導航的索引大於現在導航時啟動滑動效果
        if(AppBars.IndexOf(appBar) > AppBars.IndexOf(selectModel))
        {
            Class = "slide-out-left";
            Task.Run(async () =>
            {
                // 由於特效時間為0.5s 這裡是等待特效完成
                await Task.Delay(450);
                NavigationManager.NavigateTo(selectModel.Href);
                Class = "slide-in-right";
                _ = InvokeAsync(StateHasChanged);
            });
        }
        // 當點選導航的索引小於現在導航時啟動滑動效果
        else if (AppBars.IndexOf(appBar) < AppBars.IndexOf(selectModel))
        {
            Class = "slide-out-right";
            Task.Run(async () =>
            {
                // 由於特效時間為0.5s 這裡是等待特效完成
                await Task.Delay(450);
                NavigationManager.NavigateTo(selectModel.Href);
                Class = "slide-in-left";
                _ = InvokeAsync(StateHasChanged);
            });
        }
        selectModel = appBar;
        NavigationManager.NavigateTo(appBar.Href);
    }


    /// <summary>
    /// 開始X座標
    /// </summary>
    private double _startX;


    #region 行動端滑動處理

    /// <summary>
    /// 記錄開始座標
    /// </summary>
    /// <param name="args"></param>
    private void TouchStart(TouchEventArgs args)
    {
        var touch = args.ChangedTouches[0];
        _startX = touch.ScreenX;
    }

    private void TouchEnd(TouchEventArgs args)
    {
        var touch = args.ChangedTouches[0];
        Switchover((decimal)touch.ScreenX);
    }

    #endregion

    #region PC滑動處理

    /// <summary>
    /// 記錄開始座標
    /// </summary>
    /// <param name="args"></param>
    private void Mousedown(MouseEventArgs args)
    {
        _startX = args.ScreenX;
    }

    private void Mouseup(MouseEventArgs args)
    {
        Switchover((decimal)args.ScreenX);
    }

    #endregion

    private void Switchover(decimal screenX)
    {
        var index = AppBars.IndexOf(selectModel);
        // 限制過度滑動
        if (index == AppBars.Count || index > AppBars.Count)
        {
            return;
        }

        // 設定滑動最大位限制,達到這個限制才滑動生效
        var size = 200;

        // 需要滑動200才切換 如果開始座標x大於 當前結束的x座標往右邊切換tab
        if ((decimal)_startX - size > screenX)
        {
            // 如果右邊往左邊滑動 當前索引是當前最大數量的話不需要切換
            if (index == AppBars.Count - 1)
            {
                return;
            }
            selectModel = AppBars[index + 1];
            Class = "slide-out-left";

            Task.Run(async () =>
            {
                // 由於特效時間為0.5s 這裡是等待特效完成
                await Task.Delay(450);
                NavigationManager.NavigateTo(selectModel.Href);
                Class = "slide-in-right";
                _ = InvokeAsync(StateHasChanged);
            });
        }
        else if ((decimal)_startX + size < screenX)
        {
            // 如果左邊往右邊滑動 當前索引是0的話不需要切換
            if (index == 0)
            {
                return;
            }
            selectModel = AppBars[index - 1];
            Class = "slide-out-right";
            Task.Run(async () =>
            {
                // 由於特效時間為0.5s 這裡是等待特效完成
                await Task.Delay(450);
                NavigationManager.NavigateTo(selectModel.Href);
                Class = "slide-in-left";
                _ = InvokeAsync(StateHasChanged);
            });
        }
    }
}

執行效果:

一個熱愛學習的token