chanan / BlazorStyled

CSS in Blazor Components
https://blazorstyled.io
The Unlicense
191 stars 19 forks source link

Slow CSS after reloading. #98

Closed ghost closed 4 years ago

ghost commented 4 years ago

I tried using BlazorStyled to have a clearer thought process and workflow so far, but I'd like to keep using it so that I can easily parameterize things such as background color etc. for reusable elements. So far there is a huge delay between the page load and the css being applied to the components. Video example: https://gofile.io/d/SiaFXq Note how there are multiple steps of the css being loaded in, first there is none, then you see the label box and the yellow content box (which is hidden by default), then the content box gets hidden away and the next element starts loading.

I think it might be related to issue https://github.com/chanan/BlazorStyled/issues/3 but that issue is all over the place topic wise, so I wasn't sure and decided to open a new one.

I am even unsure if this is really BlazorStyled fault, this is a Blazor server-side app running in debug/development mode and the component is using js interop for a clean animation with variable content heights. Style composition is used as per the examples on your website.

Is there any way to optimize this? Any help and suggestions are welcome.

Thanks and cheers.

chanan commented 4 years ago

Hi,

Thanks for using BlazorStyled!

Here are a few tips:

  1. First of all, update to v3 when it gets released in a hour or so. I did a lot of work on allocations which will help speed up BlazorStyled especially in WASM mode. I plan to keep this work up in the following weeks in months
  2. Make sure you are running in development: false when looking at speed, it should be slightly faster (Version 2.x ignored this argument, but version 3 uses it again)
  3. The <Styled /> is nice to use but it is always going to be slower then using IStyled directly, this is because the tag has to "translate" the RenderFragment to a string that gets passed to IStyled
  4. Lastly, when using IStyled directly you can ensure it only occurs once (unless the string changes) for example, if you are know the css won't change you can put it inside of OnAfterRenderAsync with an if(firstRender)

Just in case anyone reads this that doesn't need these optimizations - using <Styled /> is perfectly fine in most cases, and indeed, it too has some optimizations regarding re-running the same css again if it hasn't changed, so only use the tips above if you know you need them.

One last thing, there is a measurable amount of time to go between .NET and javascript via the IJSInterop. @lupusa1 (on twitter) mentioned there is a trick to greatly speed that up, but I haven't had time to look into yet.

PS - I will also take a look at the code you sent, thank you for the repro.

ghost commented 4 years ago

I forgot to mention - I am using 3.0.0-alpha-07

I tried the app in the up to date Firefox and Opera(chromium) browsers.

Can you elaborate a bit on point 2. ? Does it mean that speed and performance of the app is significantly different and in the order of published > development > debug. I thought there might be a difference, but I wasn't sure if it should be this noticeable. Or am I misunderstanding something?

Thanks again for taking time and your amazing work!

chanan commented 4 years ago

There are two variables you pass it to AddBlazorStyled()

One thing to note is that when you use insertRule the styles become "read only" in DevTools, and you can't edit them on the fly using DevTools, which is why development: true exists.

ghost commented 4 years ago

When running with development: false it is significantly faster. But you still see all the loading steps (maybe for just one frame). But I think I what also contributes greatly to the inconsistent look is the transition that gets applied when the css gets added - which makes the element look like it is growing or popping into existence, while other elements that don't use BlazorStyled are there instantly.

When taking the transition out of the css, you can still see the process of the css getting applied, but it only takes a fracture of a second, which is much better than my original example video showed.

I think I would try using IStyled when I have some more time, but it is not nice for the developer I think. It reminds me of java swing, where there is no MVC and the definition of the looks is jumbled up with the logic.

chanan commented 4 years ago

