Blazor实现菜单动画

想到动画,你可能会去安装Blazor的动画组件BlazorAnimate,然后使用它。本人初学,暂时我也不知道原理,先不用组件,自己实现吧。虽然项目中我用了AntDesignBlazor,但是我忘了使用它的菜单组件,我用的菜单组件还是VS2022自动生成的,后来我把这个菜单改造了一下,支持多级菜单,加了展示收缩箭头,那就在这基础上做吧。

1. 引用jQuery

这里使用jquery的animate方法实现动画
在wwwroot/js目录放一个jquery-1.9.1.js文件,然后在html(或_Layout.cshtml文件)中引入该js

<script src="js/jquery-1.9.1.js"></script>

1.png


2. 为左侧菜单组件NavMenu.razor添加一个js文件:NavMenu.razor.js

2.png


内容如下:

export function animate(index) { //index是菜单编码 2位数是一级菜单,4位数是二级菜单,以此类推
    let time = 200;
    let content = $(".content" + index);
    let h = content.height() + "px";
    content.css("overflow", "hidden");

    if (index <= 99 || content.hasClass("collapse")) { //index<=99表示一级菜单,一级菜单只有展开动画,没有收缩动画;content.hasClass("collapse")表示当前是收缩状态。
        content.css("height", "0");
        //展开动画
        content.animate({
            height: h
        }, time, "linear", () => {
            content.css("height", "auto");
        });
    } else { //不是一级菜单并且当前是展开状态,将执行收缩动画
        content.css("height", "auto");
        //收缩动画
        content.animate({
            height: 0
        }, time, "linear", () => {
            setTimeout(function () { //延迟执行,否则会导致闪烁
                content.css("height", "auto");
            }, 100);
        });
        return [time]; //收缩时,需要等待收缩动画展示完成,再隐藏菜单容器div
    }
    return [0];
}

3. 在Blazor组件NavMenu.razor文件中引入该js

@inject IJSRuntime _js;

@code {
    IJSObjectReference _module;

    protected override async void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            _module = await _js.InvokeAsync<IJSObjectReference>("import", "./Shared/NavMenu.razor.js");
        }
    }
}

4. 调用js方法实现菜单动画

@code{
    ...省略此处的代码
    
    private async Task ToggleMenuClick(int index)
    {
        object[] args = new object[] { index };
        object[] objs = await _module.InvokeAsync<object[]>("animate", args);
        int time = ((JsonElement)objs[0]).GetInt32();
        if (time > 0) { await Task.Delay(time); } //time大于0,表示需要等待收缩动画展示完成,再隐藏菜单容器div

        if (index > 99)
        {
            if (_dict.ContainsKey(index))
            {
                _dict[index] = !_dict[index]; //记录非一级菜单的展开状态
            }
            else
            {
                _dict[index] = false; //记录非一级菜单的展开状态
            }
        }
        else
        {
            _index = index; //_index是当前展示的菜单编码
        }
    }

    ...省略此处的代码
}

效果图

1.gif



@using System.Text.Json;
@inject IJSRuntime _js;

<!-- 菜单组 -->
<div class="top-row ps-3 navbar navbar-dark" @onclick="@(async ()=>await ToggleMenuClick(01))">
    <div class="container-fluid">
        <a class="navbar-brand" href="javascript:void(0);">任务管理</a>
        <button title="Navigation menu" class="navbar-toggler">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="@ArrowClass(01)"></div>
    </div>
</div>

<!-- 菜单组内容 -->
<div class="@MenuGroupContentClass(01)">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="kpTask/taskList">
                <span class="oi oi-list" aria-hidden="true"></span> 任务列表
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="kpTask/taskRunList">
                <span class="oi oi-list" aria-hidden="true"></span> 任务执行记录
            </NavLink>

            <!-- 二级菜单组 -->
            <NavLink class="nav-link" href="javascript:void(0);" @onclick="@(()=> ToggleMenuClick(0101))" style="display:;">
                <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单组
                <div class="@ArrowClass(0101)"></div>
            </NavLink>

            <!-- 二级菜单组内容 -->
            <div class="@MenuGroupContentClass(0101)" style="display:;">
                <nav class="flex-column">
                    <div class="nav-item px-3">
                        <NavLink class="nav-link" href="counter">
                            <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单
                        </NavLink>

                        <!-- 三级菜单组 -->
                        <NavLink class="nav-link" href="javascript:void(0);" @onclick="@(()=> ToggleMenuClick(010101))" style="display:;">
                            <span class="oi oi-list" aria-hidden="true"></span> 测试三级菜单组
                            <div class="@ArrowClass(010101)"></div>
                        </NavLink>

                        <!-- 三级菜单组内容 -->
                        <div class="@MenuGroupContentClass(010101)" style="display:;">
                            <nav class="flex-column">
                                <div class="nav-item px-3">
                                    <NavLink class="nav-link" href="counter">
                                        <span class="oi oi-list" aria-hidden="true"></span> 测试三级菜单
                                    </NavLink>
                                </div>
                            </nav>
                        </div>
                    </div>
                    <div class="nav-item px-3">
                        <NavLink class="nav-link" href="counter">
                            <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单2
                        </NavLink>
                    </div>
                </nav>
            </div>
        </div>
    </nav>
