最近有個任務,做一個非常小的h5的應用,只有2屏,需要做橫向的全屏滑動切換和一些簡單的動畫效果,之前做這種東西用的是fullpage.js和jquery,效能不是很好,於是就想自己動手弄一個簡單的東西來實現。最後我用zepto + hammer.js 和輪播的方式解決了這個問題,效果還不錯,整個頁面不開啟Gzip時所有資源請求的資料大小為200KB左右。這篇文章總結下這個方法的實現思路。
效果演示(程式碼下載):
1. 實現要點
1)滑屏借鑑bootstrap的carousel外掛,不過完全沒有它那個複雜,只需要借鑑它的輪播實現思路即可;
2)滑屏切換的觸發,跟PC不一樣,PC通常都是通過元素的點選回撥來觸發,對於滑屏的頁面,完全可以利用window的hashchange事件來處理,這樣只要通過超連結設定錨點或者通過js改變location.hash就能觸發切換;
3)考慮到移動還得支援手勢操作,可以使用hammer.js這個手勢庫,API非常簡單易用;
4)動畫效果可以用animate.css,不過不用把它所有的程式碼都弄到程式碼裡,只需要拷貝需要的動畫效果相關的程式碼即可;
5)替代jquery,首選zepto;
6)滑屏效果使用transition動畫,為了能夠響應動畫結束的回撥,可以考慮使用transition.js,這個也是Bootstrap提供的工具,不過它預設只能跟jquery使用,要對它稍微改變一下才能跟zepto聯合使用。
這些要點說的比較粗糙,後面的內容會一一詳細介紹。
2. html結構
空的滑屏頁的html結構是這樣的:
1 2 3 4 5 6 7 8 |
<div id="container" class="container"> <section id="page-1" class="page page--1"> </section> <section id="page-2" class="page page--2"> </section> <section id="page-3" class="page page--3"> </section> </div> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
html, body { height: 100%; -webkit-tap-highlight-color: transparent; } .container, .page { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; } .page { overflow: hidden; display: none; -webkit-transition: -webkit-transform .4s ease; transition: transform .4s ease; -webkit-backface-visibility: hidden; backface-visibility: hidden; } |
.container與.page初始化的時候採用絕對定位,全屏佈局。每一個section.page代表一頁,並且預設不顯示,所有頁的定位都相同,也就是說如果所有頁都顯示的話,這些頁會重疊在一塊。
demo頁的html結構是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div id="container" class="container"> <section id="page-1" class="page page--1"> <div class="page__jump"><a href="#page-2" title="">下一頁</a></div> <p class="page__num animated">1</p> </section> <section id="page-2" class="page page--2"> <div class="page__jump"><a href="#page-1" title="">上一頁</a><a href="#page-3" title="">下一頁</a></div> <p class="page__num animated">2</p> </section> <section id="page-3" class="page page--3"> <div class="page__jump"><a href="#page-2" title="">上一頁</a></div> <p class="page__num animated">3</p> </section> </div> |
demo相關的css就不展示了。其中animated是應用animate.css需要的,animate.css是一個動畫庫,github上有。
3. 滑屏切換的實現思路
滑屏切換就是通過js控制2個要滑動的頁增加和刪除以下定義的這一些css類實現的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
.page.page--active, .page.page--prev, .page.page--next { display: block; } .page.page--next, .page.page--active.page--active-right { -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } .page.page--prev, .page.page--active.page--active-left { -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } .page.page--next.page--next-left, .page.page--prev.page--prev-right, .page.page--active { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } |
.page–active表示當前顯示的頁,頁面初始化後,通過以下js呼叫,給第一頁加上.page—active:
1 2 3 4 5 6 7 |
var $activePage; //初始化顯示第一頁 (function () { $activePage = $('#page-1'); $activePage.addClass('page--active'); })(); |
這樣頁面預設就顯示了第一頁。以向左滑屏說明這些css的使用原理:
第一步,找到下一頁的section,新增page–next類,將它定位當前頁的右邊,為滑屏做準備;
第二步,找到當前頁的section,給它新增page–active-left,由於這個類改變了translate3D屬性的值,所以當前頁會往左滑動一屏;
在第二步的同時,給下一頁的section,新增page–next-left,由於這個類改變了translate3D屬性的值,所以下一頁會往左滑動一屏;
第三步,在當前頁跟下一頁滑屏動畫結束後,找到原來的當前頁,移除掉page–active和page–active-left類;
在第三步的同時,找到下一頁,移除掉page–next和page–next-left類,新增page–active。
gif圖說明如下:
向右滑屏原理類似:
第一步,找到上一頁的section,新增page–prev類,將它定位當前頁的左邊,為滑屏做準備;
第二步,找到當前頁的section,給它新增page–active-right,由於這個類改變了translate3D屬性的值,所以當前頁會往右滑動一屏;
在第二步的同時,給上一頁的section,新增page–prev-right,由於這個類改變了translate3D屬性的值,所以上一頁會往右滑動一屏;
第三步,在當前頁跟上一頁滑屏動畫結束後,找到原來的當前頁,移除掉page–active和page–active-right類;
在第三步的同時,找到上一頁,移除掉page–prev和page–prev-right類,新增page–active。
綜合以上實現原理,封裝成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 |
var TRANSITION_DURATION = 400, sliding = false; function getSlideType($targetPage) { var activePageId = $activePage.attr('id'), targetPageId = $targetPage.attr('id'); return activePageId < targetPageId ? 'next' : activePageId == targetPageId ? '' : 'prev'; } function slide(targetPageId) { var $targetPage = $('#' + targetPageId); if (!$targetPage.length || sliding) return; var slideType = getSlideType($targetPage), direction = slideType == 'next' ? 'left' : 'right'; if (slideType == '') return; sliding = true; $targetPage.addClass('page--' + slideType); $targetPage[0].offsetWidth; $activePage.addClass('page--active-' + direction); $targetPage.addClass('page--' + slideType + '-' + direction); $activePage .one($.transitionEnd.end, function () { $targetPage.removeClass(['page--' + slideType, 'page--' + slideType + '-' + direction].join(' ')).addClass('page--active'); $activePage.removeClass(['page--active', 'page--active-' + direction].join(' ')); $activePage = $targetPage; sliding = false; }) .emulateTransitionEnd(TRANSITION_DURATION); } |
由於$activePage在頁面初始化的時候預設指定為第一頁,在每次滑屏結束後都會更新成最新的當前頁,所以呼叫的時候只要把目標頁的ID傳給slide函式即可。以上程式碼可能會有疑問的是:
1)$targetPage[0].offsetWidth的作用,這個程式碼用來觸發瀏覽器的重繪,因為目標頁原來是display: none的,如果不觸發重繪的話,下一步新增css類後將看不到動畫效果;
2)$.transitionEnd.end以及emulateTransitionEnd的作用,這個在下一部分說明。
4. 瀏覽器css動畫結束的回撥及模擬
bootstrap提供了一個工具,transition.js,用來判斷瀏覽器是否支援css動畫回撥事件,以及在瀏覽器沒有在動畫結束後自動觸發回撥的特殊情況下通過模擬的方式來手動觸發回撥,原先這個工具只能配合jquery使用,為了在zepto中使用,必須稍微改變一下,下面就是改變之後的程式碼:
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 |
(function(){ var transition = $.transitionEnd = { end: (function () { var el = document.createElement('transitionEnd'), transEndEventNames = { WebkitTransition: 'webkitTransitionEnd', MozTransition: 'transitionend', OTransition: 'oTransitionEnd otransitionend', transition: 'transitionend' }; for (var name in transEndEventNames) { if (el.style[name] !== undefined) { return transEndEventNames[name]; } } return false; })() }; $.fn.emulateTransitionEnd = function (duration) { var called = false, _this = this, callback = function () { if (!called) $(_this).trigger(transition.end); }; $(this).one(transition.end, function () { called = true; }); setTimeout(callback, duration); return this; }; })(); |
$.transitionEnd.end表示當前瀏覽器支援的動畫結束事件的名稱。$.fn.emulateTransitionEnd是一個擴充套件了Zepto原型的一個方法,傳入一個動畫的過渡時間,當這個時間段過完之後,如果瀏覽器沒有自動觸發回撥事件,called就始終是false,setTimeout會導致callback被呼叫,然後callback內部就會手動觸發動畫結束的回撥。為什麼要通過這個方式來模擬動畫結束,是因為瀏覽器即使支援動畫結束事件的回撥,但是有些時候並不會觸發這個事件,或者在動畫結束後不能立即觸發,影響回撥的準確性。傳入的duration應該與執行動畫的元素,在css上設定的transtion-duration相同,注意以下程式碼中標黃的部分:
1 2 3 4 5 6 7 8 9 |
var TRANSITION_DURATION = 400; $activePage .one($.transitionEnd.end, function () { $targetPage.removeClass(['page--' + slideType, 'page--' + slideType + '-' + direction].join(' ')).addClass('page--active'); $activePage.removeClass(['page--active', 'page--active-' + direction].join(' ')); $activePage = $targetPage; sliding = false; }) .emulateTransitionEnd(TRANSITION_DURATION); |
1 2 3 4 5 6 7 8 |
.page { overflow: hidden; display: none; -webkit-transition: -webkit-transform .4s ease; transition: transform .4s ease; -webkit-backface-visibility: hidden; backface-visibility: hidden; } |
5. hashchange事件
PC端滑屏都是給元素新增點選事件觸發的,移動端可以利用window的hashchange事件:
1 2 3 4 5 6 7 |
$(window).on('hashchange', function (e) { var hash = location.hash; if (!hash) hash = '#page-1'; slide(hash.substring(1)); }); location.hash = '#page-1'; |
hashchange事件,在js程式碼中通過改變loaction.hash或者是點選a href=”#page-2″ title=””>下一頁a>這樣的超連結時,都會觸發,所以只要在這個事件的回撥去做滑屏切換即可。這樣那些上一頁和下一頁的連結元素都不用加事件了。
6. hammer.js使用簡介
hammer.js是一個手勢庫,支援常用的手勢操作,使用簡單,引入它的js之後,通過以下的方式來支援手勢滑屏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//初始化手勢滑動 var container = document.getElementById('container'), mc = new Hammer.Manager(container), Swipe = new Hammer.Swipe(); mc.add(Swipe); mc.on('swipeleft', function (e) { swipteTo('next', e); }); mc.on('swiperight', function (e) { swipteTo('prev', e); }); function swipteTo(slideType, e) { var $targetPage = $activePage[slideType]('.page'); $targetPage.length & (location.hash = '#' + $targetPage.attr('id')); } |
把整個container元素作為滑屏的stage,監聽到swipeleft事件,就表示向左滑,頁面應該顯示下一頁;監聽到swiperight事件,就表示向右滑,頁面應該顯示下一頁。
7. 結束語
animate.css的使用就不詳細介紹了,比較簡單,這是它的github地址:https://github.com/daneden/animate.css,是一個非常好用的動畫庫。本文把最近的一點工作經驗記錄了下來,技術上的東西,有的時候一些文字不能完全講的清楚,所以我只能儘自己的能力去把一些問題講地稍微細緻一點,說的不對和有問題的儘管在評論區與我說明,我會認真檢視,另外我自己對移動端這一塊入門不深,您有更好的見解,歡迎與我們一起分享。謝謝您的閱讀,馬上就新年,祝您猴年大吉!