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 相关文章推荐
[原创]js获取数组任意个不重复的随机数组元素
Mar 15 Javascript
js 实现图片预加载(js操作 Image对象属性complete ,事件onload 异步加载图片)
Mar 25 Javascript
js利用数组length属性清空和截短数组的小例子
Jan 15 Javascript
JS长整型精度问题实例分析
Jan 13 Javascript
vue制作加载更多功能的正确打开方式
Oct 12 Javascript
Angularjs 动态改变title标题(兼容ios)
Dec 29 Javascript
JavaScript基础之AJAX简单的小demo
Jan 29 Javascript
jQuery模拟下拉框选择对应菜单的内容
Mar 07 Javascript
源码分析Vue.js的监听实现教程
Apr 23 Javascript
一文让你彻底搞清楚javascript中的require、import与export
Sep 24 Javascript
bootstrap table.js动态填充单元格数据的多种方法
Jul 18 Javascript
微信小程序仿通讯录功能
Apr 09 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中mysql与mysqli的区别分析
2013/06/10 PHP
PHP 设计模式系列之 specification规格模式
2016/01/10 PHP
php array_reverse 以相反的顺序返回数组实例代码
2017/04/11 PHP
laravel 5.1下php artisan migrate的使用注意事项总结
2017/06/07 PHP
PHP从尾到头打印链表实例讲解
2018/09/27 PHP
PHP实现的只保留字符串首尾字符功能示例【隐藏部分字符串】
2019/03/11 PHP
jQuery 研究心得 取得属性的值
2007/11/30 Javascript
javascript学习笔记(十七) 检测浏览器插件代码
2012/06/20 Javascript
IE8提示Invalid procedure call or argument 异常的解决方法
2012/09/30 Javascript
使用Java实现简单的server/client回显功能的方法介绍
2013/05/03 Javascript
js如何获取object类型里的键值
2014/02/18 Javascript
jquery日历控件实现方法分享
2014/03/07 Javascript
jquery操作select元素和option的实例代码
2016/02/03 Javascript
JS DOM实现鼠标滑动图片效果
2020/09/17 Javascript
Bootstrap3 input输入框插入glyphicon图标的方法
2016/05/16 Javascript
浅谈jquery中的each方法$.each、this.each、$.fn.each
2016/06/23 Javascript
jQuery制作图片旋转效果
2017/02/02 Javascript
layer.open关闭父窗口 以及调用父页面的方法
2018/08/17 Javascript
JS实现使用POST方式发送请求
2019/08/30 Javascript
Django日志模块logging的配置详解
2017/02/14 Python
使用python实现语音文件的特征提取方法
2019/01/09 Python
Python设计模式之代理模式实例详解
2019/01/19 Python
python爬虫解决验证码的思路及示例
2019/08/01 Python
Python 3.6打包成EXE可执行程序的实现
2019/10/18 Python
Django框架静态文件处理、中间件、上传文件操作实例详解
2020/02/29 Python
Django 实现对已存在的model进行更改
2020/03/28 Python
django form和field具体方法和属性说明
2020/07/09 Python
Python类型转换的魔术方法详解
2020/12/23 Python
详解css3自定义滚动条样式写法
2017/12/25 HTML / CSS
SEPHORA新西兰官方网站:购买化妆品和护肤品
2016/12/02 全球购物
MADE法国:提供原创设计师家具
2018/09/18 全球购物
彪马日本官网:PUMA日本
2019/01/31 全球购物
会计师职业生涯规划范文
2014/02/18 职场文书
环保倡议书格式范文
2014/05/14 职场文书
建设工地安全标语
2014/06/07 职场文书
公司财务管理制度
2015/08/04 职场文书