一个基于zepto.js和backbone的页面切换控制器

 粗糙的效果预览

 js部分

/**
 * native
 * 拥有native app的基本操作功能
 * @author 一只柯楠
 * 1、动画切换页面
 * 2、以HASH为线轴,/分开一个级别
 * 3、
 */

(function () {
 	
 	var initializing = false,
		superTest = /一只柯楠/.test(function () {一只柯楠;}) ? /\b_super\b/ : /.*/;
	// 临时Class
	this.Class = function () {};
	// 继承方法extend
	Class.extend = function (prop) {
		var _super = this.prototype;
		//创建一个实例,但不执行init
		initializing = true;
		var prototype = new this();
		initializing = false;

		for (var name in prop) {
			// 用闭包保证多级继承不会污染
			prototype[name] = (typeof prop[name] === 'function' && typeof _super[name] === 'function' && superTest.test(prop[name])) ? (function (name, fn) {
					return function () {
						var temp = this._super;	
						// 当前子类通过_super继承父类
						this._super = _super[name];
						//继承方法执行完毕后还原
						var ret = fn.apply(this, arguments);
						this._super = temp;

						return ret;
					}
				})(name, prop[name]) : prop[name];
		}
		
		//真实的constructor
		function Class () {
			if (!initializing && this.init) {
				this.init.apply(this, arguments);
			}
		}
		Class.prototype = prototype;
		Class.constructor = Class;
		Class.extend = arguments.callee;

		return Class;
	}

 })();

/**
 * @top prototype
 * 
 */