</div>

<!-- 菜单组 -->
<div class="top-row ps-3 navbar navbar-dark" @onclick="@(async ()=>await ToggleMenuClick(02))">
    <div class="container-fluid">
        <a class="navbar-brand" href="javascript:void(0);">任务管理</a>
        <button title="Navigation menu" class="navbar-toggler">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="@ArrowClass(01)"></div>
    </div>
</div>

<!-- 菜单组内容 -->
<div class="@MenuGroupContentClass(02)">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="kpTask/taskList">
                <span class="oi oi-list" aria-hidden="true"></span> 任务列表
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="kpTask/taskRunList">
                <span class="oi oi-list" aria-hidden="true"></span> 任务执行记录
            </NavLink>

            <!-- 二级菜单组 -->
            <NavLink class="nav-link" href="javascript:void(0);" @onclick="@(()=> ToggleMenuClick(0201))" style="display:none;">
                <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单组
                <div class="@ArrowClass(0201)"></div>
            </NavLink>

            <!-- 二级菜单组内容 -->
            <div class="@MenuGroupContentClass(0201)" style="display:none;">
                <nav class="flex-column">
                    <div class="nav-item px-3">
                        <NavLink class="nav-link" href="counter">
                            <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单
                        </NavLink>
                    </div>
                </nav>
            </div>
        </div>
    </nav>
</div>

<!-- 菜单组 -->
<div class="top-row ps-3 navbar navbar-dark" @onclick="@(async ()=>await ToggleMenuClick(03))">
    <div class="container-fluid">
        <a class="navbar-brand" href="javascript:void(0);">任务管理</a>
        <button title="Navigation menu" class="navbar-toggler">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="@ArrowClass(01)"></div>
    </div>
</div>

<!-- 菜单组内容 -->
<div class="@MenuGroupContentClass(03)">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="kpTask/taskList">
                <span class="oi oi-list" aria-hidden="true"></span> 任务列表
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="kpTask/taskRunList">
                <span class="oi oi-list" aria-hidden="true"></span> 任务执行记录
            </NavLink>

            <!-- 二级菜单组 -->
            <NavLink class="nav-link" href="javascript:void(0);" @onclick="@(()=> ToggleMenuClick(0301))" style="display:none;">
                <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单组
                <div class="@ArrowClass(0301)"></div>
            </NavLink>

            <!-- 二级菜单组内容 -->
            <div class="@MenuGroupContentClass(0301)" style="display:none;">
                <nav class="flex-column">
                    <div class="nav-item px-3">
                        <NavLink class="nav-link" href="counter">
                            <span class="oi oi-list" aria-hidden="true"></span> 测试二级菜单
                        </NavLink>
                    </div>
                </nav>
            </div>
        </div>
    </nav>
</div>

@code {
    int _index = 01;
    Dictionary<int, bool> _dict = new Dictionary<int, bool>();
    IJSObjectReference _module;

    protected override async void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            _module = await _js.InvokeAsync<IJSObjectReference>("import", "./Shared/NavMenu.razor.js");
        }
    }

    private async Task ToggleMenuClick(int index)
    {
        object[] args = new object[] { index };
        object[] objs = await _module.InvokeAsync<object[]>("animate", args);
        int time = ((JsonElement)objs[0]).GetInt32();
        if (time > 0) { await Task.Delay(time); }

        if (index > 99)
        {
            if (_dict.ContainsKey(index))
            {
                _dict[index] = !_dict[index];
            }
            else
            {
                _dict[index] = false;
            }
        }
        else
        {
            _index = index;
        }
    }

    private string MenuGroupContentClass(int index)
    {
        if (index > 99)
        {
            if (!_dict.ContainsKey(index) || _dict[index])
            {
                return $"collapse content{index}";
            }
            else
            {
                return $"content{index}";
            }
        }
        else
        {
            if (index == _index)
            {
                return $"content{index}";
            }
            else
            {
                return $"collapse content{index}";
            }
        }
    }

    private string ArrowClass(int index)
    {
        if (index > 99)
        {
            if (!_dict.ContainsKey(index) || _dict[index])
            {
                return "arrow-collapse arrow-float";
            }
            else
            {
                return "arrow arrow-float";
            }
        }
        else
        {
            if (index == _index)
            {
                return "arrow";
            }
            else
            {
                return "arrow-collapse";
            }
        }
    }

}