今天處理到一個網頁需求,排版需求為上方區塊大小固定,剩餘高度擺入超長清單,清單要可垂直捲動。這類情境,自然要優先考慮用佔滿剩餘網頁寬/高度的美妙解法 - CSS Flexbox搞定,做法如下:

<!DOCTYPE html>
<html>

<head>
    <style>
        html, body {
            height: 100%; margin: 0; padding: 0; font-size: 10pt;
            font-family: Verdana, Geneva, Tahoma, sans-serif;
        }
        .main {
            height: 100%; display: flex; flex-direction: column;
        }
        .banner {
            flex-basis: 40px;
            flex-shrink: 0;
            background-color: #bbb;
        }
        .content {
            flex-grow: 1;
            background-color: #eee;
            overflow-y: auto; border: 1px solid gray;
        }
        .long {
            height: 2000px; background-color: #fff;
        }
        span { font-size: 9pt; color: #777; }        
    </style>
</head>

<body>
    <div class="main">
        <div class="banner">
            Banner <span>height: 40px</span>
        </div>
        <div class="content">
            Content <span>flow-grow: 1; overflow-y: auto;</span>
            <div class="long">
                Long Content <span>height: 2000px</span>
            </div>
        </div>
</body>

</html>

.main 容器設 display: flex,垂直排列 flex-direction: column;.banner 設固定高度 flex-basis: 40px (或 height: 40px,效果相同) 並指定 flex-shrink: 0 避免高度不足時被壓縮;最下方的 .content 則設 flex-grow: 1 佔滿剩餘高度,並指定 overflow-y: auto 當內容超出 .content 高度時顯示垂直捲軸;接著在 .content 放入高度為 2000px 的超長 DIV,規格達成:

然而,實際排版還有些額外要求,.main 要先分成 .top (固定高度 40px) 及 .container (吃滿剩餘頁面高度),.banner 及 .content 則搬進 .container 內,.container 的高度扣除 .banner 固定高度 40px,餘下都留給 .content 顯示可垂直捲動的超長清單。(註:真實案例比這個更複雜些,因為有水平方向的排版要求,無法簡化將 .top 40px、.banner 40px、.content flex-grow: 1 放在同一層縮放,只能寫成巢狀,這個範例為可重現問題的簡化版本)

感覺不難,該 .main 包 .container 做出兩層 display: flex; flex-direction: column 應該就可以了:

<!DOCTYPE html>
<html>

<head>
    <style>
        html, body {
            height: 100%; margin: 0; padding: 0; font-size: 10pt;
            font-family: Verdana, Geneva, Tahoma, sans-serif;
        }
        .main {
            height: 100%; display: flex; flex-direction: column;
        }
        .top {
            height: 40px; flex-shrink: 0; background-color: #aaa;
        }
        .container {
            flex-grow: 1; display: flex; flex-direction: column;
        }

        .banner {
            flex-basis: 40px; flex-shrink: 0; background-color: #bbb;
        }
        .content {
            flex-grow: 1; background-color: #eee;
            overflow-y: auto; border: 1px solid gray;
        }
        .long {
            height: 2000px; background-color: #fff;
        }
        span { font-size: 9pt; color: #777; }      
    </style>
</head>

<body>
    <div class="main">
        <div class="top">
            → Top <span>height: 40px</span> <br />
            ↓ Container <span>flow-grow: 1</span>
        </div>
        <div class="container">
            <div class="banner">
                Banner <span>height: 40px</span>
            </div>
            <div class="content">
                Content <span>flow-grow: 1; overflow-y: auto;</span>
                <div class="long">
                    Long Content <span>height: 2000px</span>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

事與願違,.content 被 .long 撐開高度,整個頁面跟著拉長,有看到垂直捲軸,卻是整個網頁一起捲。

經過一番研究,學到神奇解法,在 .content 的 CSS 樣式加入 height: 0;: (註:建議改用文末補充之 min-height 解法)

.content {
    flex-grow: 1; background-color: #eee;
    overflow-y: auto; border: 1px solid gray;
    height: 0;
}

問題就這麼解決了!

關於這個解法的原理,似乎可以歸因到 CSS Flexbox 規格:4.5. Automatic Minimum Size of Flex Items,不少討論提到可藉由設定 min-height: 0 解決,但我實測的結果設定 height: 0 才有效,網路上也有些類似回報:12,推測可能與 Edge/Chrome 瀏覽器實作有關。

【2023-06-27 更新】發現更好的解法,改在上一層 .container 加入 min-height: 0,效果相同,且語意上不易混淆。

.container {
    flex-grow: 1; display: flex; flex-direction: column;
    min-height: 0;
}

Overflow issues in CSS flexbox layout.


Comments

Be the first to post a comment

Post a comment