(function(Bakbone){

	this.iNative= {
		version: 1.0,
	};
	var $= Backbone.$,
		config= {
			debug: false,
		},

		/*
		 * 顶级基础类,继承Backbone.View.prototype
		 */
		Browser= Class.extend( _.extend(Backbone.View.prototype, {
			init: function(el, options){
				//root元素节点
				if(options){
					this.el= $(el)[0];
				}else if(el){
					if(el instanceof Element=== true || el instanceof $ === true || _.isString(el))
						this.el= $(el)[0];
					else
						options= el;
				}
				this._configure(options || {});
				//拿到view外围元素this.$el
				this._ensureElement();
				//需要在init里每次都用新的,不能直接挂在外面,会被重用
				this.children= [];
				//代理事件
				this.delegateEvents();
			},
			parentFrame: null,
			tagName: 'div',
			sleep: true,
			pageshow: function(){
				console.log('pageshow', this)
			},
			pagehide: function(){
				console.log('pagehide', this)
			},
			//销毁对象,所有需要销毁的动作都在这里执行
			destroy: function(retain) {
				var me = this;
				//触发删除事件
				me.trigger('destroy');
				//解绑所有元素的事件
				me.$el.find('*').off();
				//删除元素
				if(!remove)
					$this.$el.remove();
				me.__proto__ = null;
				_.each(me, function(i, key){
					delete me[key];
				});
			}

		})),

		/*
		 * 浏览器类,window和page公用
		 */
		defChild= {
			//scrollTop距离
			pageY: 0,
			//最后一次的hash
			hash: [],
		},
		TopWindow= Browser.extend({

			init: function(el, options){
				this._super(el, options);
				this._bindChild();
				/*
				var cur, wind;
					cur= wind= this;
					while(parent){
						wind= cur;
						cur= cur.parent;
					}
					this.wind= wind;
				*/
			},

			//index/test/
			currentChild: null,
			previousChild: null,
			childChild: false,
			currentHash: [],
			previousHash: [],
			/*
			 * 执行本身的pageshow, 判断当前子控制器是否发生切换并执行
			 */
			_pageshow: function(hash){
				var me= this;
					me.previousHash= me.currentHash;
					me.currentHash= hash;
				//唤醒自己
				this.sleep= false;
				//使用第二节判断hash[1]
				this.checkChild(hash[1], function(val, key){
					if(_.isFunction(val.frame)) val.frame= new val.frame;
						//仅在发生改变时才切换当前子节点
						if(me.currentChild!==val){
							if(me.currentChild!== null){
								me.childChange= true;
								me.setPreviour(me.currentChild);
							}
							me.setCurrent(val);
						}else{
							me.childChange= false;
						}
				});
				hash= hash.join('');
				this.pageshow(hash);
				this.trigger('pageshow', {hash: hash});
			},

			_pagehide: function(hash){
				var previousChild, frame;
				//让上一个子控制器睡眠
				if(this.childChange){
					frame= this.previousChild.frame;
					frame._pagehide(hash);
					hash= hash.join('');
					frame.trigger('pagehide', {hash: hash});
				} 
			},		  
			setCurrent: function(child, hash){
				this.currentChild= child;
			},

			setPreviour: function(child){
				this.previousChild= child;
			},	

			checkChild: function(hash, callback){
				var me= this;
				return _.some(me.children, function(val, key){
					if(val.format.test(hash)){
						callback(val, key);
						//停止遍历
						return true;
					}
				});
			},

			addChild: function(route, name, callback){
				if (!_.isRegExp(route)) route = router._routeToRegExp(route);
				if (_.isFunction(name) || name instanceof Browser) {
					callback = name;
					name = '';
				}
				if (!callback) callback = this[name];
				//告知父级控制器
				if(_.isFunction(callback))
					callback.prototype.parentFrame= this;
				else
					callback.parentFrame= this;
				//前面加一个对象是为了不把变量加载defChild上
				this.children.unshift(_.extend({}, defChild, {
					//匹配正则
					format: route,
					//控制器
					frame: callback
				}));

			},

			_bindChild: function(){
				if (!this.routes) return;
				this.routes = _.result(this, 'routes');
				var route, routes = _.keys(this.routes);
				while ((route = routes.pop()) != null) {
					this.addChild(route, this.routes[route]);
				}
			},

			destroy: function(retain){
				this._super(retain);
			}
	   }),

		/*
		 *window类
		 */

		iWindow= TopWindow.extend({
			init: function(el, options){
				this._super(el, options); 
				var me= this, $root= this.$el, html='';
				this.$el.css({
					 '-webkit-box-orient': 'horizontal'
				});
				_.each(this.children,function(val, key){
					html+= '<div style="display:none;width:100%;-webkit-transform: translate3d(0px, 0px, 0px);" ></div>';
				});
				var record=0,
				$toNode,
				$fromNode,
				events= 'touchstart'+($.browser.android ? '' : ' scroll'),
				$doc= $(document),
				resizePos= function(){
							$doc.off(events, resizePos);
							$toNode.css({
								"-webkit-transform": "translate3d(0px, 0px, 0px)", 
								marginTop: 0
							})
							//滚动到对应位置,如果露出地址栏,则不滚动
							if(me.currentChild.pageY || pageYOffset){
								scrollTo(0, me.currentChild.pageY);
							}
						};
				me.$docNodes= $(html).appendTo($root).on('webkitTransitionEnd', function(){
					var $this= $(this);
					if(me.currentChild.frame.el===this.firstElementChild){
						$toNode= $this;
						$doc.on(events, resizePos)
					}else{
						$fromNode= $this;
					}
					//触发pageshow和pagehide,为了保证pageshow在pagehide之前发生,加record来判断
					if((++record)===2){
						$toNode.css({
							'pointer-events': 'auto',
							'position': 'static',
							'-webkit-transition': 'none'
						})
						$fromNode.css({
								'pointer-events': 'none',
								'display': 'none',
								'position': 'static',
								'-webkit-transition': 'none'
						})
						record= 0;
						me.currentChild.frame._pageshow(me.currentChild.hash);
						me._pagehide(me.currentChild.hash);
					}
				});

				handler=function(){
					$root.height(1000);
					scrollTo(0, 1);
					setTimeout(function(){
						me.trigger('hidebar', {          //hidebar事件
							height: innerHeight
						});
						var css= {'min-height': innerHeight};
		//				me.$docNodes.css(css);
						css.height='auto';
						$root.css(css);
					}, 0)  
				};
				
				$(window).on('load orientation' ,handler)  
				Backbone.history.start();
			},

			hideAddressBar: true,

			toFrame: function(frame){
				var curIndex= _.indexOf(this.children, frame),
					preIndex=  _.indexOf(this.children, this.previousChild),
					toNode= this.$docNodes.eq(curIndex),
					fromNode= this.$docNodes.eq(preIndex),
					transferTime= frame.frame.transferTime,
					dir= curIndex> preIndex ? 1 : -1, me= this,
					//记住page切走之前的位置
					pageY=  pageYOffset;
					 // 将页面嵌入
					if(!toNode[0].firstElementChild)
						toNode.append(frame.frame.$el);
					//显示出来
					toNode.css({'display':'block', 'position': 'absolute'});
					//如果没有上一个页面则直接显示
					if(!me.childChange){
						//同类page,不同子page,直接发生下级触发pageshow
						toNode.css({'position': 'static'});
						return this.currentChild.frame._pageshow(this.currentChild.hash);
					}
					//离开的页面记住距离顶部的位置
					this.previousChild.pageY= pageY;
					//把当前的page设置不占位
					fromNode.css({'position': 'absolute'});
					//要进入的page位置左边或者右边
					toNode.css({
//						"-webkit-transform": "translate3d("+(dir*100)+"%, "+(pageY- frame.pageY)+"px, 0px)",
						"-webkit-transform": "translate3d("+(dir*100)+"%, 0px, 0px)",
						marginTop: pageY- frame.pageY
					});
					//延时0s开始动画
					setTimeout(function(){
						var css= {
							"-webkit-transition-property": "-webkit-transform",
//							"-webkit-transform": "translate3d(0px, "+(pageY- frame.pageY)+"px, 0px)",
							"-webkit-transform": "translate3d(0px, 0px, 0px)",
							"-webkit-transition-duration": transferTime+"ms",
							"-webkit-transition-timing-function": "ease-out",
							marginTop: pageY- frame.pageY
						};
						//退出去
						toNode.css(css);
						css["-webkit-transform"]="translate3d("+(-dir*100)+"%, 0px, 0px)";
						//进入
						delete css.marginTop;
						fromNode.css(css);
						if(transferTime==0){
							toNode.trigger('webkitTransitionEnd');
							fromNode.trigger('webkitTransitionEnd');
						}
					}, $.browser.android ? 100 : 0)
			},

			setNodePos: function(ele, left, top){
				var style= ele.style;
				style.webkitTransform= style.webkitTransform.replace(/translate3d.+$/, 'translate3d\('+left+', '+top+', 0px)');
				return this;
			},
			
			/*
			 *给window控制器添加页面
			 */
			addChild: function(route, name, callback){
				this._super.apply(this, arguments);
				var child= this.children[0];
				var me= this;
				route= child.format;
				Backbone.history.route(route, function(fragment) {
					var args = router._extractParameters(route, fragment);
					//unshift一个空内容,因为没有父级别,判断child时,会使用hash[1]
					//
					(hash= fragment.split('/')).unshift('')
					me._pageshow.call(me,  hash);
					router.trigger('route', name, args);
				});
			},
			/*
			 *	执行子控制器的pageshow和hide
			 */
			_pageshow: function(hash){
				this._super.apply(this, arguments);
				//去除第一级别
				this.currentChild.hash= hash.slice(1);
					this.toFrame(this.currentChild);
			}
		}),

		iPage= TopWindow.extend({
			init: function(el, options){
				this._super(el, options); 
			},

			transferTime: 0,

			_pageshow: function(hash){
				this._super.apply(this, arguments);
				var childHash=hash.slice(1);
				if(this.currentChild){
					this.currentChild.hash= childHash;
					this.currentChild.frame._pageshow(childHash);
				}
			},

			_pagehide: function(hash){
				//睡眠
				this.sleep= true;	   
				this.pagehide(hash);
				this.trigger('pagehide', {hash: hash});
				this._super.apply(this, arguments);
			},

		});

	var Router= Backbone.Router.extend({}),
		router= new Router;
		iNative.iWindow=  iWindow;
		iNative.iPage=  iPage;
})(Backbone);

  html部分

 

