今天想說的是如何自己操刀做一個js的下拉刷新(js + h5 + css3)。既然是下拉,那么應用場景當然就是在手持設備上。在JavaScript的世界里,總是跟很多實用又華麗麗的效果接軌,這是一門很有色彩的編程語言。目前網絡上也有很多非常優秀的js滑動插件,比如iscroll(最開始我們就是用這款插件,真心很好用,而且解決了很多html的問題)。當然,我要講的完全沒辦法和iscroll媲美,僅僅是簡簡單單的一角而已,主要目的在於對這點小知識的總結和分享。
之前也有講過,移動設備上對CSS3和Html5的支持相當的不錯,而且使用Css3我們可以輕松的實現滑動效果,不僅不用擔心性能問題,而且效果上也是無可挑剔的。那么究竟需要做成什么樣的效果呢?
效果?嘿嘿,當然是類似淘寶那樣的。
(這里是用chrome模擬iphone5上的效果,關於如何模擬 這里 有講過的,不再贅述。)
當往下拖動頁面的時候出現紅色箭頭所指向的灰色區域,隨着往下拖動的節奏,橘黃色的圈不斷的被填滿,然后停頓幾秒頁面刷新(不太明白的話就自己試試吧)。 額額,可是樓上的圖片貌似不是很適合做分析。下面以一個極為粗糙的頁面做整個思路分析。
布局很重要!
整個過程中,布局是決定編碼難度及js代碼量的重要因素,科學的布局可以帶來飛一般的感覺(當然,不同的效果布局上有所不同也是正常的)。
查看粗制濫造的在線Demo 請戳這里
圖1 所展示的是整個需要滑動的頁面的布局結構。整個布局使用的是一個div(藍色框部分)里包含 加載數據的提示部分(簡稱“提示部分”)(紅色框部分) 和需要刷新的內容部分(簡稱“內容部分”)(文字部分)兩個div的結構。如果外層div向下移動,那么里面的提示部分和內容部分自然就跟着向下移動了(這樣是不是比同時使用js去控制兩個元素上下移動簡單多了?)。
圖2 所展示的是正常的內容頁面(滑動完成之后,也是滑動之前的效果),布局上主要是利用css3的transform屬性控制提示部分的隱藏和顯示。當translateY為負時,整個div向上移動(圖2的效果),為0時,整個提示部分就完全展示出來(圖1的效果)。
對於上面的描述如果沒看懂也別再看了(正在努力突破自我表達極限)。直接上代碼:

<html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>test</title> </head> <body style="background-color: beige;"> <div id="container" style="width:100%;border:solid 1px blue; transform:translate(0px,-61px)"> <div style="height:50px; line-height:50px; text-align:center; width:100%; border:solid 1px red;"> 努力加載中... </div> <div style="width:100%; line-height:30px;background-color:#F2F2F2; font-size:17px; font-family:'Adobe Garamond Pro'"> JavaScript一種直譯式腳本語言,是一種動態類型、弱類型、基於原型的語言,內置支持類型。它的解釋器被稱為JavaScript引擎,為瀏覽器的一部分,廣泛用於客戶端的腳本語言,最早是在HTML(標准通用標記語言下的一個應用)網頁上使用,用來給HTML網頁增加動態功能。 在1995年時,由Netscape公司的Brendan Eich,在網景導航者瀏覽器上首次設計實現而成。因為Netscape與Sun合作,Netscape管理層希望它外觀看起來像Java,因此取名為JavaScript。但實際上它的語法風格與Self及Scheme較為接近。[1] 為了取得技術優勢,微軟推出了JScript,CEnvi推出ScriptEase,與JavaScript同樣可在瀏覽器上運行。為了統一規格,因為JavaScript兼容於ECMA標准,因此也稱為ECMAScript。 JavaScript是一種屬於網絡的腳本語言,已經被廣泛用於Web應用開發,常用來為網頁添加各式各樣的動態功能,為用戶提供更流暢美觀的瀏覽效果。通常JavaScript腳本是通過嵌入在HTML中來實現自身的功能的。[3] 是一種解釋性腳本語言(代碼不進行預編譯)。[4] 主要用來向HTML(標准通用標記語言下的一個應用)頁面添加交互行為。[4] 可以直接嵌入HTML頁面,但寫成單獨的js文件有利於結構和行為的分離。[4] 跨平台特性,在絕大多數瀏覽器的支持下,可以在多種平台下運行(如Windows、Linux、Mac、Android、iOS等)。 Javascript腳本語言同其他語言一樣,有它自身的基本數據類型,表達式和算術運算符及程序的基本程序框架。Javascript提供了四種基本的數據類型和兩種特殊數據類型用來處理數據和文字。而變量提供存放信息的地方,表達式則可以完成較復雜的信息處理。[5] </div> </div> </body> </html> <!--JQuery是那么的好用,這種情況下怎么能沒有它呢!--> <script type="text/javascript" src="http://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script>
布局上大致就是這樣的結構,那么~
JavaScript該做什么呢?
1、根據滑動軌跡動態調整滑塊位置(transfrom=>translate);
2、根據滑動的距離判斷是否執行刷新(或數據加載)。
當然,如果滑動結束后使用ajax重新加載頁面數據,還將涉及到一個頁面向上滑動並隱藏提示部分的效果。
大致思路:
(前提條件:當前元素已滑動至頂部)
1、當鼠標左鍵按下(移動設備上的touchstart事件)的時候記錄下當前鼠標位置的 Y軸坐標;
2、當鼠標移動的時候(touchmove事件),記錄下鼠標的Y 軸坐標判斷滑動軌跡並進行相應的滑塊移動;
3、當鼠標左鍵松開(touchend事件)的時候,通過對比鼠標開始和結束的Y軸坐標的距離判斷是否應該刷新頁面(或重新加載數據)。
亮代碼,秀畫風:

