有時,瀏覽器默認的滾動條不能滿足需求,我們要實現自定義的滾動條。借助於鼠標移動事件和滾輪事件,以及內容元素的滾動相關屬性,可以很容易地實現這樣的需求。下面就來試一試。
我們這次要實現的滾動條需要有以下功能或要素:
- 可拖動的滑塊;
- 滾動條兩端有可以小幅度滾動的按鈕;
- 滑塊與兩端按鈕之間的區域可點擊以進行大幅度滾動,這點與常見的滾動條一致;
- 在內容區域上滾動鼠標滾輪時可滾動內容;
- 內容區域滾動到上下兩端時繼續滾動鼠標滾輪,應滾動整個頁面,這點與大頁面中包含小的可滾動區域時的行為一致。
滾動區域包含上下兩頭高度固定的、點擊之后進行小幅滾動的按鈕,固定高度的滑塊,以及它們之間的、點擊之后進行大幅滾動的區域。我們讓下側大幅滾動點擊區域占滿其他元素余下的空間,改變上側大幅滾動點擊區域的高度,就能達到滑塊滑動的效果。
下面來看代碼,代碼的說明包含在注釋里。
HTML:
<!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:
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); }
最終效果:
這篇隨筆主要演示制作自定義滾動條的技術要點,當然還有一些可以改進的地方,比如根據內容的多少設置不同的滑塊高度,內容不足以滾動時隱藏滾動條,橫向滾動條以及代碼的可復用等。