<html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1"/>
        <meta name="format-detection" content="telephone=no" />
        <meta name="apple-mobile-web-app-capable" content="yes"/>
        <meta name="apple-mobile-web-app-status-bar-style" content="white" />
        <title>n.js by conan</title>
	<link rel="stylesheet" href="css/index.css" />
	<script src="script/underscore.js" type="text/javascript"></script>
	<script src="script/zepto.js" type="text/javascript"></script>
	<script src="script/backbone.js" type="text/javascript"></script>
	<script src="script/n.js" type="text/javascript"></script>
	<style type="text/css">
		 
		 #root>div #index_view{
			background: yellow;
			width: 100%;
			height:600px;
		}
		#channel_view{
			width: 100%;
			height:500px;;
			background: blue;
		}
		.title{
			height: 60px;
			line-height: 60px;
			text-align: center;
			background: #dedede;
			border-bottom: 2px #ddd solid;
		}
		.title>a{
			position: absolute;
			left: 0px;
			top: 10px;
			height: 40px;
			line-height: 40px;
			padding: 0 22px;
			font-size: 14px;
		}
		.content>a{
			display: block;
			height: 60px;
			margin: 200px 0;
			border-bottom: 2px #ddd solid;
			border-top: 2px #eee solid;
			text-align: center;
			line-height: 60px
		}
		.index,.channel,
		.tvshow,.movie{
			display: none;
		}
		hr{
			margin: 20px 0;
		}
	</style>
	</head>
	<body>
		<div id="root">
		</div>
		<div class="index">
			<h1 class="title">首页</h1>
			<div class="content">
				<a href="#channel/tvshow?zn=123">去看tvshow</a>
				<a href="#channel/movie?zn=456">去movie</a>
				<div class="status"></div>
			</div>
		</div>
		<div class="channel">
			<h1 class="title"><a href="#index?fr=channel">返回首页</a>频道页</h1>
			<div class="movie">
				这里是movie
				<hr />
				这里是movie
				<hr />
				这里是movie
				这里是movie
				<hr />
				这里是movie
				<hr />
				这里是movie
				这里是movie
				<hr />
				这里是movie
				<hr />
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				这里是movie
				这里是movie
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<hr />
				这里是movie
				<hr />
				这里是movie
				<hr />
				<div class="status"></div>
			</div>

			<div class="tvshow">
				这里是tvshow
				<hr />
				<hr />
				<div calss="asdf">
				这这里是tvshow</div>
				<hr />
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				<div calss="asdf">
				这这里是tvshow</div>
				这这里是tvshow</div>
				这里是tvshow
				<hr />
				这这里是tvshow
				<hr />
				这里是tvshow
				<hr />
				这里是tvshow
				<hr />
				这这里是tvshow
				<hr />
				这里是tvshow
				<hr />
				<hr />
				这里是tvshow
				<hr />
				这这里是tvshow
				<hr />
				这里是tvshow
				<hr />
				<hr />
				这里是tvshow
				<hr />
				这这里是tvshow
				<hr />
				这里是tvshow
				<hr />
				<div class="status"></div>
			</div>
		</div>
