UI5

Page 스크롤 vs ScrollContainer — 영역 분리 #shorts #SAP #UI5

▶ YouTube에서 보기

개요 및 이 글의 목표

SAPUI5 화면을 만들다 보면 "헤더는 고정하고 목록만 스크롤하고 싶다", "좌우 패널의 스크롤을 분리하고 싶다"는 요구가 자주 등장합니다. 기본 sap.m.Page는 콘텐츠 전체를 하나의 스크롤 영역으로 묶기 때문에, 부분 스크롤이 필요한 시점에는 sap.m.ScrollContainer가 핵심 빌딩 블록이 됩니다. 이 글에서는 ScrollContainer의 동작 원리와 height/width 설정의 함정, 그리고 실무에서 마주치는 레이아웃 패턴을 단계별로 다룹니다.

핵심 개념 — Page 스크롤 vs ScrollContainer 독립 영역

ScrollContainer를 이해하려면 "스크롤은 누가 갖는가"라는 질문에서 출발해야 합니다. sap.m.Page는 기본적으로 enableScrolling="true" 상태로, 페이지의 헤더/푸터를 제외한 콘텐츠 전체에 하나의 스크롤 영역을 부여합니다. 즉 Page 안의 List가 길어지면 화면 전체가 위아래로 움직입니다.

반면 ScrollContainer는 "내부에 독립된 뷰포트를 하나 더 만든다"는 컨셉입니다. 마치 문서 안에 작은 메모장을 끼워 넣는 것과 비슷합니다. 메모장 내부 스크롤은 본문에 영향을 주지 않고, 그 반대도 마찬가지입니다.

여기서 가장 중요한 함정은 height와 width입니다. ScrollContainer가 스크롤바를 만들려면 자기 자신의 크기를 알아야 합니다. height="100%"를 적용해도 부모가 명확한 픽셀 높이를 갖지 않으면 자식의 100%는 0으로 계산됩니다. 따라서 다음 중 하나의 조건을 만족시켜야 합니다.

  • 고정 단위 직접 지정: height="400px", height="30rem"
  • 부모 컨테이너가 FlexBox로 실제 픽셀 높이를 보장
  • Page의 enableScrolling="false" 상태에서 가용 공간을 ScrollContainer가 차지

vertical/horizontal 속성은 각각 세로/가로 스크롤 허용 여부입니다. 기본값은 vertical=true, horizontal=false이며, 가로로 긴 차트나 타임라인을 보여줄 때 horizontal=true로 바꿔 사용합니다.

1단계: 기본 독립 스크롤 영역

가장 기본적인 패턴입니다. Page 스크롤을 끄고 ScrollContainer만 스크롤을 담당하도록 구성합니다.

<mvc:View
    controllerName="zui5.scroll.controller.Basic"
    xmlns="sap.m"
    xmlns:mvc="sap.ui.core.mvc">
    <Page title="ScrollContainer 기본" enableScrolling="false">
        <content>
            <ScrollContainer
                height="400px"
                width="100%"
                vertical="true"
                horizontal="false"
                focusable="true">
                <VBox>
                    <Text text="항목 1" class="sapUiSmallMargin"/>
                    <Text text="항목 2" class="sapUiSmallMargin"/>
                </VBox>
            </ScrollContainer>
        </content>
    </Page>
</mvc:View>

핵심은 enableScrolling="false"height="400px" 두 줄입니다. Page 스크롤을 끄지 않으면 두 개의 스크롤바가 겹쳐 사용자가 어디를 잡고 움직여야 할지 헷갈리게 됩니다.

2단계: 헤더 고정 + 목록만 스크롤

주문 내역 화면처럼 상단에 필터와 합계 카드가 고정되고, 그 아래 List만 스크롤되는 실무 패턴입니다.

