javascript jscroll類比html元素捲軸_javascript技巧

來源:互聯網
上載者:User

主流瀏覽器預設為html元素提供的捲軸不美觀,而且前端開發人員想對其通過css進行統一樣式的美化也是不可實現的。比如ie可以通過樣式來實現簡單的美化、Webkit核心瀏覽器可以控制捲軸的顯示效果,firefox則不允許使用者為捲軸定義樣式。但是對於追求友好的使用者體驗的前端開發人員,是不會被這些瀏覽器的不一致行為所阻止的。我們可以自己通過標準的html元素類比來實現自訂的捲軸。

這裡是自己在工作不太忙的時候寫出來了一個使用者可以自訂的捲軸jscroll,以下簡稱jscroll。jscroll預設只提供一種捲軸樣式,部分樣式來自google webstore ,其中有部分css3樣式主要用於實現圓角,陰影製作效果。為實現跨瀏覽器情況下捲軸顯示效果的一致,對於ie6, 7, 8不支援css3的瀏覽器引入了 PIE.htc 檔案。下面就實現的功能以及相容性、使用方法、具體代碼實現分別做一下講解。

實現功能以及相容性

jscroll實現了系統預設捲軸的幾乎所有功能,比如:拖動捲軸查看內容、滾動滑鼠滾輪查看內容、點擊捲軸可到達地區的上方或者下方來觸發捲軸的滾動、鍵盤上下鍵來觸發捲軸的滾動。firefox、chrome,、ie9+ 等最新瀏覽器支援css3以及js的最新API,所以沒有任何相容性問題。ie6, 7, 8 不支援css3通過引入PIE.htc 的hack檔案來做相容處理。js方面對於不支援的API通過舊的API來做了相容。有最大相容性問題的瀏覽器是ie6,不支援點擊捲軸可到達地區來觸發捲軸滾動,也不支援鍵盤上下鍵來觸發捲軸的滾動。導致這個問題的原因主要是因為引入了支援css3的PIE.htc檔案,如果不引入該hack檔案,所有操作都能支援,沒法辦為了顯示效果的一致,只好選擇了不支援部分功能。

使用方法

使用自訂捲軸最多的情況應該是頁面彈出層,或者是頁面上某一個地區,千萬不要對整個頁面的捲軸進行自訂動作。對於需要使用jscroll的元素,需要添加自訂屬性data-scroll="true"來告訴程式需要使用jscroll來替換系統預設的捲軸,同時還需要通過添加自訂屬性data-width=""、data-height=""來指定元素要顯示的寬度和高度。jscroll會根據使用者定義的寬度和高度計算內容的顯示寬度以及捲軸顯示的高度並添加互動的事件。

具體代碼實現

jscroll的實現邏輯並不複雜,實現具體功能的js代碼不到400行,但是這裡依賴了一些基礎的方法,所以需要引入squid.js作為基礎方法的支援。對捲軸樣式的控制的css在一個單獨的jscroll-1.0.css檔案裡面,使用者可以自己修改擴充以滿足自己的需求。下面是對實現具體功能的每個方法做一個簡單的分析:

複製代碼 代碼如下:

init: function(selector, context) {
selecotr = selector || 'data-scroll'
context = context || document

var elems = squid.getElementsByAttribute(selector, context)
this.initView(elems)
}

init()是初始化函數,根據指定selector和context擷取需要使用自訂捲軸的元素,selector預設是data-scroll,上下文預設是當前document。這裡無論元素自訂屬性data-scroll="true"或者data-scroll="false"都會使用自定捲軸覆蓋系統預設捲軸,squid的getElementsByAttribute()方法只是提供通過元素是否有指定屬性來尋找元素而忽略屬性值,這個方法沒有jquery選取器或者進階瀏覽器提供的querySelectorAll()方法強大,因為這裡squid只是做最基本的支援。找到需要自訂捲軸的元素之後調用initView方法來初始化捲軸整體結構和顯示。
複製代碼 代碼如下:

initView: function(elems) {
var i = 0,
length = elems.length,
elem;

for(; i < length; i++) {
elem = elems[i]
if(this.hasScroll(elem)) {
this.create(elem)
}
}

this.initEvent()
}

