前言:
现在移动互联网发展火热,手机上网的用户越来越多,甚至大有超过pc访问的趋势。所以,用web程序做出仿原生效果的移动应用,也变得越来越流行了。这种程序也就是我们常说的单页应用程序,它也有一个英文缩写,叫SPA; 它最大的特点就是可以利用前端技术做出跨平台的移动应用。技术难点在于理解虚拟页面与物理页面之间的变换关系。一个偶然的机会,我由php程序员转为web前端开发,主攻javascript编程,不知不觉,已经快两年了。一直有一种想写一个webapp应用框架的冲动,但是各种原因,终究没有付出实践。于是打算从做一个简单的webapp应用开始,万事开头难,今天就搭一个简单的界面。
HTML代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>单页应用</title>
<link rel="stylesheet" href="css/common.css" type="text/css"/>
</head>
<body>
<div class="container">
<header>
<h3>sameple test </h3>
</header>
<ul class="root">
<li class="page">1</li>
<li class="page">2</li>
<li class="page">3</li>
<li class="page">4</li>
<li class="page">5</li>
<li class="page">6</li>
<li class="page">7</li>
<li class="page">8</li>
<li class="page">9</li>
<li class="page">10</li>
</ul>
<div class="left">prev</div>
<div class="right">next</div>
<footer>
<h4>(c)2015 by ouyangli</h4>
</footer>
</div>
</body>
<script type="text/javascript" src="lib/core.js"></script>
</html>
css:
ul , li {
margin:;
padding:;
list-style: none;
}
h3,h4,p {
margin:;
padding:;
}
header {
position: absolute;
width:100%;
top:;
left:;
z-index:;
} header h3 {
text-align: center;
height: 3em;
line-height: 3em;
border-bottom: 1px solid green;
} .container {
position: absolute;
width :320px;
height: 480px;
left:320px;
top:2em;
} .root {
position: absolute;
width :100%;
height: 100%;
top :;
left:;
overflow:hidden;
-webkit-perspective:;
-webkit-user-select: none;
-webkit-transform-style:preserve-3d;
} .page {
position: absolute;
width: 318px;
height: 100%;
overflow: hidden;
border:1px solid green;
} .left {
left :2px;
}
.right {
right:2px;
} .left,.right {
position: absolute;
top:45%;
width:3em;
height: 3em;
line-height: 3em;
text-align: center;
border-radius: 15%;
border:1px dashed blue;
} .left:hover,.right:hover {
background-color: #33ff44;
cursor:pointer;
} footer {
position: absolute;
width: 100%;
bottom:;
} h4 {
height: 3em;
line-height: 3em;
text-align: center;
border-top: 1px solid green;
}
以上源码将会在页底提供打包下载,这里贴出中间过程,只是想让大家能明白,我是怎么一步一步把这个程序写出来的。如果有疑问的地方就给我留言好了,我会尽量回复。
演示地址:http://runjs.cn/detail/o4ql6f6a
细心的话,你会发现左上角有一个“乱码”,其实那是因为所有的页面都堆叠在一起,造成页数看不清了。这正是我们接下来要解决的问题之一。
js:
//初始化
;(function(){
var pages = document.querySelectorAll('li');
var width = 320;
var len = pages.length;
var setpost = function(element){
element.style.transform = 'translate3d('+width+'px, 0, 0)';
} //把除1以外的页堆在右边
while(--len){
setpost(pages[len]);
}
}());
好了,现在看起来仅管还是很丑,但是至少已经符合我预期的样子了。在这里我把第一页已外的页面全部摆到了屏幕的右边,你看不到它们。这样做的目的是要模拟手机上的翻页效果。接下来,就要实现这个非常令人期待的滑动翻页效果。现在该javascript发挥威力的时候了。这是一个简单的应用,我尽量把所有的js写在core.js中,并采用最普通的函数式编程。
//初始化
;(function(){
var pages = document.querySelectorAll('li');
var width = 320;
var len = pages.length;
var setpost = function(element){
element.style.transform = 'translate3d('+width+'px, 0, 0)';
} //把除1以外的页堆在右边
while(--len){
setpost(pages[len]);
}
}()); //控制逻辑
;(function(){
//这里直接使用了新的api,因为移动应用可以这样任性。
var pages = document.querySelectorAll('li');
//左右翻页按钮
var left = document.querySelector('.left');
var right = document.querySelector('.right');
//取得所有的子页面
var pagesLen = pages.length-1; //标记当前页面
var currIndex = 0; //移动页面
var move = function(index,pos){
var width = 320 * pos;
var page = pages[index];
page.style.transform = 'translate3d('+width+'px, 0, 0)';
} //向左翻页
var toLeft = function(){ if(currIndex!==pagesLen){
move(currIndex,-1);
move(++currIndex,0);
}
} //向右翻页
var toRight = function(){
if(currIndex!==0){
move(currIndex,1);
move(--currIndex,0);
}
} //监听动作
left.onclick = function(){
toLeft();
} right.onclick = function(){
toRight();
} }())
现在我们的程序可以左右翻页了,不过除了页码发生了变化之外,用户好像感觉不到有翻页的效果。左右翻页按钮的作用与我们期望的效果不一致,所以初始化时,把页面堆在右边其实是不太好的,改为左边会更好一些。嗯,还要继续完善。
//初始化
;(function(){
var pages = document.querySelectorAll('li');
var width = 320;
var len = pages.length;
var setpost = function(element){
element.style.transform = 'translate3d(-'+width+'px, 0, 0)';
} //把除1以外的页堆在左边
while(--len){
setpost(pages[len]);
}
}()); //控制逻辑
;(function(){
//这里直接使用了新的api,因为移动应用可以这样任性。
var pages = document.querySelectorAll('li');
//左右翻页按钮
var left = document.querySelector('.left');
var right = document.querySelector('.right');
//取得所有的子页面
var pagesLen = pages.length-1; //标记当前页面
var currIndex = 0; //移动页面
var move = function(index,pos){
var width = 320 * pos;
var page = pages[index];
page.style.transform = 'translate3d('+width+'px, 0, 0)';
page.style.transitionDuration = '300ms';
} //上一页
var toLeft = function(){
if(currIndex >0){
move(currIndex,-1);
move(--currIndex,0);
}
} //下一页
var toRight = function(){
if(currIndex < pagesLen){
move(currIndex,1);
move(++currIndex,0);
}
} //监听动作
left.onclick = function(){
toLeft();
} right.onclick = function(){
toRight();
} }())
现在终于看起来像是在翻页的样子了,不过呢,我们在手机上操作的时候,是可以滑动翻页的,这个效果自然也要支持才行。我们先在pc上用鼠标拖动来模拟这一个过程,等逻辑跑通后,再加入触摸事件即可。
继续下一步之前,我们先小结一下:
首先在初始化代码时,顺道把所有不可见的页面全部堆叠到左边,放在左边的原因是因为我们习惯右边的按钮作为下一页,所以这样摆放,实现起来最简单,我当然要选择最有利于我编码的方式来摆放啦。其次呢,每一次翻页,其实都要移动两个页面。拿点击下一页按钮来说,首先要把当前页移动到不可见的屏幕右边,然后把上一页移到屏幕中间来。上一页的过程正好与之相反。我们发现,点击翻页和滑动翻页,其实都会调用相同的功能。所以在这里可先抽出公共方法,方便代码复用。
接下来,我们实现鼠标拖动翻页的效果。
//初始化
;(function(){
var pages = document.querySelectorAll('li');
var width = 320;
var len = pages.length;
var setpost = function(element){
element.style.transform = 'translate3d(-'+width+'px, 0, 0)';
} //把除1以外的页堆在左边
while(--len){
setpost(pages[len]);
}
}()); //控制逻辑
;(function(){
//这里直接使用了新的api,因为移动应用可以这样任性。
var pages = document.querySelectorAll('li');
//左右翻页按钮
var left = document.querySelector('.left');
var right = document.querySelector('.right');
//取得所有的子页面
var pagesLen = pages.length-1;
//屏宽
var screenWidth = 320; //标记当前页面
var currIndex = 0; //标记是否触发滑动
var isTouch = false;
//记录当前位置
var axis = {
x:0,
y:0
} //移动页面
var move = function(index,width,time){
var page = pages[index];
page.style.transform = 'translate3d('+width+'px, 0, 0)';
page.style.transitionDuration = time+'ms';
} //上一页
var toLeft = function(){
if(currIndex >0){
move(currIndex,-screenWidth,300);
move(--currIndex,0,300);
}
} //下一页
var toRight = function(){
if(currIndex < pagesLen){
move(currIndex,screenWidth,300);
move(++currIndex,0,300);
}
} //监听动作
left.onclick = function(){
toLeft();
} right.onclick = function(){
toRight();
} document.addEventListener('mousedown',function(e){
isTouch = true;
axis.x = e.clientX;
axis.y = e.clientY;
}); document.addEventListener('mousemove',function(e){
if(isTouch){
var distance = e.clientX - axis.x;
if(distance>0){
//next
//此时需要看到实时移动效果,所以时间为0
move(currIndex,distance,0);
//当前页与上一页之间总是相差一个屏宽
move(currIndex+1,distance-screenWidth,0);
}else{
//prev
move(currIndex,distance,0);
move(currIndex-1,screenWidth+distance,0); }
}
}); document.addEventListener('mouseup',function(e){
isTouch = false;
}); }())
这一步我们已经实现了拖动滑页效果,但是感觉怪怪的,对比一下手机上的滑动翻页效果发现,真机上只要我们滑动一定距离之后,页面就自动翻过去了,而不是要我们从一边一直滑到另一边,这样也太不实际了,而且如果我们只滑了一点距离,那么页面会自动归位,也就是常说的反弹效果。要实现这些也不难,我们继续完善代码。
//初始化
;(function(){
var pages = document.querySelectorAll('li');
var width = 320;
var len = pages.length;
var setpost = function(element){
element.style.transform = 'translate3d(-'+width+'px, 0, 0)';
} //把除1以外的页堆在左边
while(--len){
setpost(pages[len]);
}
}()); //控制逻辑
;(function(){
//这里直接使用了新的api,因为移动应用可以这样任性。
var pages = document.querySelectorAll('li');
//左右翻页按钮
var left = document.querySelector('.left');
var right = document.querySelector('.right');
//取得所有的子页面
var pagesLen = pages.length-1;
//屏宽
var screenWidth = 320;
//反弹时间
var time = 300;
//滑动距离
var distance=0;
//标记当前页面
var currIndex = 0; //标记是否触发滑动
var isTouch = false;
//记录当前位置
var axis = {
x:0,
y:0
} //移动页面
var move = function(index,width,time){
var page = pages[index];
page.style.transform = 'translate3d('+width+'px, 0, 0)';
page.style.transitionDuration = time+'ms';
} //上一页
var toLeft = function(){
if(currIndex >0){
move(currIndex,-screenWidth,time);
move(--currIndex,0,time);
}
} //下一页
var toRight = function(){
if(currIndex < pagesLen){
move(currIndex,screenWidth,time);
move(++currIndex,0,time);
}
} //监听动作
//prev
left.onclick = function(){
toLeft();
}
//next
right.onclick = function(){
toRight();
} document.addEventListener('mousedown',function(e){
isTouch = true;
axis.x = e.clientX;
axis.y = e.clientY;
}); document.addEventListener('mousemove',function(e){
if(isTouch){
distance = e.clientX - axis.x;
if(distance>0){
//next
if(currIndex<pagesLen){
//此时需要看到实时移动效果,所以时间为0
move(currIndex,distance,0);
//当前页与上一页之间总是相差一个屏宽
move(currIndex+1,distance-screenWidth,0);
}
}else{
if(currIndex>0){
//prev
move(currIndex,distance,0);
move(currIndex-1,screenWidth+distance,0);
}
}
}
}); document.addEventListener('mouseup',function(e){
isTouch = false;
//反弹条件
var band = Math.ceil(screenWidth * 0.3);
//next
if(distance >0 && currIndex < pagesLen){
if(distance > band){
toRight();
}else{
//滑动距离太小,页面反弹
move(currIndex,0,time);
move(currIndex+1,-screenWidth,time);
}
return;
}
//prev
if(distance < 0 && currIndex > 0){
if(-distance > band){
toLeft();
}else{
//反弹
move(currIndex,0,time);
move(currIndex-1,screenWidth,time);
}
}
}); }())
最后发现有一点小问题,滑动之后再点翻页按钮,乱套了。仔细分析之后,找出了问题所在,松手时的移动距离不应该用滑动时的最后距离,所以修复很容易。
//初始化
;(function(){
var pages = document.querySelectorAll('li');
var width = 320;
var len = pages.length;
var setpost = function(element){
element.style.transform = 'translate3d(-'+width+'px, 0, 0)';
} //把除1以外的页堆在左边
while(--len){
setpost(pages[len]);
}
}()); //控制逻辑
;(function(){
//获取所有页面
var pages = document.querySelectorAll('li');
//左右翻页按钮
var left = document.querySelector('.left');
var right = document.querySelector('.right');
//取得所有的子页面
var pagesLen = pages.length-1;
//屏宽
var screenWidth = 320;
//反弹时间
var time = 300; //标记当前页面
var currIndex = 0; //标记是否触发滑动
var isTouch = false;
//记录当前位置
var axis = {
x:0,
y:0
} //移动页面
var move = function(index,width,time){
var page = pages[index];
page.style.transform = 'translate3d('+width+'px, 0, 0)';
page.style.transitionDuration = time+'ms';
} //上一页
var toLeft = function(){
if(currIndex >0){
move(currIndex,-screenWidth,time);
move(--currIndex,0,time);
}
} //下一页
var toRight = function(){
if(currIndex < pagesLen){
move(currIndex,screenWidth,time);
move(++currIndex,0,time);
}
} //监听动作
//prev
left.onclick = function(){
toLeft();
}
//next
right.onclick = function(){
toRight();
} document.addEventListener('mousedown',function(e){
isTouch = true;
axis.x = e.clientX;
axis.y = e.clientY;
}); document.addEventListener('mousemove',function(e){
if(isTouch){
//滑动距离
var distance = e.clientX - axis.x;
if(distance>0){
//next
if(currIndex<pagesLen){
//此时需要看到实时移动效果,所以时间为0
move(currIndex,distance,0);
//当前页与上一页之间总是相差一个屏宽
move(currIndex+1,distance-screenWidth,0);
}
}else{
if(currIndex>0){
//prev
move(currIndex,distance,0);
move(currIndex-1,screenWidth+distance,0);
}
}
}
}); document.addEventListener('mouseup',function(e){
//松手时的移动距离
var distance = e.clientX - axis.x;
//反弹条件
var band = Math.ceil(screenWidth * 0.3);
isTouch = false;
//next
if(distance >0 && currIndex < pagesLen){
if(distance > band){
toRight();
}else{
//滑动距离太小,页面反弹
move(currIndex,0,time);
move(currIndex+1,-screenWidth,time);
}
return;
}
//prev
if(distance < 0 && currIndex > 0){
if(-distance > band){
toLeft();
}else{
//反弹
move(currIndex,0,time);
move(currIndex-1,screenWidth,time);
}
}
}); }())
到此,这个简易的移动应用就建好了。发现这其实只是搭好了一个架子,里边还有很多内容可以填。比如,给每页加点背景和文字,让页面看起来更加丰满,给标题栏加点工具图标,让它看起来更像是一个原生的apk程序。当我们双击或长按页面空白区域的时候,自动隐藏或显示翻页按钮,增加对更多浏览器的支持,增加对移动设备的支持........
可以发挥的地方太多太多了,锦上添花的事就只好交给各位了。
一步一步做下来,收获了些什么呢?总结一下:
首先要有一个清晰的思路,先确定要实现什么功能,在脑海里有一个整的印象。把UI先建起来,这一步很容易实现,在此基础上,我们就可以看到,有哪些功能需要js去做,哪些效果需要css去做。先易后难。先整体后局部。在做的过程中,一步一步的完善,扩展,优化。一开始就想着要多完美,多优化,结果只是不断的否定自己的想法。不要怕出错,前面的演示中,我们发现,每一步都有些小错误,但是只要我们的整体思路是符合预期的,修正起来就会很快,这个修错的过程,也是一次学习的机地,总结的多了,以后就可以少出错,少被坑。
效果图:
加点内容,是不是立马显得高大上了呀!
源码打包下载:https://github.com/bjtqti/swipe.git
本文系原创,如果喜欢就砸个赞吧!