XiaosanSky'Blog


  • 首页

  • 归档

  • 标签

移动端滑动框架scroll5

发表于 2016-04-07   |  

解决痛点

移动设备中原生对大段文章或超长列表滚动显示的支持不太友好:当一屏显示不全内容时,对于有确定高度和宽度的内容是可以滑动的,但是在低版本Android及现在的IPhone设备中,CSS的position:fixed属性是不支持的。这实际上限制了我么实现上方固定标题/工具栏,下方固定页脚,中间内容滚动的展示效果。借助Iscroll,我们可以只对需要滚动的部分使用Iscroll,同时预留出上下位置即可。另一方面当内容过长时,依靠原生的滚动支持会有明显的卡顿情况,Iscroll则会根据设备支持情况自动选用不同滚动实现形式,当然我们也可以手动控制。
下载及官网地址

可能的坑

兼容性

我们的项目之前做过一部分机型的适配检测:Iscroll总体来说兼容性做的还是不错的,在现在主流版本的Android和所有iPhone设备上都运行良好。目前发现在MUI和华为部分机型的4.4版本下会出现页面直接不能滑动的情况,这个问题卡了我好久,看了好几天Android webView的BUG,最后推测应该是这两个平台下的webView控件对触摸事件的响应有问题,js默认的触摸事件覆盖了Iscroll的事件冒泡。
解决方法是优先调用Iscroll的touchmove事件而不是document的:

1
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);

当然这样做的话页面上如果有用默认的滚动效果的话也就不起作用了,我们可以进一步限定只在使用Iscroll滚动的元素上禁止touchmove事件:

1
2
var scroller=document.getElementById('scroller');
scroller.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);

动态加载后不能滚动

我在列表ul上使用了Iscroll,同时做了分页加载,我发现当在ul后新添加了元素后页面就卡住不能滚动了,这个其实是我们新增加元素后滚动元素的高度发生了变化,这时候还按照之前高度计算滚动距离等当然就不对了,所以我们应该在每次做改变元素高度的操作的时候手动通知Iscroll刷新,比如在ajax请求数据成功后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$.ajax({
async:true,
cache:false,
type:"POST",
url:xxx
data: {},
dataType: "JSON",
error:function(jqXHR, textStatus, errorThrown){
$.showDialog();
},
success:function(msg){
fillList(msg);
myScroll.refresh();
}
});

不能选取某些元素

有时我们在滚动元素内部会添加输入框、下拉框等元素,如果使用默认Iscroll设置会出现这些元素不能获取焦点的问题,这是因为Iscroll为了让滚动更流畅,屏蔽了一些元素的touch等事件(想象一下我们用手滚动页面时实际上touch了很多元素,如果这些事件都需要处理的话是比较耗资源的)。
在Iscroll4版本里我们可能需要修改源码才能支持对某些元素的选中,源码:

1
2
3
4
5
6
onBeforeScrollStart: function (e) {
var target = e.target;
  while (target.nodeType != 1) target = target.parentNode;
  if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA')
    e.preventDefault();
},

但在Iscroll5版本里已经可以在初始化Iscroll时通过设置来指定过滤掉哪些元素的touch响应:

1
2
3
var myScroll = new IScroll('#wrapper', {
preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|LI)$/ }
});

我们想使用输入框是需要去掉“INPUT”就行了。

最简实践

最简单的HTML结构为:

1
2
3
4
5
6
<div id="wrapper">
<ul>
<li></li>
...
</ul>
</div>

Iscroll需要在页面加载后手动初始化,对于要滚动元素既可以指定选择器字符串,也可以是引用对象:

1
2
3
4
5
function loaded () {
var myScroll = new IScroll('#wrapper',{});
/*var wrapper = document.getElementById('wrapper');
var myScroll = new IScroll(wrapper,{});*/

}

{}内是Iscroll的一个数组参数,在这里我们可以配置一些常见设置,如是否滑动后回弹、开启或关闭CSS transforms支持等。

分页加载

