{"id":13807990,"url":"https://github.com/le-nn/blazor-transition-group","last_synced_at":"2025-04-10T01:12:50.650Z","repository":{"id":63162609,"uuid":"565690044","full_name":"le-nn/blazor-transition-group","owner":"le-nn","description":"An easy way to perform animations when a Blazor component enters or leaves the DOM","archived":false,"fork":false,"pushed_at":"2024-02-26T14:21:14.000Z","size":17701,"stargazers_count":25,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T01:12:45.949Z","etag":null,"topics":["animation","animation-css","asp-net-core","blazor","blazor-component","blazor-webassembly"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/le-nn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-14T05:35:19.000Z","updated_at":"2025-02-05T13:13:27.000Z","dependencies_parsed_at":"2024-08-04T01:08:18.838Z","dependency_job_id":"60bfb86c-0de2-407a-a67a-798aba23d669","html_url":"https://github.com/le-nn/blazor-transition-group","commit_stats":{"total_commits":19,"total_committers":2,"mean_commits":9.5,"dds":0.3157894736842105,"last_synced_commit":"fa02fa40645de7b75893f7e2875542634d74f97a"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le-nn%2Fblazor-transition-group","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le-nn%2Fblazor-transition-group/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le-nn%2Fblazor-transition-group/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/le-nn%2Fblazor-transition-group/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/le-nn","download_url":"https://codeload.github.com/le-nn/blazor-transition-group/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248137891,"owners_count":21053775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","animation-css","asp-net-core","blazor","blazor-component","blazor-webassembly"],"created_at":"2024-08-04T01:01:33.342Z","updated_at":"2025-04-10T01:12:50.632Z","avatar_url":"https://github.com/le-nn.png","language":"C#","funding_links":[],"categories":["Libraries \u0026 Extensions"],"sub_categories":["2D/3D Rendering engines"],"readme":"# Blazor Transition Group\r\n\r\nEnglish / [日本語](https://zenn.dev/remrem/articles/3e13d64bcba6b5)\r\n\r\nExposes simple components useful for defining entering and exiting transitions. \r\nit does not animate styles by itself. Instead it exposes transition stages, manages classes and group elements and manipulates the DOM in useful ways, making the implementation of actual visual transitions much easier.\r\n\r\nThis project is inspired from [React Transition Group](https://github.com/reactjs/react-transition-group).\r\n\r\n\u003cimg src=\"./overview.gif\" width=\"600\"/\u003e\r\n\r\nHere is demo page.\r\n\r\nhttps://le-nn.github.io/blazor-transition-group/\r\n\r\n## Installation\r\n\r\n```\r\ndotnet add package BlazorTransitionGroup\r\n```\r\n\r\nHere is Nuget package link\r\nhttps://www.nuget.org/packages/BlazorTransitionGroup\r\n\r\n## Supported platform\r\n\r\n.NET 7 or higher\r\n\r\n## Components\r\n\r\n### Transition\r\n\r\nThe Transition component lets you describe a transition from one component\r\nstate to another over time with a simple declarative API. \r\nMost commonly it's used to animate the mounting and unmounting of a component,\r\nbut can also be used to describe in-place transition states as well.\r\n\r\nBy default the Transition component does not alter the behavior of the component it renders, it only tracks \"enter\" and \"exit\" states for the components.\r\nIt's up to you to give meaning and effect to those states. For example we can add styles to a component when it enters or exits:\r\n\r\nThere are 4 main states a Transition can be in:\r\n\r\n* ```entering```\r\n* ```entered```\r\n* ```exiting```\r\n* ```exited```\r\n\r\nHere is example of how to use Transition component\r\n\r\n[Sample code is here](./samples/BlazorTransitionGroup.Samples/Demo/GrowTransition.razor)\r\n\r\nInherits ```BlazorTransitionGroup.Transition``` and override razor template as BuildRenderTree method.\r\n\r\n```razor\r\n\r\n// GrowTransition.razor\r\n@using BlazorTransitionGroup\r\n\r\n@inherits Transition\r\n\r\n\u003cdiv style=\"@ActualStyle\" class=\"@Class\"\u003e\r\n    @ChildContent?.Invoke(TransitionState)\r\n\u003c/div\u003e\r\n\r\n@code {\r\n    string ActualStyle =\u003e $\"opacity: {Opacity};transform:scale({Size});transition:opacity {Duration / 2}ms ease-in-out,transform {Duration}ms ease-in-out;{Style}\";\r\n\r\n    string Opacity =\u003e TransitionState switch {\r\n        TransitionState.Entering or TransitionState.Entered =\u003e \"1\",\r\n        _ =\u003e \"0\",\r\n    };\r\n\r\n    string Size =\u003e TransitionState switch {\r\n        TransitionState.Entering or TransitionState.Entered =\u003e \"1\",\r\n        _ =\u003e \"0\",\r\n    };\r\n\r\n    double Duration =\u003e TransitionState switch {\r\n        TransitionState.Entering or TransitionState.Entered =\u003e DurationEnter,\r\n        _ =\u003e DurationExit,\r\n    };\r\n\r\n    string HeightStyle =\u003e TransitionState switch {\r\n        TransitionState.Entering or TransitionState.Entered =\u003e \"100%\",\r\n        _ =\u003e \"0%\",\r\n    };\r\n\r\n    [Parameter]\r\n    public string Height { get; set; } = \"auto\";\r\n\r\n    [Parameter]\r\n    public string Width { get; set; } = \"auto\";\r\n\r\n    [Parameter]\r\n    public string? Style { get; set; }\r\n\r\n    [Parameter]\r\n    public string? Class { get; set; }\r\n}\r\n\r\n```\r\n\r\n### Public API Reference\r\n| Name                | Type            | Category            | Default                | Description                                                 |\r\n|---------------------|-----------------|---------------------|------------------------|-------------------------------------------------------------|\r\n| ChildContent        | RenderFragment? | Component Attribute | null                   | The Render fragment for child content.                      |\r\n| TransitionBegan     | EventCallback   | Component Attribute | ---                    | The event callback that fired when Transition is Began.     |\r\n| TransitionCompleted | EventCallback   | Component Attribute | ---                    | The event callback that fired when Transition is Completed. |\r\n| Delay               | int             | Component Attribute | 0                      | The milliseconds of deley time that animation begin.        |\r\n| DurationEnter       | int             | Component Attribute | 400                    | The Duration of entering animation.                         |\r\n| DurationExit        | int             | Component Attribute | 400                    | The Duration of exiting animation.                          |\r\n| In                  | bool?           | Component Attribute | null                   | Is the animation enabled.                                   |\r\n| IsAnimating         | bool            | Property            | false                  | Whether it is currently animated.                           |\r\n| TransitionState     | TransitionState | Property            | TransitionState.Exited | The current transition state.                               |\r\n| Dispose             | void Dispose()  | Method              | ---                    | Dispose current context.                                    |\r\n\r\n\r\n### TransitionGroup\r\n\r\nThe ```\u003cTransitionGroup\u003e``` component manages a set of transition components\r\n(```\u003cTransition\u003e```) in a list. \r\n\r\nLike with the transition components, ```\u003cTransitionGroup\u003e``` is a state machine for managing\r\nthe mounting and unmounting of components over time.\r\n\r\nConsider the example below. \r\nAs items are removed or added to the TodoList the\r\nin prop is toggled automatically by the ```\u003cTransitionGroup\u003e```.\r\n\r\nNote that ```\u003cTransitionGroup\u003e``` does not define any animation behavior!\r\nExactly how a list item animates is up to the individual transition component.\r\nThis means you can mix and match animations across different list items.\r\n\r\n```TransitionGroup``` Component requires unique ```@key``` field.\r\nThe ```Transition``` Component to be animated must be placed directly under the ```TransitionGroup```.\r\nIt won't work if you wrap it in a ```div``` or other component.\r\n\r\nOK\r\n```razor\r\n\u003cTransitionGroup\u003e\r\n    @foreach(var item in items) {\r\n       \u003cHogeTransition @key=\"item\" /\u003e\r\n    }\r\n\u003c/TransitionGroup\u003e\r\n```\r\n\r\nFailed\r\n```razor\r\n\u003cTransitionGroup\u003e\r\n    @foreach(var item in items) { \r\n        \u003cdiv @key=\"item\"\u003e\r\n            \u003cHogeTransition @key=\"item\" /\u003e\r\n        \u003c/div\u003e\r\n    }\r\n\u003c/TransitionGroup\u003e\r\n```\r\n\r\n[Sample code in Demo is here](./samples/BlazorTransitionGroup.Samples/Demo/TransitionDemo.razor)\r\n\r\n```razor\r\n\r\n@using BlazorTransitionGroup\r\n\r\n\u003cTransitionGroup\u003e\r\n    @foreach (var (i, text, id) in _items) {\r\n        @if (i % 2 is 0) {\r\n            \u003cGrowTransition @key=\"@($\"{text}-{id}\")\" Context=\"state\"\u003e\r\n                \u003cdiv class=\"item d-flex p-3 align-items-center shadow mt-3 rounded-3 bg-white\"\u003e\r\n                    \u003cbutton class=\"btn btn-danger\" @onclick=\"@(() =\u003e Remove((i, text, id)))\"\u003e\r\n                        \u003ci class=\"oi oi-trash\" /\u003e\r\n                    \u003c/button\u003e\r\n                    \u003cdiv class=\"p-1 mx-3\" style=\"width:100px;\"\u003e@state\u003c/div\u003e\r\n                    \u003cdiv class=\"p-1 mx-3\"\u003e@text\u003c/div\u003e\r\n                \u003c/div\u003e\r\n            \u003c/GrowTransition\u003e\r\n        }\r\n        else {\r\n            \u003cSlideTransition @key=\"@($\"{text}-{id}\")\"\u003e\r\n                \u003cdiv class=\"item d-flex p-3 align-items-center shadow mt-3 rounded-3 bg-white\"\u003e\r\n                    \u003cbutton class=\"btn btn-danger\" @onclick=\"@(() =\u003e Remove((i, text, id)))\"\u003e\r\n                        \u003ci class=\"oi oi-trash\" /\u003e\r\n                    \u003c/button\u003e\r\n                    \u003cdiv class=\"p-1 mx-3\" style=\"width:100px;\"\u003e\u003c/div\u003e\r\n                    \u003cdiv class=\"p-1 mx-3\"\u003e@text\u003c/div\u003e\r\n                \u003c/div\u003e\r\n            \u003c/SlideTransition\u003e\r\n        }\r\n    }\r\n\u003c/TransitionGroup\u003e\r\n\r\n\u003cdiv class=\"d-flex mt-4\"\u003e\r\n    \u003cinput @bind-value=\"_text \" /\u003e\r\n    \u003cbutton class=\"btn btn-primary\" @onclick=\"Add\"\u003e ADD\u003c/button\u003e\r\n\u003c/div\u003e\r\n\r\n@code {\r\n    string _text = \"\";\r\n    int _i = 3;\r\n\r\n    List\u003c(int Index, string Text, Guid Key)\u003e _items = new() {\r\n        (0, \"item 1\", Guid.NewGuid()),\r\n        (1, \"item 2\", Guid.NewGuid()),\r\n        (2, \"item 3\", Guid.NewGuid()),\r\n    };\r\n\r\n    void Add() {\r\n        if (string.IsNullOrWhiteSpace(_text)) {\r\n            return;\r\n        }\r\n\r\n        _items.Add((_i++, _text, Guid.NewGuid()));\r\n        _text = \"\";\r\n    }\r\n\r\n    void Remove((int, string, Guid) text) {\r\n        _items.Remove(text);\r\n    }\r\n}\r\n\r\n```\r\n\r\n### Public API Reference\r\n| Name          | Type               | Description                                                         |  \r\n| ------------- | ------------------ | ------------------------------------------------------------------- |\r\n| ChildContent  | RenderFragment?    | The render fragment for ChildContent.                               |\r\n\r\n### TransitionBase\r\n\r\nAnother option to implements transition is to inherit ```TransitionBase```.\r\nTransition can be implemented with composition.\r\n\r\nRenderFragment context provider stransition state.\r\n\r\n\r\n[Sample code in Demo is here](./samples/BlazorTransitionGroup.Samples/Demo/SlideTransition.razor)\r\n\r\nHere is example of how to use Transition component.\r\nDon't forget to specify the Key as a Transition Attribute.\r\n\r\n```razor\r\n\r\n@using BlazorTransitionGroup\r\n\r\n@inherits TransitionBase\r\n\r\n\u003cTransition Key=\"Key\" Context=\"transitionState\"\u003e\r\n    \u003cdiv style=\"@(GetActualStyle(transitionState))\" class=\"@Class\"\u003e\r\n        @ChildContent\r\n    \u003c/div\u003e\r\n\u003c/Transition\u003e\r\n\r\n@code {\r\n    string GetActualStyle(TransitionState state) {\r\n        var (x, opacity) = (GetX(state), GetOpacity(state));\r\n        return $\"opacity: {opacity};transform:translateX({x});transition:opacity {Duration / 2}ms ease-in-out,transform {Duration}ms ease-in-out;{Style}\";\r\n    }\r\n\r\n    string GetOpacity(TransitionState state) {\r\n        return state switch {\r\n            TransitionState.Entering or TransitionState.Entered =\u003e \"1\",\r\n            _ =\u003e \"0\",\r\n        };\r\n    }\r\n\r\n    double Duration =\u003e 800;\r\n\r\n    string GetX(TransitionState state) {\r\n        return state switch {\r\n            TransitionState.Entering or TransitionState.Entered =\u003e \"0%\",\r\n            _ =\u003e \"-50%\",\r\n        };\r\n    }\r\n\r\n    [Parameter]\r\n    public string? Style { get; set; }\r\n\r\n    [Parameter]\r\n    public string? Class { get; set; }\r\n\r\n    [Parameter]\r\n    public RenderFragment? ChildContent { get; set; }\r\n}\r\n\r\n```\r\n### Public API Reference\r\n| Name          | Type       | Description                                                         |  \r\n| ------------- | ---------- | ------------------------------------------------------------------- |\r\n| Key           | object?    | Gets or sets the key property to detect that should play animation. |\r\n\r\n## Implementation\r\n\r\nDue to the difference in the structure of the Virtual DOM Tree between React and Blazor, it is not possible to easily achieve what is done in react-transition-group in Blazor.\r\nWhen representing nested components in React, the nodes of the RenderTree (children: ReactNode[]) can be touched directly,\r\nWhen building the tree, the map function can be used to\r\nThe tree can be easily filtered or cached in a separate array.\r\nBlazor is not so straightforward.\r\nThere is a fundamental difference between ReactNode in React and RenderFragment in Blazor.\r\nBlazor does not have a way to directly handle RenderTree items like ReactNode does.\r\nThis is because Blazor's RenderFragment is a Delegate.\r\n\r\nReact\r\n```jsx\r\nfunction Component(props) {\r\n  return (\r\n    \u003cdiv\u003e\r\n        {props.children} // ReactNode array\r\n    \u003c/div\u003e\r\n  );\r\n}\r\n```\r\n\r\nBlazor\r\n```razor\r\n\u003cdiv\u003e\r\n@ChildContent // Delegate\r\n\u003c/div\u003e\r\n@code {\r\n    RenderFragment? ChildContent { get; set; }\r\n}\r\n```\r\n\r\nSo I decided to create a wrapper that further wraps the Virtual DOM Tree (RenderTreeFrames) generated by the framework, and then\r\nI decided to restore the deleted nodes by lazy build.\r\n\r\nFirst, by re-evaluating the one-dimensional array of RenderTreeFrames from the RenderTreeBuilder, a wrapper was created to represent them as a tree structure.\r\nFirst, a wrapper is created and cached to represent it as a tree structure by re-evaluating the RenderTreeFrame, a 1D array from RenderTreeBuilder.\r\n\r\nNext, when the state of the tree is updated, the cached RenderTreeFrame is re-evaluated.\r\n2. the next time the state of the tree is updated, the cached RenderTreeFrame wrapper is used again to reconstruct the tree according to the conditions.\r\n\r\nThe new RenderTreeBuilder can then be rendered to the View.\r\nWhen a tree is updated, even if it has been removed from the RenderTreeBuilder\r\nThe node where the animation is taking place (the child component) will be restored from the cache and the Frame will be rendered to the View.\r\nThe node that is animating (child component) can restore the Frame from the cache and wait until the animation is finished.\r\n\r\nWrapper to restore deleted nodes (RenderFrameBuilder)\r\n\r\nhttps://github.com/le-nn/blazor-transition-group/blob/main/src/BlazorTransitionGroup/Internal/RenderFrameBuilder.cs#L91\r\n\r\nFunction to create RenderFrameBuilder\r\n\r\nhttps://github.com/le-nn/blazor-transition-group/blob/main/src/BlazorTransitionGroup/TransitionGroup.cs#L120\r\n\r\nCurrently, transition cannot work unless it is placed directly under TransitionGroup, so it is not possible to completely reproduce react-transition-group.\r\nIn addition, a wrapper for the RenderTree is created and the RenderTreeFrame is rebuilt, so some performance sacrifices must be made.\r\nHowever, we measured the render time for a ``TransitionGroup`` with more than 200 items and a few complex child components in less than a few milliseconds, so we assume that the overhead is negligible.\r\nFunction to create a RenderFrameBuilder\r\n\r\n### Conclusion\r\nThe experience will be similar to react-transition-group, which will be very useful for declarative animations.\r\nIt will be very useful to realize animation in a declarative way 😎\r\n\r\n# License\r\nDesigned with ❤️ by le-nn. Licensed under the MIT License.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fle-nn%2Fblazor-transition-group","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fle-nn%2Fblazor-transition-group","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fle-nn%2Fblazor-transition-group/lists"}