javascript jscroll模拟html元素滚动条


Posted in Javascript onDecember 18, 2012

主流浏览器默认为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下

javascript jscroll模拟html元素滚动条

初始化之后

javascript jscroll模拟html元素滚动条

滚动过程中

javascript jscroll模拟html元素滚动条

滚动到底部

ie7

javascript jscroll模拟html元素滚动条

滚动条初始化之后

javascript jscroll模拟html元素滚动条

滚动过程中

javascript jscroll模拟html元素滚动条

滚动到最底部

ie9

javascript jscroll模拟html元素滚动条

开始滚动前

javascript jscroll模拟html元素滚动条

滚动过程中

javascript jscroll模拟html元素滚动条

滚动到最底部

总结:基本的功能实现代码就这么多了,可能分析的不够细致,里面涉及最多的也许就是位置的计算,事件的绑定处理。如果有什么问题,欢迎一起沟通、学习、交流。

注意:PIE.htc文件路径要放正确,引用时写成绝对路径,否则在ie6, 7, 8下没有css3的效果(如果那样我代码里所做的兼容处理就没啥意义了!),需要改变引用路径的话可以在jscroll-1.0.css文件中修改。最后附上源码,欢迎感兴趣者下载试用。

Javascript 相关文章推荐
基于jquery的页面划词搜索JS
Sep 14 Javascript
缓动函数requestAnimationFrame 更好的实现浏览器经动画
Dec 07 Javascript
js判断IE浏览器版本过低示例代码
Nov 22 Javascript
jQuery 中$(this).index与$.each的使用指南
Nov 20 Javascript
一步步教大家编写酷炫的导航栏js+css实现
Mar 14 Javascript
JS模态窗口返回值兼容问题的完美解决方法
May 28 Javascript
微信小程序图片选择、上传到服务器、预览(PHP)实现实例
May 11 Javascript
javascript ES6 新增了let命令使用介绍
Jul 07 Javascript
jQuery发请求传输中文参数乱码问题的解决方案
May 22 jQuery
JS学习笔记之贪吃蛇小游戏demo实例详解
May 29 Javascript
解决nuxt 自定义全局方法,全局属性,全局变量的问题
Nov 05 Javascript
JS相册图片抖动放大展示效果的示例代码
Jan 29 Javascript
Android中资源文件(非代码部分)的使用概览
Dec 18 #Javascript
js获取单选框或复选框值及操作
Dec 18 #Javascript
JQuery触发radio或checkbox的change事件
Dec 18 #Javascript
Jquery为单选框checkbox绑定单击click事件
Dec 18 #Javascript
jQuery实现form表单reset按钮重置清空表单功能
Dec 18 #Javascript
js/jquery获取浏览器窗口可视区域高度和宽度以及滚动条高度实现代码
Dec 17 #Javascript
jQuery获取样式中的背景颜色属性值/颜色值
Dec 17 #Javascript
You might like
php数组相加 array(“a”)+array(“b”)结果还是array(“a”)
2012/09/19 PHP
PHP实现发送邮件的方法(基于简单邮件发送类)
2015/12/17 PHP
跨浏览器的设置innerHTML方法
2006/09/18 Javascript
摘自启点的main.js
2008/04/20 Javascript
jquery仿百度经验滑动切换浏览效果
2015/04/14 Javascript
jQuery中队列queue()函数的实例教程
2016/05/03 Javascript
AngularJS入门教程之ng-class 指令用法
2016/08/01 Javascript
JS实现中国公民身份证号码有效性验证
2017/02/20 Javascript
jquery自定义显示消息数量
2017/12/19 jQuery
原生JS控制多个滚动条同步跟随滚动效果
2017/12/22 Javascript
Vue.js更改调试地址端口号的实例
2018/09/19 Javascript
浅谈angular2子组件的事件传递(任意组件事件传递)
2018/09/30 Javascript
JS添加或删除HTML dom元素的方法实例分析
2019/03/05 Javascript
Vue keepAlive 数据缓存工具实现返回上一个页面浏览的位置
2019/05/10 Javascript
JavaScript中0、空字符串、'0'是true还是false的知识点分享
2019/09/16 Javascript
[03:52]显微镜下的DOTA2第三期——英雄在无聊的时候干什么
2014/06/20 DOTA
python连接MySQL、MongoDB、Redis、memcache等数据库的方法
2013/11/15 Python
使用python实现正则匹配检索远端FTP目录下的文件
2015/03/25 Python
Python使用multiprocessing实现一个最简单的分布式作业调度系统
2016/03/14 Python
Python中实现变量赋值传递时的引用和拷贝方法
2018/04/29 Python
python实现超市扫码仪计费
2018/05/30 Python
python判断设备是否联网的方法
2018/06/29 Python
python读取txt文件并取其某一列数据的示例
2019/02/19 Python
python中的句柄操作的方法示例
2019/06/20 Python
用Python将Excel数据导入到SQL Server的例子
2019/08/24 Python
Django认证系统user对象实现过程解析
2020/03/02 Python
python+requests接口压力测试500次,查看响应时间的实例
2020/04/30 Python
Python Dataframe常见索引方式详解
2020/05/27 Python
Python解析微信dat文件的方法
2020/11/30 Python
ESDlife健康生活易:身体检查预订、搜寻及比较
2019/05/10 全球购物
skyn ICELAND官网:冰岛成分天然护肤品
2020/08/24 全球购物
抵押贷款承诺书
2014/05/30 职场文书
单位一把手群众路线四风问题整改措施
2014/09/25 职场文书
班子成员四风问题自我剖析材料
2014/09/29 职场文书
地心历险记观后感
2015/06/15 职场文书
Python 内置函数速查表一览
2021/06/02 Python