javascript jscroll模擬html元素滾動(dòng)條
主流瀏覽器默認(rèn)為html元素提供的滾動(dòng)條不美觀,而且前端開發(fā)人員想對(duì)其通過css進(jìn)行統(tǒng)一樣式的美化也是不可實(shí)現(xiàn)的。比如ie可以通過樣式來實(shí)現(xiàn)簡(jiǎn)單的美化、Webkit內(nèi)核瀏覽器可以控制滾動(dòng)條的顯示效果,firefox則不允許用戶為滾動(dòng)條定義樣式。但是對(duì)于追求友好的用戶體驗(yàn)的前端開發(fā)人員,是不會(huì)被這些瀏覽器的不一致行為所阻止的。我們可以自己通過標(biāo)準(zhǔn)的html元素模擬來實(shí)現(xiàn)自定義的滾動(dòng)條。
這里是自己在工作不太忙的時(shí)候?qū)懗鰜砹艘粋€(gè)用戶可以自定義的滾動(dòng)條jscroll,以下簡(jiǎn)稱jscroll。jscroll默認(rèn)只提供一種滾動(dòng)條樣式,部分樣式來自google webstore ,其中有部分css3樣式主要用于實(shí)現(xiàn)圓角,陰影效果。為實(shí)現(xiàn)跨瀏覽器情況下滾動(dòng)條顯示效果的一致,對(duì)于ie6, 7, 8不支持css3的瀏覽器引入了 PIE.htc 文件。下面就實(shí)現(xiàn)的功能以及兼容性、使用方法、具體代碼實(shí)現(xiàn)分別做一下講解。
實(shí)現(xiàn)功能以及兼容性
jscroll實(shí)現(xiàn)了系統(tǒng)默認(rèn)滾動(dòng)條的幾乎所有功能,比如:拖動(dòng)滾動(dòng)條查看內(nèi)容、滾動(dòng)鼠標(biāo)滾輪查看內(nèi)容、點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域的上方或者下方來觸發(fā)滾動(dòng)條的滾動(dòng)、鍵盤上下鍵來觸發(fā)滾動(dòng)條的滾動(dòng)。firefox、chrome,、ie9+ 等最新瀏覽器支持css3以及js的最新API,所以沒有任何兼容性問題。ie6, 7, 8 不支持css3通過引入PIE.htc 的hack文件來做兼容處理。js方面對(duì)于不支持的API通過舊的API來做了兼容。有最大兼容性問題的瀏覽器是ie6,不支持點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域來觸發(fā)滾動(dòng)條滾動(dòng),也不支持鍵盤上下鍵來觸發(fā)滾動(dòng)條的滾動(dòng)。導(dǎo)致這個(gè)問題的原因主要是因?yàn)橐肓酥С謈ss3的PIE.htc文件,如果不引入該hack文件,所有操作都能支持,沒法辦為了顯示效果的一致,只好選擇了不支持部分功能。
使用方法
使用自定義滾動(dòng)條最多的情況應(yīng)該是頁面彈出層,或者是頁面上某一個(gè)區(qū)域,千萬不要對(duì)整個(gè)頁面的滾動(dòng)條進(jìn)行自定義操作。對(duì)于需要使用jscroll的元素,需要添加自定義屬性data-scroll="true"來告訴程序需要使用jscroll來替換系統(tǒng)默認(rèn)的滾動(dòng)條,同時(shí)還需要通過添加自定義屬性data-width=""、data-height=""來指定元素要顯示的寬度和高度。jscroll會(huì)根據(jù)用戶定義的寬度和高度計(jì)算內(nèi)容的顯示寬度以及滾動(dòng)條顯示的高度并添加交互的事件。
具體代碼實(shí)現(xiàn)
jscroll的實(shí)現(xiàn)邏輯并不復(fù)雜,實(shí)現(xiàn)具體功能的js代碼不到400行,但是這里依賴了一些基礎(chǔ)的方法,所以需要引入squid.js作為基礎(chǔ)方法的支持。對(duì)滾動(dòng)條樣式的控制的css在一個(gè)單獨(dú)的jscroll-1.0.css文件里面,用戶可以自己修改擴(kuò)展以滿足自己的需求。下面是對(duì)實(shí)現(xiàn)具體功能的每個(gè)方法做一個(gè)簡(jiǎn)單的分析:
init: function(selector, context) {
selecotr = selector || 'data-scroll'
context = context || document
var elems = squid.getElementsByAttribute(selector, context)
this.initView(elems)
}
init()是初始化函數(shù),根據(jù)指定selector和context獲取需要使用自定義滾動(dòng)條的元素,selector默認(rèn)是data-scroll,上下文默認(rèn)是當(dāng)前document。這里無論元素自定義屬性data-scroll="true"或者data-scroll="false"都會(huì)使用自定滾動(dòng)條覆蓋系統(tǒng)默認(rèn)滾動(dòng)條,squid的getElementsByAttribute()方法只是提供通過元素是否有指定屬性來查找元素而忽略屬性值,這個(gè)方法沒有jquery選擇器或者高級(jí)瀏覽器提供的querySelectorAll()方法強(qiáng)大,因?yàn)檫@里squid只是做最基本的支持。找到需要自定義滾動(dòng)條的元素之后調(diào)用initView方法來初始化滾動(dòng)條整體結(jié)構(gòu)和顯示。
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()方法會(huì)首先對(duì)頁面上獲取的帶有自定義屬性data-scroll的元素遍歷,判斷每一個(gè)元素是否會(huì)出現(xiàn)滾動(dòng)條,通過hasScroll()方法判斷。如果元素會(huì)出現(xiàn)滾動(dòng)條則調(diào)用create()方法分別創(chuàng)建自定義的滾動(dòng)條。initView()方法結(jié)束會(huì)調(diào)用initEvent()方法。
//是否有滾動(dòng)條
hasScroll: function(elem) {
return elem.offsetHeight < elem.scrollHeight
}
hasScroll()方法用于判斷元素是否會(huì)出現(xiàn)滾動(dòng)條,返回true或者false。這里忽略元素的margin和padding,通過jscroll創(chuàng)建的滾動(dòng)條默認(rèn)margin和padding都是0。
//創(chuàng)建滾動(dòng)條元素整體結(jié)構(gòu)
create: function(elem) {
var wrapper,
list,
//滾動(dòng)條元素
s,
//帶滾動(dòng)條元素顯示的高度
height = elem['data-height'] || elem.getAttribute('data-height'),
//帶滾動(dòng)條元素顯示的寬度
width = elem['data-width'] || elem.getAttribute('data-width'),
//滾動(dòng)條顯示高度
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'
})
//滾動(dòng)條
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)
//把需要出現(xiàn)滾動(dòng)條的元素包裹起來
elem.parentNode.replaceChild(wrapper, elem)
wrapper.insertBefore(elem, list)
//滾動(dòng)條高度
value = this.scrollbarHeight(elem, height)
squid.css(s, {
height: value + 'px'
})
//add event
this.regEvent(wrapper)
}
create()方法用戶調(diào)整創(chuàng)建帶有自定義滾動(dòng)條的元素整體結(jié)構(gòu),首先通過創(chuàng)建了wrapper元素,用于包裝會(huì)出現(xiàn)滾動(dòng)條的元素elem和滾動(dòng)條可滾動(dòng)的區(qū)域元素list以及滾動(dòng)條元素s。通過從出現(xiàn)滾動(dòng)條元素設(shè)置的自定義屬性data-width、data-height分別設(shè)置wrapper元素的寬度和高度。通過scrollbarHeight()方法計(jì)算得到了滾動(dòng)條元素顯示的高度,整體結(jié)構(gòu)不算復(fù)雜。創(chuàng)建自定義滾動(dòng)條整體結(jié)構(gòu)之后是為滾動(dòng)條元素s和滾動(dòng)條可到達(dá)區(qū)域元素list添加事件處理,通過regEvent()方法實(shí)現(xiàn)。
//計(jì)算滾動(dòng)條的高度
scrollbarHeight: function(elem, height) {
var value = elem.scrollHeight;
return (height / value) * height
}
scrollbarHeight()方法通過簡(jiǎn)單的數(shù)學(xué)計(jì)算返回滾動(dòng)條元素應(yīng)該顯示的高度。
initEvent: function() {
var that = this,
_default,
elem,
top,
min,
max,
prev,
parent,
sbody,
unit;
//滾動(dòng)條滾動(dòng)
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)
}
})
//滾動(dòng)結(jié)束
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()方法實(shí)現(xiàn)了為document元素添加mousemove和mouseup事件,mousemove實(shí)現(xiàn)了在拖動(dòng)滾動(dòng)條元素滾動(dòng)時(shí)查看的內(nèi)容跟隨變化。代碼首先判斷當(dāng)前是否有拖動(dòng)滾動(dòng)條查看內(nèi)容的操作,如果有就計(jì)算滾動(dòng)條被拖動(dòng)到的位置,然后計(jì)算查看內(nèi)容應(yīng)該到的地方。代碼里對(duì)ie6的判斷,是因?yàn)橐氲腜IE.htc文件破壞了原有的結(jié)構(gòu)(為了實(shí)現(xiàn)跨瀏覽器下顯示效果的一致,付出太大了!?。。?。mouseup事件處理程序?qū)崿F(xiàn)了清除上次操作的滾動(dòng)條元素。
//添加滾動(dòng)條事件
regEvent: function(elem) {
var that = this,
sbody = elem.firstChild,
list = sbody.nextSibling,
//滾動(dòng)條元素
s = list.firstChild,
//滾動(dòng)條滾動(dòng)最小值
min = 0,
//滾動(dòng)條滾動(dòng)最大值
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,
//鼠標(biāo)滾輪事件
mousewheel = firefox ? 'DOMMouseScroll' : 'mousewheel',
//opera瀏覽器
opera = window.oprea && navigator.userAgent.indexOf('MSIE') === -1,
//is firing mousedown event
firing = false,
//鼠標(biāo)點(diǎn)擊,定時(shí)器執(zhí)行時(shí)間
interval,
//滾動(dòng)條距離容器高度
top,
//滾動(dòng)條當(dāng)前top值
cur,
//每次滾動(dòng)多少像素
speed = 18;
//變量緩存min, max
this.limits[s] = {
min: 0,
max: max
}
//scroll事件 鼠標(biāo)滑動(dòng)滾輪移動(dòng)滾動(dòng)條
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)
//向上滾動(dòng)
if(delta > 0) {
top = cur - speed
if(top < min) {
top = min
}
}else{//向下滾動(dòng)
top = cur + speed
if(top > max) {
top = max
}
}
s.style.top = top + 'px'
that.doScroll(sbody, top * unit)
//阻止body元素滾動(dòng)條滾動(dòng)
event.preventDefault()
})
//ie6, 7, 8下,如果鼠標(biāo)連續(xù)點(diǎn)擊兩次且時(shí)間間隔太短,則第二次事件不會(huì)觸發(fā)
//拖動(dòng)滾動(dòng)條,點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域
squid.on(list, 'mousedown', function(event) {
var target = event.target,
y = event.clientY;
target = event.target
if(target.tagName.toLowerCase() === 'shape')
target = s
//鼠標(biāo)點(diǎn)擊元素是滾動(dòng)條
if(target === s) {
//invoke elem setCapture
s.setCapture && s.setCapture()
that.scrolling.diffy = y - s.offsetTop
//鼠標(biāo)移動(dòng)過程中判斷是否正在拖動(dòng)滾動(dòng)條
that.scrolling.elem = s
}else if(target.className.match('jscroll-list')){
firing = true
interval = setInterval(function() {
if(firing) {
that.mouseHandle(list, y, unit)
}
}, 80)
}
})
//鼠標(biāo)松開滾動(dòng)條停止?jié)L動(dòng)
squid.on(list, 'mouseup', function() {
//invoke elem releaseCapture
s.releaseCapture && s.releaseCapture()
firing = false
clearInterval(interval)
})
//滾動(dòng)條元素獲取焦點(diǎn),可以觸發(fā)keyup事件
squid.on(s, 'click', function() {
this.focus()
})
//滾動(dòng)條獲取焦點(diǎn)后,觸發(fā)鍵盤上下鍵,滾動(dòng)條滾動(dòng)
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()方法實(shí)現(xiàn)了以下功能,應(yīng)該是jscroll組件的核心方法了:
1. 鼠標(biāo)在包含滾動(dòng)條的元素區(qū)域,上下滾動(dòng)鼠標(biāo)滾輪,查看的內(nèi)容跟隨滾輪上下翻的功能
2. 點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域,即滾動(dòng)條上方或者下方,滾動(dòng)條和查看的內(nèi)容向上或者向下滾動(dòng)。鼠標(biāo)點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域不松開,可連續(xù)滾動(dòng)滾動(dòng)條和查看的內(nèi)容,通過調(diào)用mouseHandle()方法來具體實(shí)現(xiàn)該功能。
3. 點(diǎn)擊滾動(dòng)條元素后,可以通過鍵盤上下鍵來觸發(fā)滾動(dòng)條和查看內(nèi)容的滾動(dòng)
//鼠標(biāo)點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域上面或者下面時(shí),滾動(dòng)條滾動(dòng)
mouseHandle: function(elem, place, unit) {
var prev = elem.previousSibling,
//包含滾動(dòng)條顯示內(nèi)容元素
a = prev.tagName.toLowerCase() === 'div' ? prev : prev.previousSibling,
//
n = elem.firstChild,
//滾動(dòng)條元素
s = this.ie6 ? n.lastChild : n.tagName.toLowerCase() === 'div' ? n : n.nextSibling,
//滾動(dòng)條高度
height,
//list元素距body的top值
value,
//滾動(dòng)條距離容器高度
top,
//滾動(dòng)條距body的top值
sTop,
//滾動(dòng)條滾動(dòng)最小值
min,
//滾動(dòng)條滾動(dòng)最大值
max,
//每點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域,滾動(dòng)條向下或向上移動(dòng)10px
step = 10,
//鼠標(biāo)點(diǎn)擊滾動(dòng)條可到達(dá)區(qū)域距離最頂端或者最底端小于distance時(shí),滾動(dòng)條能夠自動(dòng)移動(dòng)到最頂端或者最低端
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
//鼠標(biāo)點(diǎn)擊滾動(dòng)條下方區(qū)域,滾動(dòng)條向下滾動(dòng)
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{
//鼠標(biāo)點(diǎn)擊區(qū)域距滾動(dòng)條頂端小于滾動(dòng)條長(zhǎng)度時(shí),滾動(dòng)條自動(dòng)滾動(dòng)到最頂端
if(place - value < distance && top < distance) {
top = min
}else{
//滾動(dòng)條距頁面頂部高度減去鼠標(biāo)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()方法通過判斷鼠標(biāo)點(diǎn)擊位置在頁面中的位置坐標(biāo),和滾動(dòng)條元素在頁面中的位置來判斷是點(diǎn)擊了滾動(dòng)條的上方區(qū)域還是下方區(qū)域。如果點(diǎn)擊了下方區(qū)域則滾動(dòng)條向下滾動(dòng),否則向上滾動(dòng),對(duì)于點(diǎn)擊的位置在上方區(qū)域或者下方區(qū)域小于distance值時(shí),滾動(dòng)條自動(dòng)滾動(dòng)到最小值或者最大值。
顯示效果
該控件的demo使用了淘寶網(wǎng)用戶注冊(cè)協(xié)議內(nèi)容,因?yàn)閒irefox、chrome等高級(jí)瀏覽器都能保證很好的兼容性和顯示效果,所以這里只展示ie低版本瀏覽器顯示效果, ie瀏覽器顯示截圖如下:
ie6下
初始化之后
滾動(dòng)過程中
滾動(dòng)到底部
ie7
滾動(dòng)條初始化之后
滾動(dòng)過程中
滾動(dòng)到最底部
ie9
開始滾動(dòng)前
滾動(dòng)過程中
滾動(dòng)到最底部
總結(jié):基本的功能實(shí)現(xiàn)代碼就這么多了,可能分析的不夠細(xì)致,里面涉及最多的也許就是位置的計(jì)算,事件的綁定處理。如果有什么問題,歡迎一起溝通、學(xué)習(xí)、交流。
注意:PIE.htc文件路徑要放正確,引用時(shí)寫成絕對(duì)路徑,否則在ie6, 7, 8下沒有css3的效果(如果那樣我代碼里所做的兼容處理就沒啥意義了?。?,需要改變引用路徑的話可以在jscroll-1.0.css文件中修改。最后附上源碼,歡迎感興趣者下載試用。
- Js 獲取HTML DOM節(jié)點(diǎn)元素的方法小結(jié)
- js 動(dòng)態(tài)創(chuàng)建 html元素
- javascript 控制 html元素 顯示/隱藏實(shí)現(xiàn)代碼
- Javascript createElement和innerHTML增加頁面元素的性能對(duì)比
- javascript 隱藏/顯示指定的區(qū)域附HTML元素【legend】用法
- JavaScript改變HTML元素的樣式改變CSS及元素屬性
- JavaScript動(dòng)態(tài)改變HTML頁面元素例如添加或刪除
- 通過JS動(dòng)態(tài)創(chuàng)建一個(gè)html DOM元素并顯示
- javascript刪除一個(gè)html元素節(jié)點(diǎn)的方法
- JavaScript中獲取HTML元素值的三種方法
相關(guān)文章
javascript常用經(jīng)典算法實(shí)例詳解
這篇文章主要介紹了javascript常用算法,結(jié)合實(shí)例形式較為詳細(xì)的分析總結(jié)了JavaScript中常見的各種排序算法以及堆、棧、鏈表等數(shù)據(jù)結(jié)構(gòu)的相關(guān)實(shí)現(xiàn)與使用技巧,需要的朋友可以參考下2015-11-11JavaScript日期時(shí)間與時(shí)間戳的轉(zhuǎn)換函數(shù)分享
這篇文章主要介紹了JavaScript日期時(shí)間與時(shí)間戳的轉(zhuǎn)換函數(shù)分享,本文給出兩個(gè)函數(shù)實(shí)現(xiàn)日期時(shí)間和時(shí)間戳間的轉(zhuǎn)換,需要的朋友可以參考下2015-01-01js獲取客戶端外網(wǎng)ip的簡(jiǎn)單實(shí)例
這篇文章主要介紹了js獲取客戶端外網(wǎng)ip的簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下2013-11-11firefox事件處理之自動(dòng)查找event的函數(shù)(用于onclick=foo())
在ie中,事件對(duì)象是作為一個(gè)全局變量來保存和維護(hù)的。 所有的瀏覽器事件,不管是用戶觸發(fā)的,還是其他事件, 都會(huì)更新window.event 對(duì)象。2010-08-08詳解JS截取字符串的三個(gè)方法substring,substr,slice
js中有三個(gè)截取字符的方法,分別是substring()、substr()、slice(),平時(shí)我們可能都用到過,但總是會(huì)對(duì)這些方法有點(diǎn)混淆。本文將詳細(xì)介紹一下這三者的區(qū)別,需要的可以參考一下2022-03-03bootstrap-table后端分頁功能完整實(shí)例
這篇文章主要介紹了bootstrap-table后端分頁功能,結(jié)合完整實(shí)例形式分析了bootstrap-table后端請(qǐng)求、數(shù)據(jù)分頁功能具體步驟與實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-06-06