对于超长列表,我们经常会用到上拉加载和下拉刷新功能以实现分页展示,这在用户体验和性能上的优势不言自明,对Iscroll进行一些改造就能支持这样的效果。在这里我们主要利用了Iscroll的自定义事件:Iscroll提供了一些自定义事件可以用on的方式绑定到滚动元素上,以在一些特殊时刻处理滚动操作,比如scrollStart和scrollEnd分别在滚动开始和结束时触发。
HTML结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="fltList" id="wrapper">
<div id="scroller">
<div id="pullDown">
<span class="pullDownIcon"></span><span class="pullDownLabel">下拉刷新...</span>
</div>
<ul id="testList">
</ul>
<div id="pullUp">
<span class="pullUpIcon"></span><span class="pullUpLabel">加载更多...</span>
</div>
</div>
</div>
<div id="return2top"></div>

数据都添加到testList这个ul中;pullDown和pullUp用于实现根据下拉和上拉的距离更改用户提示;return2top这个元素是用来从任意位置返回列表顶端的(在一个超长列表中让用户自己从最低端再划回去有点太不人道了,最好有一键返回。这里用到了scrollTo接口)。
js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var myScroll,
scrollLen=5,
pullDownEl = document.getElementById('pullDown'),
pullDownOffset = pullDownEl.offsetHeight,
pullUpEl = document.getElementById('pullUp'),
pullUpOffset = pullUpEl.offsetHeight,
winHeight=window.innerHeight,
pageLoading=document.getElementById('pageLoading'),
isLoading=false;
$('#return2top').click(function(){
myScroll.scrollTo(0,0,500,IScroll.utils.ease.quadratic);
});
myScroll.on("refresh",function(){
if (pullDownEl.className.match('loading')) {
pullDownEl.className = '';
pullDownEl.querySelector('.pullDownLabel').innerHTML = '下拉刷新...';
} else if (pullUpEl.className.match('loading')) {
pullUpEl.className = '';
pullUpEl.querySelector('.pullUpLabel').innerHTML = '加载更多...';
}
});

myScroll.on("scroll",function(){
if (this.y > scrollLen && !pullDownEl.className.match('flip')) {
pullDownEl.className = 'flip';
pullDownEl.querySelector('.pullDownLabel').innerHTML = '释放后刷新...';
this.minScrollY = 0;
} else if (this.y < scrollLen && pullDownEl.className.match('flip')) {
pullDownEl.className = '';
pullDownEl.querySelector('.pullDownLabel').innerHTML = '下拉刷新...';
this.minScrollY = -pullDownOffset;
pageLoading.style='';
} /*else if (this.y < (this.maxScrollY - scrollLen) && !pullUpEl.className.match('flip')) {
pullUpEl.className = 'flip';
pullUpEl.querySelector('.pullUpLabel').innerHTML = '释放后加载...';
this.maxScrollY = this.maxScrollY;
} else if (this.y > (this.maxScrollY + scrollLen) && pullUpEl.className.match('flip')) {
pullUpEl.className = '';
pullUpEl.querySelector('.pullUpLabel').innerHTML = '加载更多...';
this.maxScrollY = pullUpOffset;
}*/

});

myScroll.on("scrollEnd",function(){
if (pullDownEl.className.match('flip')) {
pullDownEl.className = 'loading';
pullDownEl.querySelector('.pullDownLabel').innerHTML = '正在加载...';
} else /*if (pullUpEl.className.match('flip'))*/ {
pullUpEl.className = 'loading';
pullUpEl.querySelector('.pullUpLabel').innerHTML = '正在加载...';
}
if((document.getElementById('fltList').clientHeight-200)>winHeight){
$('#return2top') .show();
}else{
$('#return2top') .hide();
}
});
myScroll.on("slideDown",function(){
if(this.y > scrollLen && !isLoading){
isLoading=true;
freshList();//刷新列表方法
}
});

myScroll.on("slideUp",function(){
if(this.maxScrollY - this.y > scrollLen && !isLoading){
isLoading=true;
getList();//获取列表数据,并添加至列表中
}
});

