Blazor 앵커 스크롤 기능 문의

안녕하세요.
blazor 강의를 보면서 따라서 작업을 하고있는데 강의와 다르게 동작이 안되는 부분이 있어 문의 드립니다.

탭을 누르면 강의에서는 탭이 바뀌는데 제가 그대로 따라할 때는 탭이 변경되지 않습니다…
아래는 해당 소스인데 이유를 알 수 있을까요?

@page "/Samples/TabDemo"

<ul class="nav nav-tabs" id="myTab" role="tablist">
    <li class="nav-item" role="presentation">
        <button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#home" type="button" role="tab" aria-controls="home" aria-selected="true">Home</button>
    </li>
    <li class="nav-item" role="presentation">
        <a class="nav-link" id="profile-tab" data-bs-toggle="tab" href="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
    </li>
    <li class="nav-item" role="presentation">
        <button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false">Contact</button>
    </li>
</ul>
<div class="tab-content" id="myTabContent">
    <div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">tab1</div>
    <div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">tab2</div>
    <div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">tab3</div>
</div>

강의는 2년전 꺼라 동일하게 google에서 bootstrap tab을 검색해서 사이트를 들어가니 강의 에는 탭이 a 테그로 되어 있는데 현재는 button으로 바꼈더라고요…
버튼이 동작하지 않아 강의와 동일하게 profile만 a 테그로 변경해서해 봤는데 profile을 누르면 index페이지로 이동해 버리네요 …

image

2개의 좋아요

저도 예제를 실행해 봤는데, 되지 않더군요.
또한, bootstrap 사이트의 collapse 예제(이 글의 예제와 동일함)를 실행해봐도 안되더군요.

저도 안되는 이유를 모릅니다.
아마 제가 자바 스크립트를 모르고, 자바스크립트가 블레이저에서 어떻게 동작하는지를 모르기 때문일 것입니다.

다만, bootstrap 의 콘트롤(예제의 탭과 같은) 중, 자바 스크립트가 포함된 경우, 이 콘트롤의 상태는 bootstrap 에 포함된 자바 스크립트가 관리하게 됩니다.

그런데, 블레이저에서는 화면 객체의 상태를 C#으로 관리하게 됩니다.

추측컨데, 블레이저가 화면을 리프레시할 때마다, 자바 스크립트가 관리하는 상태를 항상 초기화하는 것 같습니다.

예제를 아래와 같이 C# 코드가 상태 관리를 하도록 변경하면, 문제 없이 동작합니다.

<ul class="nav nav-tabs" id="myTab" role="tablist">
    <li class="nav-item" role="presentation">
        <button class="nav-link @(_tapStates[0] ? "active" : "")" onclick="@(() => SetTapStates(0))" id="home-tab" role="tab" aria-controls="home" aria-selected="true">Home</button>
    </li>
    <li class="nav-item " role="presentation">
        <a class="nav-link @(_tapStates[1] ? "active" : "")" id="profile-tab" onclick="@(() => SetTapStates(1))" data-bs-toggle="tab" href="#profile" type="button" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
    </li>
    <li class="nav-item " role="presentation">
        <button class="nav-link @(_tapStates[2] ? "active" : "")" id="contact-tab" onclick="@(() => SetTapStates(2))" data-bs-toggle="tab" data-bs-target="#contact" type="button" role="tab" aria-controls="contact" aria-selected="false">Contact</button>
    </li>
</ul>
<div class="tab-content" id="myTabContent">
    <div class="@(_tapStates[0] ? _cssActivePane : _cssInactivePane)" id="home" role="tabpanel" aria-labelledby="home-tab">tab1</div>
    <div class="@(_tapStates[1] ? _cssActivePane : _cssInactivePane)" id="profile" role="tabpanel" aria-labelledby="profile-tab">tab2</div>
    <div class="@(_tapStates[2] ? _cssActivePane : _cssInactivePane)" id="contact" role="tabpanel" aria-labelledby="contact-tab">tab3</div>
</div>

@code{
    private bool[] _tapStates = { true, false, false };

    private string _cssActivePane = "tab-pane fade show active";
    private string _cssInactivePane = "tab-pane fade";

    private void SetTapStates(int index)
    {
        Array.Fill(_tapStates, false);
        _tapStates[index] = true;
    }
}

보시다시피, 부트스트랩의 collapse 관련 코드가 있거나 없거나 정상적으로 동작합니다.