initView()方法會首先對頁面上擷取的帶有自訂屬性data-scroll的元素遍曆,判斷每一個元素是否會出現捲軸,通過hasScroll()方法判斷。如果元素會出現捲軸則調用create()方法分別建立自訂的捲軸。initView()方法結束會調用initEvent()方法。
複製代碼 代碼如下:

//是否有捲軸
hasScroll: function(elem) {
return elem.offsetHeight < elem.scrollHeight
}

hasScroll()方法用於判斷元素是否會出現捲軸,返回true或者false。這裡忽略元素的margin和padding,通過jscroll建立的捲軸預設margin和padding都是0。
複製代碼 代碼如下:

//建立捲軸元素整體結構
create: function(elem) {
var wrapper,
list,
//捲軸元素
s,
//帶捲軸元素顯示的高度
height = elem['data-height'] || elem.getAttribute('data-height'),
//帶捲軸元素顯示的寬度
width = elem['data-width'] || elem.getAttribute('data-width'),
//捲軸顯示高度
value;

//wrapper
wrapper = document.createElement('div')
wrapper.className = 'jscroll-wrapper'
//forbid select text, for ie9
/*
* wrapper.onselectstart = function() {
* return false
* }
*/
squid.css(wrapper, {
height: height + 'px',
width: width + 'px'
})

squid.addClass(elem, 'jscroll-body')
//overwrite the user define style
squid.css(elem, {
overflow: 'visible',
position: 'absolute',
height: 'auto',
width: (width - 40) + 'px',
padding: '0 20px 0 23px'
})

//list
list = document.createElement('div')
list.className = 'jscroll-list unselectable'<BR> list.unselectable = 'on'
squid.css(list, {
height: (height - 5) + 'px'
})

//捲軸
s = document.createElement('div')
s.className = 'jscroll-drag unselectable'
s.unselectable = 'on'
s.setAttribute('tabindex', '1')
s.setAttribute('hidefocus', true)

list.appendChild(s)
wrapper.appendChild(list)
//把需要出現捲軸的元素包裹起來
elem.parentNode.replaceChild(wrapper, elem)
wrapper.insertBefore(elem, list)

//捲軸高度
value = this.scrollbarHeight(elem, height)
squid.css(s, {
height: value + 'px'
})

//add event
this.regEvent(wrapper)
}

create()方法使用者調整建立帶有自訂捲軸的元素整體結構,首先通過建立了wrapper元素,用於封裝會出現捲軸的元素elem和捲軸可滾動的地區元素list以及捲軸元素s。通過從出現捲軸元素設定的自訂屬性data-width、data-height分別設定wrapper元素的寬度和高度。通過scrollbarHeight()方法計算得到了捲軸元素顯示的高度,整體結構不算複雜。建立自訂捲軸整體結構之後是為捲軸元素s和捲軸可到達地區元素list添加事件處理,通過regEvent()方法實現。
複製代碼 代碼如下:

//計算捲軸的高度
scrollbarHeight: function(elem, height) {
var value = elem.scrollHeight;

return (height / value) * height
}

scrollbarHeight()方法通過簡單的數學計算返復原動條元素應該顯示的高度。
複製代碼 代碼如下:

initEvent: function() {
var that = this,
_default,
elem,
top,
min,
max,
prev,
parent,
sbody,
unit;

//捲軸滾動
squid.on(document, 'mousemove', function(event) {
elem = that.scrolling.elem
if(elem !== null) {
squid.addClass(elem, 'scrolling')
top = event.clientY - that.scrolling.diffy
parent = that.ie6 ? elem.parentNode.parentNode : elem.parentNode
min = that.limits[elem].min
max = that.limits[elem].max
prev = parent.previousSibling
sbody = prev.tagName.toLowerCase() === 'div' ? prev : prev.previousSibling
_default = parseInt(sbody['data-height'] || sbody.getAttribute('data-height'), 10)
unit = (sbody.scrollHeight - _default) / max

squid.addClass(sbody.parentNode, 'unselectable')
if(top < min) {
top = min
}else if(top > max) {
top = max
}
elem.style.top = top + 'px'
that.doScroll(sbody, top * unit)
}
})

//滾動結束
squid.on(document, 'mouseup', function(event) {
elem = that.scrolling.elem
if(elem) {
prev = that.ie6 ? elem.parentNode.parentNode.previousSibling : elem.parentNode.previousSibling
sbody = prev.tagName.toLowerCase() === 'div' ? prev : prev.previousSibling
squid.removeClass(sbody.parentNode, 'unselectable')
}

that.scrolling.elem = null
that.scrolling.diffy = 0
})
}

