用 HTML 元素實現自定義的滾動條

有時,瀏覽器默認的滾動條不能知足需求,咱們要實現自定義的滾動條。藉助於鼠標移動事件和滾輪事件,以及內容元素的滾動相關屬性,能夠很容易地實現這樣的需求。下面就來試一試。css

咱們此次要實現的滾動條須要有如下功能或要素:html

  • 可拖動的滑塊;
  • 滾動條兩端有能夠小幅度滾動的按鈕;
  • 滑塊與兩端按鈕之間的區域可點擊以進行大幅度滾動,這點與常見的滾動條一致;
  • 在內容區域上滾動鼠標滾輪時可滾動內容;
  • 內容區域滾動到上下兩端時繼續滾動鼠標滾輪,應滾動整個頁面,這點與大頁面中包含小的可滾動區域時的行爲一致。

滾動區域包含上下兩頭高度固定的、點擊以後進行小幅滾動的按鈕,固定高度的滑塊,以及它們之間的、點擊以後進行大幅滾動的區域。咱們讓下側大幅滾動點擊區域佔滿其餘元素餘下的空間,改變上側大幅滾動點擊區域的高度,就能達到滑塊滑動的效果。chrome

下面來看代碼,代碼的說明包含在註釋裏。瀏覽器

HTML:app

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>自定義滾動條</title>
    <link href="styles.css" rel="stylesheet" />
</head>
<body>
    <!--滾動視圖-->
    <div id="scrollView">
        <!--滾動內容-->
        <div id="scrollContent"></div>
        <!--滾動條區域-->
        <div id="scrollTrack">
            <!--小幅度向上滾動按鈕-->
            <div id="btnUp"></div>
            <!--大幅度向上滾動點擊區域-->
            <div id="trackUp"></div>
            <!--滾動條滑塊-->
            <div id="scrollBar"></div>
            <!--大幅度向下滾動點擊區域-->
            <div id="trackDown"></div>
            <!--小幅度向下滾動按鈕-->
            <div id="btnDown"></div>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

CSS:函數

#scrollView {
    width: 320px;
    height: 300px;
    /*上下邊距是爲了演示當滾動到頂部或底部時,繼續滾動鼠標滾輪會滾動整個頁面*/
    margin: 100px 0;
    display: flex;
}

#scrollContent {
    width: 300px;
    height: 300px;
    padding-left: 8px;
    background-color: antiquewhite;
    /*隱藏超出滾動內容區域的元素*/
    overflow: hidden;
}

#scrollTrack {
    width: 20px;
    height: 300px;
    background-color: cadetblue;
    display: flex;
    /*豎着排列滾動條區域內的上下按鈕、滑塊等元素*/
    flex-direction: column;
}

#btnUp, #btnDown {
    height: 20px;
    background-color: brown;
}

#scrollBar {
    height: 50px;
    background-color: darkblue;
}

#trackDown {
    /*讓大幅度向下滾動點擊區域佔據排列完其餘元素後剩下的全部區域*/
    flex-grow: 1;
}

