分享一個 JavaScript 小技巧,假設有個很多項目的清單,靠 CSS overflow-y: scroll 啟用垂直捲軸,除了由使用者操作上下捲動,也能用程式控制捲動到指定的一筆嗎?

用講的不容易理解,看示範就清楚吧! 在以下展示中,我用 div 當清單容器放入 16 個項目 div,清單高度只能顯示四筆,但有垂直捲軸可上下捲動,按鈕可捲動到指定項目並標示為藍底:

感覺挺酷的,說穿了不值錢,其實就是用了一個所有可視元素都支援的 API - Element.scrollIntoView(),負責捲動元素所屬容器讓元素出現在可視範圍。

展示網頁的程式碼如下:線上展示

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <style>
        .container {
            width: 200px; margin: auto;
        }
        button { 
            width: 25px; padding: 0 2px;
        }
        #list {
            width: 96%; height: 92px;
            margin-top: 6px; padding: 3px;
            border: 1px solid gray;
            overflow-y: scroll;
        }
        .active {
            background-color: lightskyblue;
        }
    </style>
</head>

<body>
    <div class="container">
        <div id="buttons"></div>
        <div id="list"></div>
    </div>
    <script>
        function setFocus(no) {
            // 清除目前元素的 .active 樣式
            [].forEach.call(document.querySelectorAll('.active'), function (el) {
                el.classList.remove('active');
            });
            var line = document.getElementById('L' + no);
            line.classList.add('active');
            //捲動 line 所處容器,確保 line 元素在可視範圍
            line.scrollIntoView();
            document.getElementById('B' + no).classList.add('active');
        }
        // 在 #list div 加入 16 筆 div,並建立 16 顆連動按鈕
        for (var i = 1; i <= 16; i++) {
            var line = document.createElement('div');
            line.id = 'L' + i;
            line.textContent = 'LINE ' + i;
            document.getElementById('list').appendChild(line);
            
            var btn = document.createElement('button');
            btn.onclick = function () {
                setFocus(this.innerText);
            };
            btn.innerText = i;
            btn.id = 'B' + i;
            document.getElementById('buttons').appendChild(btn);
        }
    </script>
</body>

</html>

以上寫法在某些情境會出問題。我遇上網頁左側有個超長導覽選單的狀況,scrollIntoView() 會捲過頭:

要解決其實很簡單。呼叫 scrollIntoView() 時可傳入參數的,alignTop 參數 (true/false) 指定向頂端或向底部看齊;或傳入物件參數 scrollIntoViewOptions 指定 behavior (動畫轉場採 auto 或 smooth)、block (垂直對齊依據:start/center/end/nearest,nearest 指依當下位置找最近的對齊點)、inline (水平對齊依據:start/center/end/nearset) 等參數。未傳參數時等同 alignTop = ture / scrollIntoViewOptions: { block: "start", inline: "nearest" },在這個清單捲動範例中,scrollIntoView(false) 設定 alignTop = false / scrollIntoViewOptions: { block: "end", inline: "nearest" } 即可解決。線上展示

2022-10-05 補充

有讀者反映不易想像捲動到清單項目的應用,再補上一個按字母快速捲動到字母起首項目的範例,希望大家會更有感。

程式碼:線上展示

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <style>
        #list {
            width: 96%;
            height: 90px;
            margin-top: 6px;
            padding: 3px;
            border: 1px solid gray;
            overflow-y: scroll;
        }
        #list > div {
            cursor: pointer;
        }
        #list > div:hover {
            background-color: #ddd;
        }
        #sel { width: 96%; margin-top: 5px; }
        .container {
            width: 250px; font-size: 9pt;
        }
    </style>
</head>

<body>
    <div class="container">
        <div>Press A-Z to scroll</div>
        <div id="list" tabindex="0"></div>
        <input type="text" id="sel">
    </div>
    <script>
        var countries = `Afghanistan
Albania
Algeria
//...省略...
Zambia
Zimbabwe`
    </script>
    <script>
        countries.split('\n').forEach(c => {
            var line = document.createElement('div');
            line.textContent = c;
            document.getElementById('list').appendChild(line);
        });
        var listBox = document.getElementById('list');
        listBox.addEventListener('click', e => {
            document.getElementById('sel').value = e.target.innerText;
        });
        listBox.addEventListener('keydown', e => {
            let m = e.code.match('Key([A-Z])');
            if (m) {
                let found = false;
                listBox.childNodes.forEach(line => {
                    if (!found && line.innerText.startsWith(m[1])) {
                        line.scrollIntoView();
                        found = true;
                    }
                });
            }
        });
    </script>

</body>

Example of how to use JavaScript scrollIntoView() API to implementing a list can scroll to selected item automatically.


Comments

# by Jeffery

您好,想請問gif圖片使用者什麼軟體錄製的?

# by Jeffrey

to Jeffery, 地表最強的螢幕擷取軟體 - Snagit,錄製操作影片再轉成 gif

# by Anonymous

免費的如 LICEcap 也不錯使用喔

# by 布丁布丁吃布丁

錄影轉gif也可以用ScreenToGif https://www.screentogif.com/ 選範圍、錄影、編輯結果、存成gif,一氣喝成。

# by IronArks

分享之前有用JQuery做過類似的Code 跟Jeffery的情形不一樣是在於 主要是因為需要將該item拉到頂端(下半有跟資料, 比如用到JqueryUI的According) 用focus的方法會只能保證看到該item(下半可能被遮住) 確保定位要執行二次(因為可能相對位置有時在初始載入不明確) --------------------------------------------------------------------------------------- $("#Content_dialog").scrollTop($("#Content_dialog").scrollTop() + $('#targetItem').position().top); $("#Content_dialog").scrollTop($("#Content_dialog").scrollTop() + $('#targetItem').position().top);

Post a comment