initEvent()方法實現了為document元素添加mousemove和mouseup事件,mousemove實現了在拖動捲軸元素滾動時查看的內容跟隨變化。代碼首先判斷當前是否有拖動捲軸查看內容的操作,如果有就計算捲軸被拖動到的位置,然後計算查看內容應該到的地方。代碼裡對ie6的判斷,是因為引入的PIE.htc檔案破壞了原有的結構(為了實現跨瀏覽器下顯示效果的一致,付出太大了!!!)。mouseup事件處理常式實現了清除上次操作的捲軸元素。
複製代碼 代碼如下:

//添加捲軸事件
regEvent: function(elem) {
var that = this,
sbody = elem.firstChild,
list = sbody.nextSibling,
//捲軸元素
s = list.firstChild,
//捲軸滾動最小值
min = 0,
//捲軸滾動最大值
max = list.offsetHeight - s.offsetHeight,
_default = parseInt(sbody['data-height'] || sbody.getAttribute('data-height'), 10),
unit = (sbody.scrollHeight - _default) / max,
//firefox瀏覽器
firefox = 'MozBinding' in document.documentElement.style,
//滑鼠滾輪事件
mousewheel = firefox ? 'DOMMouseScroll' : 'mousewheel',
//opera瀏覽器
opera = window.oprea && navigator.userAgent.indexOf('MSIE') === -1,
//is firing mousedown event
firing = false,
//滑鼠點擊,定時器執行時間
interval,
//捲軸距離容器高度
top,
//捲軸當前top值
cur,
//每次滾動多少像素
speed = 18;

//變數緩衝min, max
this.limits[s] = {
min: 0,
max: max
}
//scroll事件 滑鼠滑動滾輪移動捲軸
squid.on(elem, mousewheel, function(event) {
var delta;

if(event.wheelDelta) {
delta = opera ? -event.wheelDelta / 120 : event.wheelDelta / 120
}else{
delta = -event.detail / 3
}

cur = parseInt(s.style.top || 0, 10)
//向上滾動
if(delta > 0) {
top = cur - speed
if(top < min) {
top = min
}
}else{//向下滾動
top = cur + speed
if(top > max) {
top = max
}
}
s.style.top = top + 'px'
that.doScroll(sbody, top * unit)

//阻止body元素捲軸滾動
event.preventDefault()
})

//ie6, 7, 8下,如果滑鼠連續點擊兩次且時間間隔太短,則第二次事件不會觸發
//拖動捲軸,點擊捲軸可到達地區
squid.on(list, 'mousedown', function(event) {
var target = event.target,
y = event.clientY;

target = event.target
if(target.tagName.toLowerCase() === 'shape')
target = s

//滑鼠點擊元素是捲軸
if(target === s) {
//invoke elem setCapture
s.setCapture && s.setCapture()

that.scrolling.diffy = y - s.offsetTop
//滑鼠移動過程中判斷是否正在拖動捲軸
that.scrolling.elem = s
}else if(target.className.match('jscroll-list')){
firing = true
interval = setInterval(function() {
if(firing) {
that.mouseHandle(list, y, unit)
}
}, 80)
}
})

//滑鼠鬆開捲軸停止滾動
squid.on(list, 'mouseup', function() {
//invoke elem releaseCapture
s.releaseCapture && s.releaseCapture()

firing = false
clearInterval(interval)
})

//捲軸元素擷取焦點,可以觸發keyup事件
squid.on(s, 'click', function() {
this.focus()
})

//捲軸擷取焦點後,觸發鍵盤上下鍵,捲軸滾動
squid.on(s, 'keydown', function(event) {
var keyCode = event.keyCode,
state = false;

cur = parseInt(s.style.top || 0, 10)
switch(keyCode) {
case 38:
top = cur - speed
if(top < min) {
top = min
}
state = true
break
case 40:
top = cur + speed
if(top > max) {
top = max
}
state = true
break
default:
break
}
if(state) {
s.style.top = top + 'px'
that.doScroll(sbody, top * unit)
}

event.preventDefault()
})
}

