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属性选择器not has怎么写 行悬停高亮显示
Nov 13 Javascript
JQuery动画与特效实例分析
Feb 02 Javascript
原生JS实现LOADING效果
Mar 16 Javascript
解析预加载显示图片艺术
Dec 05 Javascript
AngularJS双向绑定和依赖反转实例详解
Apr 15 Javascript
vue组件watch属性实例讲解
Nov 07 Javascript
Vue.js@2.6.10更新内置错误处机制Fundebug同步支持相应错误监控
May 13 Javascript
JS实现的字符串数组去重功能小结
Jun 17 Javascript
微信小程序实现上拉加载功能
Nov 20 Javascript
jQuery实现全选、反选和不选功能的方法详解
Dec 04 jQuery
JavaScript 声明私有变量的两种方式
Feb 05 Javascript
解决vue-router的beforeRouteUpdate不能触发
Apr 14 Vue.js
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
实例讲解yii2.0在php命令行中运行的步骤
2015/12/01 PHP
tp5框架的增删改查操作示例
2019/10/31 PHP
PHP之header函数详解
2021/03/02 PHP
javascript 获取表单file全路径
2009/12/31 Javascript
在javascript将NodeList作为Array数组处理的方法
2010/07/09 Javascript
javascript ie6兼容position:fixed实现思路
2013/04/01 Javascript
浅析js中取绝对值的2种方法
2013/07/09 Javascript
JavaScript设置首页和收藏页面的小例子
2013/11/11 Javascript
javascript实现tab切换的两个实例
2015/11/05 Javascript
js生成随机数的过程解析
2015/11/24 Javascript
基于jQuery实现点击最后一行实现行自增效果的表格
2016/01/12 Javascript
AngularJS入门教程之AngularJS 模板
2016/08/18 Javascript
无阻塞加载js,防止因js加载不了影响页面显示的问题
2016/12/18 Javascript
详解vue-cli项目中用json-sever搭建mock服务器
2017/11/02 Javascript
Bootstrap导航菜单点击后无法自动添加active的处理方法
2018/08/10 Javascript
JAVA面试题 static关键字详解
2019/07/16 Javascript
js计算最大公约数和最小公倍数代码实例
2019/09/11 Javascript
基于python代码实现简易滤除数字的方法
2018/07/17 Python
Python高级特性与几种函数的讲解
2019/03/08 Python
Django文件存储 默认存储系统解析
2019/08/02 Python
使用Python的Turtle绘制哆啦A梦实例
2019/11/21 Python
python工具——Mimesis的简单使用教程
2021/01/16 Python
python 利用openpyxl读取Excel表格中指定的行或列教程
2021/02/06 Python
CSS3 选择器 伪类选择器介绍
2012/01/21 HTML / CSS
工业自动化专业毕业生推荐信
2013/11/18 职场文书
自我评价怎么写好呢?
2013/12/05 职场文书
激励口号大全
2014/06/17 职场文书
教师三严三实学习心得体会
2014/10/11 职场文书
2014年内部审计工作总结
2014/12/09 职场文书
承德避暑山庄导游词
2015/02/03 职场文书
无锡灵山大佛导游词
2015/02/09 职场文书
大学开学感言
2015/08/01 职场文书
《比的意义》教学反思
2016/02/18 职场文书
Html分层的box-shadow效果的示例代码
2021/03/30 HTML / CSS
MySQL获取所有分类的前N条记录
2021/05/07 MySQL
pandas中对文本类型数据的处理小结
2021/11/01 Python