<script>
(function(window){
var index = iNative.iPage.extend({
		id: 'index_view',
		el: $('.index'),
		transferTime: 300,
		pageshow: function(hash){
			this.$('.status').html('big--'+hash);
			this.$el.show();
		},
		pagehide: function(hash){
		}
	}),

	Movie= iNative.iPage.extend({
			init: function(){
				this._super.apply(this, arguments);
			},
			id: 'movie',
			el: $('.movie'),
			pageshow: function(hash){
				console.log(this.id, 'pageshow');
				this.$('.status').html('little--'+hash);
				this.$el.show();
			},
			pagehide: function(hash){
				console.log(this.id, 'pagehide');
			}
	}),

	Tvshow= Movie.extend({
			id: 'tvshow',
			el: $('.tvshow')
	}),

	channel= index.extend({
		id: 'channel_view',
		el: $('.channel'),
		routes: {
			'movie*page': 'movie',
//			'tvshow*page': 'tvshow' 
		},
		movie: Movie,
//		tvshow: Tvshow,
		pageshow: function(hash){
			this._super.apply(this, arguments);
			this.addChild('tvshow*page',Tvshow);
			this.$el.show();
			if(this.previousChild) this.previousChild.frame.$el.hide();
		}
	}),

	iwindow= iNative.iWindow.extend({
		routes: {
			'channel*page': 'channel',
			'*page': 'index',
		},
		index: index,
		pageshow: function(){},
		pagehide: function(){},
		channel: channel,
	});
	app= new iwindow('#root')
})(window)
</script>

	</body>
</html>

  

by 一只柯楠

posted @ 2012-11-17 00:43  一只柯楠  阅读(4155)  评论(8编辑  收藏  举报