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示例收集
Nov 05 Javascript
jquery 设置元素相对于另一个元素的top值(实例代码)
Nov 06 Javascript
关于jQuery中的each方法(jQuery到底干了什么)
Mar 05 Javascript
jQuery基础知识小结
Dec 22 Javascript
浅谈重写window对象的方法
Dec 29 Javascript
JS 对象属性相关(检查属性、枚举属性等)
Apr 05 Javascript
Document.body.scrollTop的值总为零的快速解决办法
Jun 09 Javascript
Bootstrap 下拉多选框插件Bootstrap Multiselect
Jan 22 Javascript
JavaScript中的事件与异常捕获详析
Feb 24 Javascript
史上最为详细的javascript继承(推荐)
May 18 Javascript
JavaScript 自定义html元素鼠标右键菜单功能
Dec 02 Javascript
基于vue3.0.1beta搭建仿京东的电商H5项目
May 06 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下批量挂马和批量清马代码
2011/02/27 PHP
PHP验证码函数代码(简单实用)
2013/09/29 PHP
PHP中一些可以替代正则表达式函数的字符串操作函数
2014/11/17 PHP
php实现微信公众号企业转账功能
2018/10/01 PHP
Jquery原生态实现表格header头随滚动条滚动而滚动
2014/03/18 Javascript
html文本框提示效果的示例代码
2014/06/28 Javascript
jquery实现带缩略图的可定制高度画廊效果(5种)
2015/08/28 Javascript
JavaScript识别网页关键字并进行描红的方法
2015/11/09 Javascript
jQuery实现的网页换肤效果示例
2016/09/20 Javascript
滚动条的监听与内容随着滚动条动态加载的实现
2017/02/08 Javascript
原生JS+Canvas实现五子棋游戏
2020/05/28 Javascript
[02:10]三分钟回顾完美世界城市挑战赛
2019/01/24 DOTA
Python中3种内建数据结构:列表、元组和字典
2014/11/30 Python
利用Python生成文件md5校验值函数的方法
2017/01/10 Python
python学习之matplotlib绘制散点图实例
2017/12/09 Python
python生成随机红包的实例写法
2019/09/02 Python
Python3如何对urllib和urllib2进行重构
2019/11/25 Python
python利用datetime模块计算程序运行时间问题
2020/02/20 Python
Python实现爬取网页中动态加载的数据
2020/08/17 Python
Python之京东商品秒杀的实现示例
2021/01/06 Python
我们是伦敦女孩:WalG
2018/01/08 全球购物
护士检查书
2014/01/17 职场文书
大学活动邀请函
2014/01/28 职场文书
学生干部培训方案
2014/06/12 职场文书
体育馆的标语
2014/06/24 职场文书
食堂厨师岗位职责
2014/08/25 职场文书
综治维稳工作承诺书
2014/08/30 职场文书
领导班子作风建设年个人整改措施
2014/09/29 职场文书
2015年班级元旦晚会活动总结
2014/11/28 职场文书
董事长秘书岗位职责
2015/02/13 职场文书
2019年最新版见习人员管理制度!
2019/07/08 职场文书
golang 接口嵌套实现复用的操作
2021/04/29 Golang
Python数据可视化之绘制柱状图和条形图
2021/05/25 Python
新手初学Java网络编程
2021/07/07 Java/Android
MySQL连接控制插件介绍
2021/09/25 MySQL
win10怎么设置右下角图标不折叠?Win10设置右下角图标不折叠的方法
2022/07/15 数码科技