从源码角度深入理解iScroll中的scrollbars和indicators配置
问题1:在IScroll中都是使用同样的方法对scrollbars和indicators进行初始化
if ( this.options.scrollbars || this.options.indicators ) {this._initIndicators();}
如果配置了scrollbars和indicators都是调用_initIndicators方法来完成的
问题2:scrollX和scrollY表示的是什么?
this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY;this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX;
eventPassthrough表示忽略哪一个方向上的滚动,如果为vertical那么表示忽略垂直方向的滚动,这时候this.options.scrollY就是false!
问题3:如何创建滚动条
创建滚动条和滚动槽是通过下面的方法来完成的:
function createDefaultScrollbar (direction, interactive, type) {var scrollbar = document.createElement('div'),indicator = document.createElement('div');//如果含有滚动条,那么我们给滚动条设置absolute定位if ( type === true ) {scrollbar.style.cssText = 'position:absolute;z-index:9999';indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px';}//indicator含有className为iScrollIndicatorindicator.className = 'iScrollIndicator';//如果方向是水平的滚动条同时也有滚动条if ( direction == 'h' ) {if ( type === true ) {scrollbar.style.cssText += ';height:7px;left:2px;right:2px;bottom:0';indicator.style.height = '100%';}scrollbar.className = 'iScrollHorizontalScrollbar';} else {//如果是垂直方向的滚动条if ( type === true ) {scrollbar.style.cssText += ';width:7px;bottom:2px;top:2px;right:1px';indicator.style.width = '100%';}scrollbar.className = 'iScrollVerticalScrollbar';}scrollbar.style.cssText += ';overflow:hidden';//如果interactive为false表示不允许响应事件,那么为scrollbar元素的style添加pointerEvents为"none"就可以了,默认是""空字符串if ( !interactive ) {scrollbar.style.pointerEvents = 'none';}//scrollbar添加子元素为indicator元素scrollbar.appendChild(indicator);return scrollbar;
}
注意:其中scrollbar表示的是滚动槽,而我们的indicator表示的是滚动条,这一点要理解。直接调用这个函数就会看到效果(滚动条的高度要设置,否则默认为0)。到了这一步,滚动条就创建好了,同时append到wrapper后面就完成了。下面就会如何让滚动条在滚动槽中移动。
问题4:到底什么是indicators?
解答:滚动条(非滚动槽);自定义指示元素
看个demo源码:
<div id="viewport"><div id="wrapper"><div id="scroller"><!--scroller中的元素才是我们可以看到的元素,wrapper定宽,而scroller不定宽--><div class="slide"><div class="painting giotto"></div></div><div class="slide"><div class="painting leonardo"></div></div><div class="slide"><div class="painting gaugin"></div></div><div class="slide"><div class="painting warhol"></div></div></div></div>
</div><div id="indicator"><div id="dotty"></div>
</div>
然后我们这样使用iScroll组件:
var myScroll;
function loaded () {myScroll = new IScroll('#wrapper', {scrollX: true,scrollY: false,momentum: false,snap: true,snapSpeed: 400,keyBindings: true,//可以通过indicators来指定自己的Indicator,而滚动条也有自己的Indicator。iScroll会把两者结合起来然后逐个//创建Indicator元素indicators: {el: document.getElementById('indicator'),resize: false}});
}
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
我们看看最后生成的DOM结构:
我们看看Indicator的构造函数主要做了什么:
//注意:这里创建Indicator是基于上面对滚动条的创建来完成的,其中Indicator的wrapper属性就是对滚动条的包裹元素,即scrollbar滚动槽元素的引用!
function Indicator (scroller, options) {this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el;//wrapper自己指定(此处的wrapper是Indicator对象具有的wrapper)。返回的DOM结构为<div id="scrollbar"><div id="indicator"></div></div>,也就是wrapper对象就是内部的scrollbar元素DOM。//因为这里构造的是Indicator对象,所以其wrapper当然就是scrollbar元素。如果是创建指示元素那么其wrapper就表示我们自己通过el指定this.wrapperStyle = this.wrapper.style;//scrollbar元素的style属性this.indicator = this.wrapper.children[0];//获取indticator属性,也是一个DOMthis.indicatorStyle = this.indicator.style;//获取indicator的style属性this.scroller = scroller;//indicator的scroller属性持有的就是iScroll元素的引用this.options = {listenX: true,//表示监听X轴listenY: true,//表示监听Y轴interactive: false,//可以操作resize: true,//滚动条的大小是基于wrapper和scroller的width/height来设定的,通过设置resizeScrollbars可以把滚动条设置为一个指定的大小defaultScrollbars: false,shrink: false,fade: false,//fadespeedRatioX: 0,//指示元素的移动速度是根据sroller的大小来设定的。默认情况下是自动设置的,一般yuansu不需要改变这个值speedRatioY: 0//指示元素的移动速度是根据sroller的大小来设定的。默认情况下是自动设置的,一般不需要改变这个值};//绑定listenX,listenY,speedRatioX,speedRatioY,shrink,fade属性等for ( var i in options ) {this.options[i] = options[i];}this.sizeRatioX = 1;this.sizeRatioY = 1;this.maxPosX = 0;this.maxPosY = 0;if ( this.options.interactive ) {//如果可以是touch事件,那么我们为Indicator添加touchstart,touchend事件if ( !this.options.disableTouch ) {utils.addEvent(this.indicator, 'touchstart', this);utils.addEvent(window, 'touchend', this);}//如果可以有pointer事件,我们为Indicator添加pointerdown,pointerup事件if ( !this.options.disablePointer ) {utils.addEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this);utils.addEvent(window, utils.prefixPointerEvent('pointerup'), this);}//为Indicator添加mousedown,mouseup事件if ( !this.options.disableMouse ) {utils.addEvent(this.indicator, 'mousedown', this);utils.addEvent(window, 'mouseup', this);}}//如果没有操作滚动条就消失,fade对应于this.options.fadeScrollbarsif ( this.options.fade ) {//为iscrollbar元素添加transform属性,也就是启动硬件加速this.wrapperStyle[utils.style.transform] = this.scroller.translateZ;var durationProp = utils.style.transitionDuration;if(!durationProp) {return;}//为scrollbar元素添加transition-duration属性this.wrapperStyle[durationProp] = utils.isBadAndroid ? '0.0001ms' : '0ms';// remove 0.0001msvar self = this;if(utils.isBadAndroid) {rAF(function() {if(self.wrapperStyle[durationProp] === '0.0001ms') {self.wrapperStyle[durationProp] = '0s';}});}//为我们的scrollbar元素添加opaitcity,然后让它开始执行transform动画this.wrapperStyle.opacity = '0';}
}
其实在这里我们只为Indicator指定了wrapper,其对应于滚动条的滚动槽对象,而Indicator属性对应于滚动条对象,同时scroller对应于iScroll对象。同时为Indicator绑定了一系列的事件(注意是绑定到this.indicator上还是window对象上的)。 当然,还有一部分Indicator的方法全部定义在prototype上的,以后再分析!
创建了Indicator后,我们需要做的就是为他绑定各种事件,不过在这之前我们看看一个方法:
fade: function (val, hold) {//如果hold为true同时当前元素是不可见的,那么不会调用fade放啊if ( hold && !this.visible ) {return;}clearTimeout(this.fadeTimeout);this.fadeTimeout = null;var time = val ? 250 : 500,delay = val ? 0 : 300;//如果没有传递valval = val ? '1' : '0';this.wrapperStyle[utils.style.transitionDuration] = time + 'ms';//下面是一个立即执行函数this.fadeTimeout = setTimeout((function (val) {this.wrapperStyle.opacity = val;this.visible = +val;}).bind(this, val), delay);}
下面就是绑定的各种事件,如scrollCancel,scrollStart,beforeScrollStart,refresh,destroy事件等:
if ( this.options.fadeScrollbars ) {this.on('scrollEnd', function () {_indicatorsMap(function () {this.fade();//默认time是500(也就是anmation-durantion),delay为300,val(也就是opacity)为"0"(表示完全透明)。//就是使用val参数来指定animation-duration和animation-delay属性的值,其中iScroll元素的visible属性也是通过val来指定的//如果第一个参数没有指定那么就是0,否则就是1});});this.on('scrollCancel', function () {_indicatorsMap(function () {this.fade();//调用Indicator的prototype上的fade方法。});});this.on('scrollStart', function () {_indicatorsMap(function () {this.fade(1);//scrollstart表示开始滚动,这时候opacity就是1,也就是要让它显示出来});});this.on('beforeScrollStart', function () {_indicatorsMap(function () {this.fade(1, true);//beforeScrollStart还没有开始滚动});});
}//绑定refresh事件
this.on('refresh', function () {_indicatorsMap(function () {this.refresh();});
});
//绑定destroy事件
this.on('destroy', function () {_indicatorsMap(function () {this.destroy();});delete this.indicators;
});
从上面我们可以清楚的看到,我们为iScroll对象绑定了refresh事件
//绑定refresh事件this.on('refresh', function () {_indicatorsMap(function () {this.refresh();});});
在refresh事件中我们调用了Indicators中的所有的refresh事件,我们先看看iScroll对象的refresh事件:
//刷新:refresh做的事情就是获取水平垂直可以滚动的距离,然后触发refresh事件refresh: function () {utils.getRect(this.wrapper);//首先获取到包裹元素矩形对象的clientWidth/clientHeight,clientWidth=width+2*borderWidththis.wrapperWidth = this.wrapper.clientWidth;this.wrapperHeight = this.wrapper.clientHeight;//获取scroller元素的矩形对象,也就是他的width/height属性var rect = utils.getRect(this.scroller);this.scrollerWidth = rect.width;this.scrollerHeight = rect.height;//maxScrollX,maxScrollY表示的最大的滚动距离,其值为父元素的clientWidth-子元素的width//wrapper可以设置width,但是scroll是不可以设置宽度的,所以maxScrollX如果为负数,那么表示scroll特别宽,这时候表示可以往左边移动,也就是是负数//wrapper可以设置height,但是scroll是不可以设置高度的,所以maxScrollY如果为负数,表示元素可以往上面移动this.maxScrollX = this.wrapperWidth - this.scrollerWidth;this.maxScrollY = this.wrapperHeight - this.scrollerHeight;this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0;this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0;//如果指定了scrollX,同时maxScrollX<0。那么这时候表示有水平的滚动条,如果>=0肯定是没有水平滚动条的//如果指定了scrollY,同时maxScrollY<0。那么这时候表示有垂直的滚动条,如果>=0肯定是没有垂直滚动条的if ( !this.hasHorizontalScroll ) {this.maxScrollX = 0;this.scrollerWidth = this.wrapperWidth;}//如果没有垂直滚动条,那么maxScrollY就是0,同时scroll的高度和wrap的高度是一样的if ( !this.hasVerticalScroll ) {this.maxScrollY = 0;this.scrollerHeight = this.wrapperHeight;}this.endTime = 0;this.directionX = 0;this.directionY = 0;this.wrapperOffset = utils.offset(this.wrapper);//获取wrapper元素的offset值,一直往上计算,一直到该元素没有offsetParent为止,同时要记住:这是逐级往上计算的,而且这是负数,通过这种方式可以简单的获取到距离document的距离!this._execEvent('refresh');//触发refresh事件this.resetPosition();}
在iScroll对象的refresh事件中主要是获取到可以滚动的垂直方向和水平方向的距离,同时触发iScroll对象的refresh事件。在看iScroll的refresh事件之前我们首先看看resetPosition方法:
//重新设置位置,以time作为参数//that.resetPosition(that.options.bounceTime)resetPosition: function (time) {//this.x、this.y表示iScroll对象当前所在的位置var x = this.x,y = this.y;time = time || 0;//如果没有水平滚动条或者this.x>0那么x=0if ( !this.hasHorizontalScroll || this.x > 0 ) {x = 0;//如果this.x<this.maxScrollX那么水平方法可以滚动的距离为this.maxScrollX } else if ( this.x < this.maxScrollX ) {x = this.maxScrollX;}//没有垂直滚动条y=0,如果有垂直滚动条那么就是this.maxScrollYif ( !this.hasVerticalScroll || this.y > 0 ) {y = 0;} else if ( this.y < this.maxScrollY ) {y = this.maxScrollY;}if ( x == this.x && y == this.y ) {return false;}//滚动到x,y的坐标,时间为time,函数为this.options.bounceEasing。调用对象为该iScroll对象this.scrollTo(x, y, time, this.options.bounceEasing);return true;}
看完resetPosition,我们看看触发了iScroll的refresh事件时候,iScroll是如何处理的:
this.on('refresh', function () {_indicatorsMap(function () {this.refresh();});});
很显然,其会触发所有的Indicator的refresh事件:
参考文献:
从源码角度深入理解iScroll中的scrollbars和indicators配置相关推荐
- 从源码角度深入理解Toast
Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(this, "333", Toast.LENGTH_LONG).sho ...
- 从源码角度解析线程池中顶层接口和抽象类
摘要:我们就来看看线程池中那些非常重要的接口和抽象类,深度分析下线程池中是如何将抽象这一思想运用的淋漓尽致的. 本文分享自华为云社区<[高并发]深度解析线程池中那些重要的顶层接口和抽象类> ...
- 从源码角度彻底理解ReentrantLock(重入锁)
源码分析ReentrantLock https://blog.csdn.net/lxltmac/article/details/84871929白话讲解lock
- [Java]源码角度深入理解哈希表,手撕常见面试题
专栏简介 :java语法及数据结构 题目来源:leetcode,牛客,剑指offer 创作目标:从java语法角度实现底层相关数据结构,达到手撕各类题目的水平. 希望在提升自己的同时,帮助他人,,与大 ...
- 从源码角度解读 xml 文件中的 xmlns、xsi、xsd
xml 文件中的 xmlns.xsi.xsd xmlns xsi xsd 下面是 spring.xml 中的一段: <beans xmlns="http://www.springfra ...
- 从源码角度理解 FragmentTransaction实现
谈到fragment的使用,肯定绕不过FragmentTransaction事务,对fragment的操作必定用到它,其提供show,hide,add,remove,replace等常用的fragme ...
- 从源码角度理解LinearLayout#onMeasure对child的measure调用次数
熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们就从LinearLayout开始学起,看一下它对子View的onMeasure调用次数具体是多少. 简单起见 ...
- 从源码角度理解FrameLayout#onMeasure对child的measure调用次数
熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们就从最简单的FrameLayout开始学起,看一下它对子View的onMeasure调用次数具体是多少. 简 ...
- 从源码角度理解ConstraintLayout#onMeasure对child的measure调用次数
熟悉绘制流程的都知道,ViewGroup可以决定child的绘制时机以及调用次数. 今天我们简单看下较为复杂的ConstraintLayout,看一下它对子View的onMeasure调用次数具体是多 ...
- 从源码角度解析Android中APK安装过程
从源码角度解析Android中APK的安装过程 1. Android中APK简介 Android应用Apk的安装有如下四种方式: 1.1 系统应用安装 没有安装界面,在开机时自动完成 1.2 网络下载 ...
最新文章
- notepad++安装
- MySQL子查询操作实例详解
- 安卓应用自动化测试工具汇总
- Python+Opencv根据颜色进行目标检测
- sharepoint timer job 读取config文件内容
- MongoDB数据库可视化工具实现删除功能
- mongodb集群分片环境搭建
- python二次开发ug_CAD二次开发(UG/Proe/其他) - 随笔分类 - 白途思 - 博客园
- 苹果连接电脑 计算机不显示硬盘,苹果连接电脑没反应怎么办?苹果连接电脑没反应解决方法...
- android 4g ram够么,4G还不够,安卓手机内存极限是多少
- 天蝎座最适合的职业-天蝎座不同型血适合工作分析
- gif动态图太大如何发微信?手机如何快速压缩动图?
- JustSoso笔记
- 启天m420进入不了bios_联想启天M420c装win7及BIOS设置教程(USB驱动可用)
- [千峰安全篇9]Public Key Infrastructure
- 这可能是前端开发中能遇到最全的cookie问题了
- 【逻辑漏洞】业务中常见的漏洞
- Appium自动化测试框架
- .net 多线程之线程取消
- Apollo自动驾驶进阶课(5)——Apollo感知技术
热门文章
- 从微盟员工删库跑路看程序员的职业素养。
- 安徽汽车网程序员删库跑路?安徽汽车官网只剩3张图片!
- c语言if用法详解,C语言if语句的使用讲解
- 利用jsonp跨域访问
- 顺利通过2020年下工信部的系统架构设计师考试,在此感悟一下
- 「HEOI 2014」南园满地堆轻絮
- Cadence用于版图设计时芯片logo的制作
- python爬取去哪儿网机票_去哪儿网机票爬虫
- javascript——构造函数和原型对象
- Nik Collection v3.0.7 2020 Mac/Win PS/LR超强调色滤镜合集Nik插件中文版+中文教程