When more thing you can try to help with that is to hide your div till the CSS is loaded. I started adding that directly into BlazorStyled but haven't gotten it right yet since it is very dependent on the actual situation and doesnt always work right (Transitions is one of those that might not work). Here is an example:

  1. in razor page: https://github.com/chanan/BlazorSlides/blob/master/src/BlazorSlides/Slides.razor#L2 look at the style attribute
  2. In the code behind it is set to visibility hidden: https://github.com/chanan/BlazorSlides/blob/master/src/BlazorSlides/Slides.razor.cs#L22
  3. In the same file in the OnAfterRenderAsync: https://github.com/chanan/BlazorSlides/blob/master/src/BlazorSlides/Slides.razor.cs#L82-L102 - you can see on line 98 it removes the visibility hidden.

It's not a perfect solution, and I think I actually have a better version that I didnt upload to Github, but can't check right now, working on the V3 release :)

ghost commented 4 years ago

Thanks for the great pointers, I think I can work with that, I was thinking about adding a loading screen anyway and this approach seems perfect for that.

I wish you success with the release!

ghost commented 4 years ago

Here is a video showing the comparison between how it was before and after adding the loading screen workaround https://gofile.io/d/t2zPLC

I've put it in a component so that I can wrap it around anything that I want to have a loading animation.

I had to use await Task.Delay(600); in the OnAfterRenderAsync Task before changing the state. It is not a very neat solution, because I use a hard coded delay, so that the transition animation would be done playing at the point where the content is unhidden. There is still a super tiny inconsistency where the BlazorStyled components show up a couple of frames later then the rest of the page. Maybe there is an even better way of doing this, by completely putting the loading element over the hidden content and letting it fade out via a transition as well. But so far I am very happy with this workaround.

The added bonus of this workaround is that if the network is slow, the user sees the loading animation until the page is ready (I think).

Useage example:

<Loading Color="#ff69b4">
   <h1>This content will be displayed after the loading animation</h1>
</Loading>

Loading.razor

@if (IsLoading)
{
    <LoadingIndicator Color="@Color"/>
}
<div style="@_visibility">
    @ChildContent
</div>

@code
{
    [Parameter]
    public Boolean IsLoading { get; set; } = true;

    [Parameter]
    public string Color { get; set; }

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

    private string _visibility => IsLoading ? "visibility: hidden; overflow:hidden; max-height: 0vh;" : "";

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await Task.Delay(600);
        IsLoading = false;
        await InvokeAsync(StateHasChanged).ConfigureAwait(false);
    }
}

LoadingIndicator.razor

<div class="lds-container">
    <div class="lds-ellipsis">
        @for (int i = 0; i < 4; i++)
        {
            <div style="background: @Color"></div>
        }
    </div>
</div>

@code
{
    [Parameter]
    public string Color { get; set; } = "#000000";
}

lds-ellipsis.css (Source: https://loading.io/css/ modified to center horizontally and vertically) height: 100vh would create a scroll bar, so this is another not neat thing.

.lds-container{  
    display: flex;
    flex-flow: column;
    height: 90vh;
}
.lds-ellipsis {
    display: inline-block;
    position: relative;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 80px;
    height: 80px;
}
.lds-ellipsis div {
    position: absolute;
    top: 33px;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    animation-timing-function: cubic-bezier(0, 1, 1, 0);
}
.lds-ellipsis div:nth-child(1) {
    left: 8px;
    animation: lds-ellipsis1 0.6s infinite;
}
.lds-ellipsis div:nth-child(2) {
    left: 8px;
    animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(3) {
    left: 32px;
    animation: lds-ellipsis2 0.6s infinite;
}
.lds-ellipsis div:nth-child(4) {
    left: 56px;
    animation: lds-ellipsis3 0.6s infinite;
}
@keyframes lds-ellipsis1 {
    0% {
        transform: scale(0);
    }
    100% {
        transform: scale(1);
    }
}
@keyframes lds-ellipsis3 {
    0% {
        transform: scale(1);
    }
    100% {
        transform: scale(0);
    }
}
@keyframes lds-ellipsis2 {
    0% {
        transform: translate(0, 0);
    }
    100% {
        transform: translate(24px, 0);
    }
}