上述代码主要用来判断滑动距离以更改下拉、刷新提示信息,以及何时执行下拉、刷新动作(当滑动超过列表最上、最下距离时回弹并触发事件,模拟Android和iPhone中的原生加载体验)。代码中注释掉的是判断上拉加载的样式的部分,因为从实际上线后用户反馈来看,上拉一段距离回弹后再加载数据显得有些繁琐,我直接改成滑动到底边时就触发请求数据的方法,可以让用户感觉不到数据有分页,而是一个无限长的完整列表。如果有需要做上拉一段距离再触发加载的,可以恢复注释掉的内容。
代码开头给return2top元素绑定点击事件这里就是做了快速回到顶部的功能,scrollTo的函数定义为scrollTo(x, y, time, easing)。x和y表示要滚动到的位置,(0,0)自然是滚动到最上面的位置了;time为滚动动画持续的时间,即以多块的速度滚动到最上方;easing为指定动画的类型,可选项为quadratic、circular、back、bounce、elastic,可以都自己试一遍,里面有些回到顶端弹动一会儿再渐渐停止的效果还是很有意思的,不过我为了性能及软件整体风格的考量,只使用了最简单的quadratic动画。
还有一点需要指出的是我在触发数据加载的判断中加入了是否正在加载,即判断isLoading。默认其值应为false,当触发了加载时间时应该先置为true以防止用户多次滑动触发请求,这在使用移动网络时是非常必要的,否则将造成多次重复加载同一页数据情况。当然记得在freshList()和getList()自己实现数据异步请求和解析后将标志位isLoading复位为false。

常用API说明

本来写了一大堆,但我在查找某个参数的默认值时发现gitbook上已经有人写了中文版API文档,确实写的很好,后悔之前用的时候没发现。。
Iscroll5中文API

总结

Iscroll是一个非常好用的滚动处理插件,同时也非常轻量级(针对移动设备,不需要那么多功能的可以在下载时选择lite版本),本身已经提供了多种动画只需要简单配置就能使用。更赞的是它提供了相当多的接口,使用这些接口我们可以获取足够多的滚动相关的数据以定制化自己的功能,我还见过用Iscroll实现的PPT放映效果,对于不想使用全套移动js框架的同学来说真心推荐使用。
移动web开发使用的框架除了Iscroll和jQuery外我还用了Mobilebone,模拟原生APP页面切换用的,有需要的同学可以参考下我的上一篇日志。接下来我会整理下最近搞移动混合式开发的一些心得,主要是样式兼容性和js与原生代码交互的坑,敬请期待~

单页面切换插件Mobilebone简介

发表于 2016-04-05   |  

Mobilebone是国内大牛张旭鑫写的一个单页面切换插件,特点是轻量级,且效果也不错,最近的webAPP项目里用过,暂时没发现有什么坑,特此安利一下,顺便介绍简单用法。

解决问题

移动端webAPP模拟原生应用的各类页面切换效果。

项目的demo:


demo

推荐实践

作者在Mobilebone里提供了多种构造跳转页面的方式,甚至封装了ajax工具方法,以方便实现页面分步加载,但在我我的实践过程中发现还是用它的基本切换最好:主页面与子页面的结构都在一个HTML文件中,即第一次加载后所有页面实际已都存在,我们做页面切换只是让几个标签元素交替显示。
这个其实作者在自己的博客中也提到了,好处是切换更流畅(只异步请求数据,而不是后台拼接好的HTML),也更容易自己控制。作者原文如下:

虽然Mobilebone提供了直接请求JSON数据的方法,并提供了视图渲染接口。但是,以我个人经验,对于实际开发,这种实现策略是不推荐的。我认为更好的实现方法应该是这样的。页面骨架,也就是page主体,也就是一个空div默认就载入,然后所有的切换都是基本切换,而不是Ajax切换。在切换即将开始的时候(回调),您就可以使用自己,例如Zepto的Ajax方法去请求你需要的JSON数据,做你任何想做的事情,完全没有Mobilebone的限制。

页当然,这样做有一点不好的地方是会影响首次加载速度(毕竟所有HTML结构要在一开始就全部加载),这种基本切换适用于子页面内容相对简单的情况,我的项目中子页面的内容基本上是用js生成出来的,在实际上静态的HTML结构就只有个外壳。
html结构:

1
2
3
4
5
6
7
<div id="pageHome" class="page in"></div>
<div id="pageChild1" class="page out">
<a href="#pageHome" data-rel="back" class="backto"><span class="backto-mark"></span><span>返回</span></a>
</div>
<div id="pageChild2" class="page out">
.....
</div>