regEvent()方法實現了以下功能,應該是jscroll組件的核心方法了:

1. 滑鼠在包含捲軸的元素地區,上下滾動滑鼠滾輪,查看的內容跟隨滾輪上下翻的功能

2. 點擊捲軸可到達地區,即捲軸上方或者下方,捲軸和查看的內容向上或者向下滾動。滑鼠點擊捲軸可到達地區不鬆開,可連續滾動捲軸和查看的內容,通過調用mouseHandle()方法來具體實現該功能。

3. 點擊捲軸元素後,可以通過鍵盤上下鍵來觸發捲軸和查看內容的滾動

複製代碼 代碼如下:

//滑鼠點擊捲軸可到達地區上面或者下面時,捲軸滾動
mouseHandle: function(elem, place, unit) {
var prev = elem.previousSibling,
//包含捲軸顯示內容元素
a = prev.tagName.toLowerCase() === 'div' ? prev : prev.previousSibling,
//
n = elem.firstChild,
//捲軸元素
s = this.ie6 ? n.lastChild : n.tagName.toLowerCase() === 'div' ? n : n.nextSibling,
//捲軸高度
height,
//list元素距body的top值
value,
//捲軸距離容器高度
top,
//捲軸距body的top值
sTop,
//捲軸滾動最小值
min,
//捲軸滾動最大值
max,
//每點擊捲軸可到達地區,捲軸向下或向上移動10px
step = 10,
//滑鼠點擊捲軸可到達地區距離最頂端或者最底端小於distance時,捲軸能夠自動移動到最頂端或者最低端
distance = 20;

min = this.limits[s].min
max = this.limits[s].max
height = s.offsetHeight
top = parseInt(s.style.top || 0, 10)
value = squid.getOffset(elem).top
sTop = squid.getOffset(s).top
//滑鼠點擊捲軸下方地區,捲軸向下滾動
if(place > sTop) {
if(value + elem.offsetHeight - place < distance && (elem.offsetHeight - height - top) < distance) {
top = max
}else{
if((sTop + height + step) <= place) {
top += step
}else{
top = place - value - height
}
}
}else{
//滑鼠點擊地區距捲軸頂端小於捲軸長度時,捲軸自動滾動到最頂端
if(place - value < distance && top < distance) {
top = min
}else{
//捲軸距頁面頂部高度減去滑鼠clientY值大於step
if(sTop - place >= step) {
top -= step
}else{
top = place - value
}
}
}
if(top < min) {
top = min
}else if(top > max) {
top = max
}

s.style.top = top + 'px'
this.doScroll(a, top * unit)
}

mouseHandle()方法通過判斷滑鼠點擊位置在頁面中的位置座標,和捲軸元素在頁面中的位置來判斷是點擊了捲軸的上方地區還是下方地區。如果點擊了下方地區則捲軸向下滾動,否則向上滾動,對於點擊的位置在上方地區或者下方地區小於distance值時,捲軸自動滾動到最小值或者最大值。

顯示效果

該控制項的demo使用了淘寶網使用者註冊協議內容,因為firefox、chrome等進階瀏覽器都能保證很好的相容性和顯示效果,所以這裡只展示ie低版本瀏覽器顯示效果, ie瀏覽器顯示截圖如下:

ie6下

初始化之後

滾動過程中

滾動到底部

ie7

捲軸初始化之後

滾動過程中

滾動到最底部

ie9

開始滾動前

滾動過程中

滾動到最底部

總結:基本的功能實現代碼就這麼多了,可能分析的不夠細緻,裡面涉及最多的也許就是位置的計算,事件的綁定處理。如果有什麼問題,歡迎一起溝通、學習、交流。

注意:PIE.htc檔案路徑要放正確,引用時寫成絕對路徑,否則在ie6, 7, 8下沒有css3的效果(如果那樣我代碼裡所做的相容處理就沒啥意義了!),需要改變引用路徑的話可以在jscroll-1.0.css檔案中修改。最後附上源碼,歡迎感興趣者下載試用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.