/*當拖動滑塊時,給body元素加上該類,防止鼠標的拖動致使網頁內容的選擇操做*/
.unselectable {
    /*當前版本的火狐(53)和Edge(15)不支持user-select標準屬性,須要使用瀏覽器廠商前綴*/
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

JavaScript:flex

var scrollContent = document.getElementById("scrollContent");

//給滾動內容區域填充大量元素
for (var i = 1; i <= 300; i++) {
    var row = document.createElement("div");
    row.innerText = "第" + i + "行";
    scrollContent.appendChild(row);
}

//指示鼠標左鍵是否處於按下狀態的變量,在滑塊上按下鼠標左鍵時設爲true,在頁面上任意位置鬆開時設回false
//因爲鼠標拖動滑塊時可能會離開滑塊,因此mouseup和mousemove事件是註冊在window上的
//在mousemove事件處理程序中,會檢查該變量,以肯定當前是否在拖動滑塊
var mouseHeld = false;
//記錄上一次mousemove事件發生時,鼠標的Y軸位置,每次發生mousemove事件時,跟上一次做比較,肯定須要滾動多少距離
var previousClientY = 0;
//滑塊可滑動的距離,計算方式爲整個滾動條高度度減去上下按鈕的高度,再減去滑塊自己的高度
var barMoveLength = 300 - 20 * 2 - 50;
//內容區域可滾動的距離,計算方式爲內容區域的總高度減去內容區域自己的高度
var contentMoveLength = scrollContent.scrollHeight - 300;

//爲上下按鈕註冊事件處理程序
document.getElementById("btnUp").addEventListener("click", function () {
    scrollToRelative(-30);
});
document.getElementById("btnDown").addEventListener("click", function () {
    scrollToRelative(30);
});

//爲大幅度上下滾動點擊區域註冊事件處理程序
//保存trackUp元素變量,由於每次滾動時,都要改變它的高度,以達到移動滑塊的效果
var trackUp = document.getElementById("trackUp");
trackUp.addEventListener("click", function () {
    scrollToRelative(-300);
});
document.getElementById("trackDown").addEventListener("click", function () {
    scrollToRelative(300);
});

//爲滑塊註冊鼠標按下事件處理程序,由於只有在滑塊上按下鼠標左鍵時,纔算開始拖動滑塊
document.getElementById("scrollBar").addEventListener("mousedown", function (e) {
    mouseHeld = true;
    previousClientY = e.clientY;
    //防止頁面由於鼠標的拖動而選擇上了文本或其餘元素
    document.body.classList.add("unselectable");
});

//鼠標左鍵鬆開時可能不在滑塊上,因此mouseup事件註冊在document上
document.addEventListener("mouseup", function (e) {
    mouseHeld = false;
    //讓頁面恢復可選擇
    document.body.classList.remove("unselectable");
});

//鼠標拖動時可能離開滑塊,因此mousemove事件也註冊在document上
document.addEventListener("mousemove", function (e) {
    if (mouseHeld) {
        //相對滑動距離計算依據爲滑塊滑動距離佔總可滑動距離的比應與內容滾動距離佔總可滾動距離的比相等
        scrollToRelative((e.clientY - previousClientY) * contentMoveLength / barMoveLength);
        previousClientY = e.clientY;
    }
});

//爲內容區域註冊鼠標滾輪事件處理程序
//火狐瀏覽器使用和其餘瀏覽器不一樣的滾輪事件和事件參數屬性
if (navigator.userAgent.indexOf("Firefox") < 0) {
    scrollContent.addEventListener("mousewheel", function (e) {
        handleMouseWheel(-e.wheelDelta, e);
    });
} else {
    scrollContent.addEventListener("DOMMouseScroll", function (e) {
        handleMouseWheel(e.detail * 30, e);
    });
}

//肯定內容區域當前是否在頂部或底部
function isOnTopOrBottom() {
    //判斷是否在底部時,用了向上取整函數,由於在chrome下,滾動到底時,scrollTop常爲小數,與contentMoveLength不等,向上取整以後通常相等
    return scrollContent.scrollTop == 0 || Math.ceil(scrollContent.scrollTop) == contentMoveLength;
}

//鼠標滾輪事件的處理程序,relative爲相對滾動距離,e爲事件參數
function handleMouseWheel(relative, e) {
    //記錄下滾動以前內容區域是否在兩端
    var previousOnTopOrBottom = isOnTopOrBottom();
    scrollToRelative(relative);
    //若是如今不在兩端,或者如今在兩端而滾動以前不在,則屏蔽默認滾輪行爲————滾動整個頁面
    //反過來講,只有當「滾動」(實際上內容區域未滾動)先後內容區域都在某一端時,即已經到兩端以後繼續滾動時,才讓滾動整個頁面
    if (!isOnTopOrBottom() || (isOnTopOrBottom() && !previousOnTopOrBottom)) {
        e.preventDefault();
    }
}

//將內容區域滾動到某一絕對位置
function scrollTo(top) {
    if (top < 0) {
        scrollContent.scrollTop = 0;
    } else if (top > contentMoveLength) {
        scrollContent.scrollTop = contentMoveLength;
    } else {
        scrollContent.scrollTop = top;
    }

    //設置滑塊的位置,這是經過設置滑塊上面的大幅度向上滾動點擊區域的高度實現的
    //滑塊位置計算依據爲滑塊距頂部距離佔總可滑動距離的比應與內容區域距頂部距離佔總可滾動距離的比相等
    var barDownDistance = scrollContent.scrollTop * barMoveLength / contentMoveLength;
    trackUp.style.height = barDownDistance + "px";
}

//將內容區域滾動某一相對距離
function scrollToRelative(relative) {
    scrollTo(scrollContent.scrollTop + relative);
}

最終效果:spa

這篇隨筆主要演示製做自定義滾動條的技術要點,固然還有一些能夠改進的地方,好比根據內容的多少設置不一樣的滑塊高度,內容不足以滾動時隱藏滾動條,橫向滾動條以及代碼的可複用等。code

相關文章
相關標籤/搜索