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 相关文章推荐
javascript与cookie 的问题详解
Nov 11 Javascript
jquery提交form表单简单示例分享
Mar 03 Javascript
jquery获取radio值(单选组radio)
Oct 16 Javascript
jQuery prototype冲突的2种解决方法(附demo示例下载)
Jan 21 Javascript
浅析JS动态创建元素【两种方法】
Apr 20 Javascript
js遍历json的key和value的实例
Jan 22 Javascript
基于JS实现翻书效果的页面切换样式
Feb 16 Javascript
更改BootStrap popover的默认样式及popover简单用法
Sep 13 Javascript
Vue中的$set的使用实例代码
Oct 08 Javascript
浅谈Node框架接入ELK实践总结
Feb 22 Javascript
jQuery 常用特效实例小结【显示与隐藏、淡入淡出、滑动、动画等】
May 19 jQuery
vue-cil之axios的二次封装与proxy反向代理使用说明
Apr 07 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
php中mkdir()函数的权限问题分析
2016/09/24 PHP
PHP通过引用传递参数用法分析
2016/12/01 PHP
PHP PDOStatement::getAttribute讲解
2019/02/01 PHP
laravel 使用auth编写登录的方法
2019/09/30 PHP
window.onload 加载完毕的问题及解决方案(上)
2009/07/09 Javascript
javascript 兼容所有浏览器的DOM扩展功能
2012/08/01 Javascript
jQuery lazyLoad图片延迟加载插件的优化改造方法分享
2013/08/13 Javascript
JavaScript按位运算符的应用简析
2014/02/04 Javascript
javascript cookie用法基础教程(概念,设置,读取及删除)
2016/09/20 Javascript
BootStrap下拉菜单和滚动监听插件实现代码
2016/09/26 Javascript
关于Iframe父页面与子页面之间的相互调用
2016/11/22 Javascript
nodejs之get/post请求的几种方式小结
2017/07/26 NodeJs
Angular实现下载安装包的功能代码分享
2017/09/05 Javascript
Mac下安装vue
2018/04/11 Javascript
JS实现的tab切换并显示相应内容模块功能示例
2019/08/03 Javascript
微信小程序实现定位及到指定位置导航的示例代码
2019/08/20 Javascript
微信小程序canvas开发水果老虎机的思路详解
2020/02/07 Javascript
Python中用PIL库批量给图片加上序号的教程
2015/05/06 Python
Python玩转Excel的读写改实例
2019/02/22 Python
Django利用cookie保存用户登录信息的简单实现方法
2019/05/27 Python
Python datetime包函数简单介绍
2019/08/28 Python
基于python解线性矩阵方程(numpy中的matrix类)
2019/10/21 Python
Python Selenium安装及环境配置的实现
2020/03/17 Python
详解pandas赋值失败问题解决
2020/11/29 Python
利用CSS3的3D效果制作正方体
2020/03/10 HTML / CSS
HTML5 对各个标签的定义与规定:body的介绍
2012/06/21 HTML / CSS
canvas实现圆形进度条动画的示例代码
2017/12/26 HTML / CSS
美国酒店控股公司:Choice Hotels
2018/06/15 全球购物
什么是唯一索引
2015/07/05 面试题
升学宴答谢词
2015/01/05 职场文书
我的1919观后感
2015/06/03 职场文书
暑假打工感想
2015/08/07 职场文书
创业计划书之校园超市
2019/09/12 职场文书
python使用glob检索文件的操作
2021/05/20 Python
PHP实现两种排课方式
2021/06/26 PHP
css中有哪些方式可以隐藏页面元素及区别
2022/06/16 HTML / CSS