흥미로운 점은, 이 예제는 블레이저가 부트스트랩의 상태를 무시하는 경우라 할 수 있는데, 이와 반대로, 무시하지 않는 경우도 있었습니다. 특히 콘트롤의 스타일이 동적으로 변하는 부분인 경우, 부트스트랩의 상태가 C#의 상태가 맞지 않아 원하는 대로 동작하지 결과가 나오죠.
예를 들어, C#코드는 반전하지 않음 상태인데, 부트스트랩은 반전 상태인 경우이죠.

그래서, 화면의 상태 관리는 C#으로 하고, 화면 객체는 그 상태를 바탕으로 초기화하는 구조로 코드를 짜는 습관이 생겼습니다. 즉, 위 예제처럼, 부트스트랩 콘트롤은 언제나 C#이 제공한 상태를 기준으로 초기화되도록 말이죠.

2개의 좋아요

안녕하세요.
해당 블레이저 강의는 오픈된 강의인가요? 저도 직접 실행을 해봐야 알 수 있을 것 같아서요.
실행없이 봤을 때 저도 잘은 모르지만 profile-tab id의 a 태그에는 href 속성이 들어가있네요?
그러면 #profile 경로로 route 되지 않을까요? 그런데 그런 루트가 없어서 홈으로 가구요.

1개의 좋아요

답변 감사합니다.
혹시 해당 소스는 index페이지에서 테스트를 해 보셧을까요?

index페이지가 아닌 기본 템플릿에서 FetchData.razor 파일에서 해당 소소를 넣고 실행 했을때
2번째 profile은 url주소가 바뀌면서 index페이지로 이동 되더라고요 …

몇가지 찾아보던중 다른 해결책이 있어서 공유 드려요.
url = Anchor navigation in a Blazor application - Meziantou's blog

글에서 처럼
AnchorNavigation.razor 파일생성과 Host에

<script>
    function BlazorScrollToId(id) {
        const element = document.getElementById(id);
        if (element instanceof HTMLElement) {
            element.scrollIntoView({
                behavior: "smooth",
                block: "start",
                inline: "nearest"
            });
        }
    }
</script>

위 소스를 추가하고 Demo 페이지 처럼 했을때도 index페이지로 이동을 했습니다.

그래서 저는 여기에 추가 한게 # 앞에 페이지 라우팅 주소를 넣어주니 정상 동작 했습니다.

아래는 샘플 코드입니다.

@page "/Samples/Test"


<strong>Table of Contents</strong>
<ul>
    @for (int i = 0; i < 10; i++)
    {
        <li><a href="@GetHref(i)">Header @i</a></li>
    }
</ul>

@for (int i = 0; i < 10; i++)
{
    <h1 id="@GetId(i)">Header @i</h1>
    @LoremNET.Lorem.Paragraph(wordCount: 30, sentenceCount: 10)
}

@* 👇 Integrate the component *@
<AnchorNavigation />

@code {
    string routingPath = "/Samples/Test";
    string GetId(int i) => "header-" + i;
    string GetHref(int i) => $"{routingPath}#" + GetId(i);
}

정확한 동작원리를 이해하지 못해서 이렇게 하는게 맞는지는 모르겠는데
뭔가 비효율적이긴 한거 같습니다…

2개의 좋아요

예 급하게 하느라, index 에서 실행했습니다. 그래서 다시 다른 페이지로 옮기고 난 후에 profile 탭을 누르니, index로 이동하는 것을 확인했습니다.

index 로 이동하는 문제는, 공유해주신 링크 글 아래에 깃허브 이슈 페이지 링크가 있는데, 그 안에 해결책이 있습니다.

블레이저는 페이지와 함께 Id를 명시해야하나 봅니다.
그래서 아래와 같은 주소는

href=“#profile

index 페이지에서 profile id 를 찾는 것 같습니다.
아래와 같이 링크를 변경하면, 현재 페이지에서 profile id를 검색하기에, index로 이동하지 않습니다.

href=“/Samples/TabDemo/#profile

그 페이지에 해당 id 가 있나 없나는 중요한 문제가 아닌 것 같습니다.
그래서, 아래와 같이 라우팅 주소는 유효하지만, id가 없는 경우에도 문제가 되지 않고, 현재 페이지에 머무르게 됩니다.

href=“/Samples/TabDemo/#as;dfkj;ldksjf”

그러나, 질문의 예제 코드에서, href를 위와 같이 변경한다고 해서 탭이 정상적으로 동작하지는 않습니다.

그 이유 또한 링크된 글에 잘 설명되어 있네요.

블레이저는 싱글 페이지 앱이라 페이지의 리로드가 일어나지 않아, 리로드를 기반한 어떠한 일도 일어나지 않는 것 같습니다.

설명에 나와 있다시피, 서버 프리렌더 모드에서는 예외라고 합니다.

4개의 좋아요

유료 강의라 공유는 못 해드릴거 같습니다…