最近Path這個(gè)應(yīng)用很火爆,網(wǎng)上也出現(xiàn)了不少仿Path菜單的項(xiàng)目。即使在原生APP里邊,Path的效果也是非常贊的。
我突然想,Web APP是不是也能做出類似Path那樣的效果呢?于是就有了OPath這個(gè)項(xiàng)目,它的客戶端部分是完全用PhoneGap+HTML5開發(fā)的。
坦白的講,OPath比Path差得不是一點(diǎn)半點(diǎn),但是比很多國產(chǎn)的原生應(yīng)用體驗(yàn)要好,下邊是演示視頻。
看完視頻如果你對效果還滿意的話,請接著往下看。我會和大家分享如何做一個(gè)這樣的應(yīng)用,包括整個(gè)前端(HTML5)和后端(PHP)。
這個(gè)項(xiàng)目也是在MIT協(xié)議下完全開源的(同樣包括前端和后端),項(xiàng)目鏈接在文章最末尾。
PS:我只在iPod Touch4的iOS5系統(tǒng)上進(jìn)行了測試,其他平臺可能存在兼容性問題,需要自行測試和修復(fù)。
框架選擇
PhoneGap就不用說了,有了它才能打包。我們要選的里邊的前端框架。雖然之前我已經(jīng)做了一個(gè)基于Jquery Mobile的Tab類模板,但是很明顯,Path并沒有采用Tab方式的菜單。
加上Path的控件都是自己的風(fēng)格,所以自己渲染樣式是逃不掉的,于是最后我選擇了采用 Mobile-boilerplate + iScroll4 來做這個(gè)項(xiàng)目。
Mobile-boilerplate
Mobile-boilerplate 是一個(gè)移動設(shè)備用的HTML5空白模板,它處理掉了非常多的兼容細(xì)節(jié),比如viewport之類的。想了解詳情的同學(xué)可以去看Mobile-boilerplate里邊的注釋,寫得非常詳細(xì),還有相關(guān)issue的鏈接。
下載Mobile-boilerplate將解壓出來的目錄作為我們項(xiàng)目的根目錄。Mobile-boilerplate已經(jīng)包含了js和css目錄,其中js下的libs里邊有JQuery。
我首先在Mobile-boilerplate的模板基礎(chǔ)上做了下登錄頁面,完成后的效果是這樣的:
這個(gè)界面很簡單,直接用CSS來實(shí)現(xiàn)就可以了,遵守Mobile-boilerplate的結(jié)構(gòu),在css/style.css中部200行左右的位置開始寫入自己的css。
API接口的用戶認(rèn)證
接下來我們說說API方式用戶認(rèn)證的實(shí)現(xiàn)。在OPath項(xiàng)目中,我們采用用戶名+密碼換token,以后操作通過token鑒權(quán)的方式。
因?yàn)檫@種方式實(shí)現(xiàn)起來很方便。做PHP的同學(xué)都知道,PHP的Session機(jī)制是通過PHP SESSION ID來標(biāo)示用戶的,一般情況下這個(gè)標(biāo)示通過Cookie存儲在瀏覽器中。
我們的思路就是,將這個(gè)SESSION ID直接作為token就好啦。于是我們實(shí)現(xiàn)了get_token接口:
最核心的邏輯就這幾行
session_start();
$token = session_id(); // 將Session id作為token
$_SESSION['token'] = $token; // 在Session中存儲用戶信息,供以后的操作認(rèn)證使用。
$_SESSION['uid'] = $user['id'];
$_SESSION['name'] = $user['name'];
$_SESSION['email'] = $user['email'];
$_SESSION['level'] = $user['level'];
token在生成后,通過json格式返回給客戶端。
客戶端發(fā)送Ajax請求和解析參數(shù)
現(xiàn)在回到客戶端這邊來,當(dāng)用戶在登錄頁面填好賬號后,我們需要將這些數(shù)據(jù)發(fā)送到服務(wù)器端,換取token。使用JQuery,這個(gè)很簡單:
我們用 jQuery.parseJSON 解析返回的JSON數(shù)據(jù),然后在登錄正確后,將賬號和token保存到本地。這里的kset其實(shí)是我寫的一個(gè)快捷函數(shù),它只是簡單封裝了下HTML5的LocalStorage。
function kset( key , value )
{
window.localStorage.setItem( key , value );
}function kget( key )
{
return window.localStorage.getItem( key );
}function kremove( key )
{
window.localStorage.removeItem( key );
}
LocalStorage里邊的數(shù)據(jù)是持久化的,在應(yīng)用被關(guān)閉后依舊存在。順便說下,在Chrome和Safari的調(diào)試工具里邊,Resource的Tab里邊可以直接看到當(dāng)前應(yīng)用的LocalStorage還有IndexedDB的數(shù)據(jù),不用去找其他的工具來查看這些值。這在調(diào)試應(yīng)用的時(shí)候非常方便。
由于開發(fā)的應(yīng)用是HTML5的,我首先會實(shí)現(xiàn)標(biāo)準(zhǔn)瀏覽器支持的部分,用Safari來進(jìn)行調(diào)試;在最后才實(shí)現(xiàn)需要PhoneGap的部分,進(jìn)行真機(jī)調(diào)試,這樣可以節(jié)省很多調(diào)試時(shí)間。
Path主頁面
Path的主頁面很帥,實(shí)現(xiàn)細(xì)節(jié)也很多,我挑重點(diǎn)說。先放一張做完后的效果:
整體的布局上,其實(shí)我們可以直接沿用iScroll4的Demo,頂欄固定,將原來的Footer換成那個(gè)加號按鈕就可以了。加號按鈕的實(shí)現(xiàn)網(wǎng)上有CSS版本的,但是在Android上會出現(xiàn)嚴(yán)重的毛邊,所以我直接用圖片代替了。(Android上CSS圓角毛邊的問題非常煩人,從這個(gè)地方可以一眼認(rèn)出是否是WebAPP;iOS上則非常干凈。)考慮到iPod Touch(我主要用這個(gè))的杯具性能,我只簡單做了個(gè)位置移動效果,覺得細(xì)節(jié)不夠的同學(xué)可以自己加旋轉(zhuǎn)和彈簧效果,用JQuery很容易做。說實(shí)話我覺得原版Path的那個(gè)加號按鈕展開后很難按準(zhǔn) T__T
頁面上方的Profile Picture部分放到iScroll的wrapper內(nèi),scroll最上方;下邊的【加載更多】按鈕,放到wrapper內(nèi),scroll最下方。均通過CSS指定固定高。
其他的布局細(xì)節(jié)可以查看path.html和style.css源文件。
Retina屏幕下的圖片模糊問題
在iScroll的基礎(chǔ)上,我很快就完成了主頁面的布局,但是當(dāng)我放到頭像和圖片后,杯具發(fā)生了!在Android上看的時(shí)候很正常,但是在Touch上圖片會變得非常模糊。
按Mobile-boilerplate的viewport設(shè)定,整個(gè)頁面寬度應(yīng)該會變成 設(shè)備寬,對Touch來說就是320px。
很快我就意識到這應(yīng)該是Retina屏幕帶來的問題,因?yàn)镽etina屏將標(biāo)準(zhǔn)屏幕一個(gè)像素改用4個(gè)像素顯示,所以圖像和周圍的矢量圖對比起來就模糊了。
而在Android上都采用一個(gè)像素顯示,所以沒有這個(gè)問題。
Google了下,網(wǎng)上的解決方案是這樣的:
對于直接的圖片應(yīng)用,比如說
<img src=”image.png”/>
采用Retina屏幕的iOS設(shè)備會去找同目錄下的 image@2x.png進(jìn)行顯示。
對于通過CSS引用的圖片,比如說
<div id=”avatar”></div>
則需要使用link標(biāo)簽按條件載入專用的CSS。
<link rel="stylesheet" media="only screen and (-webkit-min-device-pixel-ratio: 2)" type="text/css" href="../iphone4.css" />
我測試了下,沒有成功,更主要的還是覺得這個(gè)方案不爽,額外CSS什么的弱爆了。然后自己試出來了一個(gè)方案:
因?yàn)槟:脑硪呀?jīng)很清楚了,那么只要朝這個(gè)方向去想就行。
對于直接引用圖片的情況,很容易想到解決方案:原本100*100的圖片,我做成200*200,然后在img標(biāo)簽中指定高和寬為100*100。這樣在Retina屏幕上可以按像素點(diǎn)進(jìn)行顯示,在其他屏幕的設(shè)備上,瀏覽器會自己先縮放后顯示,測試效果很清晰。
通過CSS引用的情況比較麻煩,我睡了一覺才想出來,如果div#avatar要顯示100*100的背景,那么將它的高和寬指定為200*200,配上200*200的背景圖片,最后,Zoom:0.5。
其他頁面需要注意的地方
其他頁面基本上都是體力勞動了,Path Feed列表渲染時(shí)有兩個(gè)需要注意的細(xì)節(jié):
一是我們用的模板本身是用<script>標(biāo)簽的,所以模板里邊就不能再有這個(gè)標(biāo)簽了。在顯示每條Feed時(shí),需要顯示對應(yīng)的用戶頭像,這個(gè)頭像當(dāng)做背景顯示的,由于不能用script標(biāo)簽,只好把url先放到標(biāo)簽里,渲染完后統(tǒng)一處理。
二是當(dāng)Feed里邊有圖片的時(shí)候,iScroll的高度會受影響。需要在圖片加載完全后,再重新調(diào)用iScroll的refresh方法。在Feed中圖片過多時(shí),F(xiàn)eed頁面會卡,這個(gè)問題可以通過串行載入圖片資源的方式來解決,在當(dāng)前這個(gè)版本里邊,沒有實(shí)現(xiàn)。
Thought頁面這部分沒有太多問題,采用了之前Tab模板的Div切換方式,從而逼近原生應(yīng)用的切換速度。
通過PhoneGap實(shí)現(xiàn)拍照和頭像設(shè)置
頭像和拍照使用PhoneGap調(diào)用了本地設(shè)備,按PhoneGap的說明,加載PhoneGap的JS文件并在頁面初始化時(shí)注冊好事件。
在點(diǎn)擊了拍照按鈕或者頭像按鈕后,調(diào)用攝像頭,并通過PhoneGap提供的的文件傳輸對象FileTransfer進(jìn)行上傳。FileTransfer可以模擬一個(gè)完全的HTTP請求,所以服務(wù)器端并不需要特殊出來,按帶file標(biāo)簽的標(biāo)準(zhǔn)From請求處理即可。
需要額外處理的是,iOS拍攝的圖片方向很可能不對。這是因?yàn)閕OS本地的相冊在顯示圖片時(shí),根據(jù)拍攝時(shí)的方向自動做了調(diào)整。要在服務(wù)器端正確的顯示圖像,必須根據(jù)圖片中的Exif信息調(diào)整方向。在將家里的小浪擺好Pose并通過各個(gè)方向的拍攝后,我寫好了調(diào)整方向的函數(shù)。
所有的代碼,我已經(jīng)放到google code上,大家可以下載。這些代碼是MIT協(xié)議,可以隨意商用。
開發(fā)以外
兼容性
由于各個(gè)平臺對CSS和HTML5 的支持差異很大,所以很難在全部平臺做到完美,像之前提到過的,Android的CSS圓角毛邊問題,Div切換時(shí)部分圖層不定期隱藏的問題;另外PhoneGap還有各種BUG和問題,比如iOS應(yīng)用從后臺呼出時(shí)有短暫的白屏閃爍問題;比如OPath里邊我使用了1.2版本,這個(gè)版本在iOS下拍照正常,但是在Android下呼叫不出攝像頭,換成1.0版本就可以,這說明PhoneGap在平臺兼容性上問題依然不少。
個(gè)人感覺,在現(xiàn)階段,一個(gè)PhoneGap應(yīng)用要想做到完美的體驗(yàn)并在各個(gè)平臺保持體驗(yàn)一致,難度非常大。不過如果能忍受一些小細(xì)節(jié),或者能做好優(yōu)雅降級的話,PhoneGap應(yīng)用是能超過很多原生應(yīng)用的。
代碼安全性
采用PhoneGap打包的應(yīng)用,不管是APK還是IPA,只要將擴(kuò)展名改為zip,解壓后在www目錄就可以得到這個(gè)應(yīng)用的全部源代碼。
這使得盜版成本非常的低,必要的時(shí)候需要對js進(jìn)行混淆。最可靠的方式是將部分核心邏輯放到云端,通過api使用。