/* *obj--滑動對象 *offset--滑動距離(當滑動距離大於等於offset時將調用callback) *callback--滑動完成后的回調函數 */ var slide = function (obj, offset, callback) { var start, end, isLock = false,//是否鎖定整個操作 isCanDo = false,//是否移動滑塊 isTouchPad = (/hp-tablet/gi).test(navigator.appVersion), hasTouch = 'ontouchstart' in window && !isTouchPad; //將對象轉換為jquery的對象 obj = $(obj); var objparent = obj.parent(); /*操作方法*/ var fn = { //移動容器 translate: function (diff) { obj.css({ "-webkit-transform": "translate(0," + diff + "px)", "transform": "translate(0," + diff + "px)" }); }, //設置效果時間 setTranslition: function (time) { obj.css({ "-webkit-transition": "all " + time + "s", "transition": "all " + time + "s" }); }, //返回到初始位置 back: function () { fn.translate(0 - offset); //標識操作完成 isLock = false; } }; //滑動開始 obj.bind("touchstart", function (e) { if (objparent.scrollTop() <= 0 && !isLock) { var even = typeof event == "undefined" ? e : event; //標識操作進行中 isLock = true; isCanDo = true; //保存當前鼠標Y坐標 start = hasTouch ? even.touches[0].pageY : even.pageY; //消除滑塊動畫時間 fn.setTranslition(0); } }); //滑動中 obj.bind("touchmove", function (e) { if (objparent.scrollTop() <= 0 && isCanDo) { var even = typeof event == "undefined" ? e : event; //保存當前鼠標Y坐標 end = hasTouch ? even.touches[0].pageY : even.pageY; if (start < end) { even.preventDefault(); //消除滑塊動畫時間 fn.setTranslition(0); //移動滑塊 fn.translate(end - start - offset); } } }); //滑動結束 obj.bind("touchend", function (e) { if (isCanDo) { isCanDo = false; //判斷滑動距離是否大於等於指定值 if (end - start >= offset) { //設置滑塊回彈時間 fn.setTranslition(1); //保留提示部分 fn.translate(0); //執行回調函數 if (typeof callback == "function") { callback.call(fn, e); } } else { //返回初始狀態 fn.back(); } } }); }
代碼分析:
1、參數:obj,要滑動的對象;offset,提示部分的transform的值(代碼中是 transform:translate(0px,-61px) ,那么這里就是61);callback,回調函數,在下拉完成后調用的函數(頁面刷新或數據加載)。
2、為什么是transform不是margin?
因為transform不會引起重繪,相比margin更流暢,性能更好。但是transfrom有個比較好玩的地方,如果translateY的值為負數(當前元素上移xx像素)下方元素不會跟着上移(margin會上移),在這點上它和margin是有區別的。 注意,這里的-webkit-transform的存在是有必要的,因為有些瀏覽器識別不了transform,比如微信內置瀏覽(我的手機上是這樣的)。為了兼容性,多扣幾個字母是值得的。
3、關於transition設置為0s。
為什么要在touchstart的時候把transition的值設置為0秒呢?transition的作用是為元素屬性的變化添加過渡效果,例如一個框變大,我們設置為transition為1s,那么這個框就是在1s內變大到指定大小。第一個參數表示設置過渡效果的 CSS 屬性的名稱(如:margin,transform;all表示所有),第二個參數表示過渡的時間。 代碼中設置transition的目的是在於滑動結束后(手指離開屏幕)為滑塊回彈添加過渡效果,這樣看上去就不會那么突兀。當然,這個過渡效果同樣會應用到數據加載完成后提示部分的隱藏上。設置為0是為了取消在滑動過程中的滑塊過渡效果,我們手指往下滑動的時候,滑塊會跟這向下移動,這樣就有了滑動滑塊的效果。如果這個時候不取消transition就會出現滑塊抖動的效果(嘿嘿,有興趣的話可以試試這種感覺。)。整個過程中transition是相當重要的。
4、關於isLock和isCanDo.
這兩個變量的作用在於防止二次滑動,在第一次滑動后數據加載完成之前不允許有第二次的滑動。當滑動開始的時候講isLock和isCanDo都設置為True,表示允許后面兩個事件里的代碼可以正常運行,當滑動結束后isCanDo設置為false表示在isLock被設置為True之前(整個操作完成之前)所有的事件代碼均不可用(不執行下拉數據加載等相關動作)。
5、如何使用?
這個比較就簡單,但也比較重要。

$(function () { slide("#container", 61, function (e) { var that = this; setTimeout(function () { that.back.call(); }, 2000); }); });
代碼中的setTimeout是用於模擬ajax加載數據的效果,加載數據這部分就沒有再單獨寫過了。JavaScript的回調函數是用着最順手的特性之一。這里在數據加載完成后需要調用一個back方法,這個方法目的就是重置slide里的各種狀態。關於這種傳來傳去的方式給人的感覺有點像做地下工作,不太容易被發現,可暫時也沒有想到更好的解決方案。
最后:
如果各位有什么好的方法或想法,歡迎大家在樓下@我☺。