javascript jscroll模拟html元素滚动条_javascript技巧

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

初始化之后

滚动过程中

滚动到底部

ie7

滚动条初始化之后

滚动过程中

滚动到最底部

ie9

开始滚动前

滚动过程中

滚动到最底部

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

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

时间: 2024-10-02 13:51:16

javascript jscroll模拟html元素滚动条_javascript技巧的相关文章

javascript实现模拟时钟的方法_javascript技巧

本文实例讲述了javascript实现模拟时钟的方法.分享给大家供大家参考.具体实现方法如下: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>javascript模拟时钟</title&

浅析JavaScript动画模拟拖拽原理_javascript技巧

模拟拖拽的原理: x1等于div.offsetLeft y1等于div.offsetTop x2等于ev.clientX(ev表示event事件) y2等于ev.clientY 当我们在方块上按下鼠标的时候,x2-x1即可确定.移动鼠标之后,我们用鼠标当前的位置即x4.y4减去x2-x1.y2-y1就可以得到方块现在的位置. 代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q

javascript动画系列之模拟滚动条_javascript技巧

前面的话 当元素内容溢出元素尺寸范围时,会出现滚动条.但由于滚动条在各浏览器下表现不同,兼容性不好.所以,模拟滚动条也是很常见的应用.本文将详细介绍滚动条模拟 原理介绍 滚动条模拟实际上和元素模拟拖拽类似.仅仅通过范围限定,使元素只可以在单一方向上拖拽 <div id="box" style="height: 200px;width: 16px;background-color:#F5F5F5;border-radius:10px;box-shadow:inset 0

js实现创建删除html元素小结_javascript技巧

如果我要创建一个div元素. 1.使用DOM对象创建: 使用document.createElement('div')方法创建元素.  2.使用JQuery创建: 使用$('<div>通过JQuery创建的新元素</div>')的方法直接创建元素. 如果需要将id是'div2js'的div元素删除. 1.使用DOM对象 首先需要找到被删元素的父元素,通过父元素将其需要删除的子元素删除. var el = document.getElementById('div2js'); el.p

深入浅析JavaScript中的作用域和上下文_javascript技巧

javascript中的作用域(scope)和上下文(context)是这门语言的独到之处,这部分归功于他们带来的灵活性.每个函数有不同的变量上下文和作用域.这些概念是javascript中一些强大的设计模式的后盾.然而这也给开发人员带来很大困惑.下面全面揭示了javascript中的上下文和作用域的不同,以及各种设计模式如何使用他们. 上下文(Context)和作用域(Scope) 首先需要知道的是,上下文和作用域是两个完全不同的概念.多年来,我发现很多开发者会混淆这两个概念(包括我自己),

JavaScript代码里的判断小结_javascript技巧

比较判断 比较判断是比较两个值,返回一个布尔值,表示是否满足比较条件.JavaScript一共提供了8个比较运算符,这里主要说一下严格相等运算符和相等运算符的区别 严格相等运算符=== 判断 返回 两个值类型不同 false 两个值都是null/undefined/true/false true 两个值其中之一为NaN false 两个值都为数值且值相等 true 两个值都为字符串,且值相等 true 两个值都指向同一个引用类型 true 1 === "1" // false true

浅谈JavaScript 覆盖原型以及更改原型_javascript技巧

覆盖原型 //囚犯示例 //1.定义原型对象 var proto = { sentence : 4, //监禁年限 probation: 2 //缓刑年限 }; //2.定义原型对象的构造函数 var Prisoner = function(name, id) { this.name = name; this.id = id; }; //3.将构造函数关联到原型 Prisoner.prototype = proto; //4.实例化对象--采用工厂函数实例化对象 var makePrisoner

JavaScript中的时间处理小结_javascript技巧

废话不多说了,主要通过以下七个方面给大家总结了时间处理相关知识. 1.获取当前时间 function getNowTime() { return new Date(); } 2.时间与天数相加 function getTimeAddDays(time, days) { return new Date(time.getTime() + days * 24 * 60 * 60 * 1000); } 3.获取并格式化日期:年-月-日 function getFormatDate(time) { ret

浅析在javascript中创建对象的各种模式_javascript技巧

最近在看<javascript高级程序设计>(第二版) javascript中对象的创建 •工厂模式 •构造函数模式 •原型模式 •结合构造函数和原型模式 •原型动态模式 面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象.虽然从技术上讲,javascript是一门面向对象的语言,但是javascript没有类的概念,一切都是对象.任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建:引用类型可以是原生的,也可以是自定义的.原生的引用类型有:Object.A