如上的HTML结构就是Mobilebone的基本切换所需的基本结构了,每一个page元素为一个页面,我在pageChild1这个子页面中增加了一个返回按钮,href内填写的是’#’+跳转页面的id,data-rel指定了跳转的过场动画为back(默认的页面切换动画只有左右滑动切换)。
这样从子页面返回主页面当然是没问题的,因为这时没有请求新数据也不需要回调,但当我们从主页面跳到子页面时应该怎么处理呢?我们需要增加回调函数,如下:

1
<a href="#pageChild1" data-reload data-preventdefault="getDetails" id="xxx"></a>

1
2
3
4
5
6
7
8
9
10
11
12
//主页面只有一处点击跳转时
Mobilebone.callback = function(pagein) {
if(pagein.id == "pageDetails"){
//判断进入的页面为子页面
}
};
//有多处需要跳转时
var getDetails = function(target) {
if (target != null && target.id != "") {
//target为点击的元素,可用来传递数据
}
}

当主页面跳转子页面只有一处时可以使用Mobilebone提供的公用的callback方法,通过判断进入的页面是子页面则进行数据ajax请求及解析数据。
若有多处跳转,如在列表点击每一项查看详情时则需要data-preventdefault属性指定页面跳转的处理函数,并传入相应的id等参数,同样调用ajax请求数据。

主要API

  • Mobilebone.callback:进入页面时执行
  • Mobilebone.fallback:离开页面时执行
    分别可以用于页面切换时进入页面请求数据,离开页面清空数据。
    每个方法都有pageInto和pageOut参数,表示进入和离开的页面对应的DOM元素对象。
    查看在线demo

    参考文档

  • 项目gitHub主页
  • 张旭鑫项目介绍
  • MobileboneAPI文档

gitHub博客第一章

发表于 2016-04-03   |  

说明

趁着清明假期搭建了这个个人博客,使用了比较火的hexo,然后随便用next主题配了下样式,详细的留待以后折腾。主要目的是整理下自己感兴趣的技术方面知识,也算是强迫自己停下来去思考、总结(最近一段时间都在赶项目进度,心累),也不排除会写写感想。

搭建博客的相关资料

  • 小白独立搭建博客
  • hexo官网文档

    友情提示:

    在搭建博客过程中遇到不少坑,在此记一记警示后来人:

    server模块

    安装网上的教程搭建博客时一定要注意文章提及的hexo版本,因为在2.x后hexo有了比较大的更新,网上教程很多针对的是老版本,而我们现在基本上在用的是3.x版本,这样就掉坑里了。
    比如在这里,hexo 3.0后将server功能独立为单独模块,我们之前使用 npm install -g hexo-cli
    安装hexo时并没有包括本功能,那么当我们进行本地调试时就需要手动安装server模块:
    npm install hexo-server –save
    避免这类问题的关键还是要以官方文档为准。

    端口冲突

    hexo的默认本地端口为4000,我在本地调试时一直不能打开博客主页,但又不是404,说明本地的这个端口是可以访问的,重新安装了多次hexo问题仍没解决,这时我想会不会有本地其他软件已经使用了4000端口,一看果然是!我的是福昕阅读器的自动更新进程占用了,有安装福昕或者也出现这样问题小伙伴可以看看是否存在端口冲突问题。若有,要么关掉第三方软件,或者增加参数指定其他端口:hexo server -p xxxx。(其实本地启动博客没多大用,也就是偶尔自己写完博客想先看看效果的时候会启一下本地,而且查看效果也可以用其他工具插件,比本地的要方便。但是我们第一次配置hexo,想看看配置是否有问题时却跑不起来,就产生误解了)

    发布至gitHub

    在填写deploy信息时要指定type为git,这里也不要受网上过时文章的误导,之前版本这里填写的是gitHub,但在最新hexo版本里这里都要填git,否则会报找不到发布模块的错误。另外某些情况下因为网络限制等原因,以HTTPS协议连接gitHub时会提示访问你的gitHub地址受限从而部署失败,这里可以改为用SSH协议(前提是你已经验证过自己的gitHub公钥),如此:
    1
    2
    3
    4
    deploy:
    type: git
    repository: git@github.com:xx/xx.github.io.git
    branch: master
Xiaosansiji

Xiaosansiji

此心安处是吾乡

3 日志
© 2016 Xiaosansiji
由 Hexo 强力驱动
主题 - NexT.Pisces