<Page title="주문 내역" enableScrolling="false">
    <content>
        <VBox height="100%">
            <!-- 고정 헤더 영역 -->
            <Panel class="sapUiNoMargin">
                <HBox justifyContent="SpaceBetween" class="sapUiSmallMargin">
                    <Title text="이번 달 주문 38건"/>
                    <SearchField width="240px" search=".onSearch"/>
                </HBox>
            </Panel>
            <!-- 스크롤되는 영역 -->
            <ScrollContainer
                height="calc(100vh - 180px)"
                width="100%"
                vertical="true">
                <List
                    items="{/orders}"
                    noDataText="조회된 주문이 없습니다">
                    <StandardListItem
                        title="{orderNo}"
                        description="{customer}"
                        info="{amount}"/>
                </List>
            </ScrollContainer>
        </VBox>
    </content>
</Page>

calc(100vh - 180px)를 사용하면 헤더와 푸터의 실제 높이를 빼고 남은 공간을 ScrollContainer가 차지합니다. 반응형 환경에서도 안정적으로 동작하며, 헤더 높이가 바뀌어도 빼는 값만 조정하면 됩니다.

3단계: 동적 높이 재계산과 리사이즈 대응

프로덕션에서는 윈도우 크기 변경 시 ScrollContainer 높이를 동적으로 재계산해야 합니다.

sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/ui/model/json/JSONModel",
    "sap/base/Log"
], function (Controller, JSONModel, Log) {
    "use strict";
    return Controller.extend("zui5.scroll.controller.Orders", {
        onInit: function () {
            var oViewModel = new JSONModel({ scrollHeight: "60vh" });
            this.getView().setModel(oViewModel, "view");
            sap.ui.Device.resize.attachHandler(this._onResize, this);
            this._onResize();

            var oModel = new JSONModel();
            oModel.loadData("/api/orders")
                .catch(function (oErr) {
                    Log.error("데이터 로드 실패: " + oErr.message, "Orders");
                });
            this.getView().setModel(oModel);
        },
        _onResize: function () {
            var iAvailable = window.innerHeight - 160 - 48;
            this.getView().getModel("view")
                .setProperty("/scrollHeight", iAvailable + "px");
        },
        onExit: function () {
            sap.ui.Device.resize.detachHandler(this._onResize, this);
        }
    });
});

View XML에서는 바인딩으로 높이를 연결합니다.

<ScrollContainer
    height="{view>/scrollHeight}"
    width="100%"
    vertical="true"
    focusable="true">

자주 겪는 함정과 해결법

스크롤바가 보이지 않습니다.
대부분 height가 0인 경우입니다. 브라우저 개발자 도구에서 해당 요소의 computed height를 확인하세요. Page의 enableScrolling이 true인 채로 두면 Page가 먼저 스크롤을 가져가 ScrollContainer는 무한히 늘어나기만 합니다.

iOS Safari에서 관성 스크롤이 어색합니다.
중첩된 ScrollContainer가 있으면 터치 이벤트가 부모로 전파되며 끊김이 생깁니다. 가능하면 중첩은 피하고, 필요한 경우 외부에 horizontal만, 내부에 vertical만 두는 식으로 방향을 분리하세요.

List의 growingScrollToLoad가 동작하지 않습니다.
ScrollContainer로 List를 감싸면 스크롤 주체가 ScrollContainer로 바뀌어 List가 "끝에 도달했다"는 신호를 받지 못합니다. growing 대신 사용자에게 "더보기" 버튼을 제공하는 패턴으로 바꾸는 것이 안정적입니다.

응용 패턴 — DynamicPage와의 선택 기준

ScrollContainer로 직접 구현하기 전에 sap.f.DynamicPage를 먼저 검토하는 것을 권장합니다. DynamicPage는 스크롤 시 헤더가 접히는 패턴을 기본 제공하므로, 헤더 고정이 주 목적이라면 더 선언적인 방법입니다. 좌우 패널이 각자 스크롤되는 레이아웃이 필요하다면 sap.ui.layout.Splitter도 함께 검토하세요.

댓글 0

아직 댓글이 없습니다.