原生實(shí)現(xiàn)一個(gè)手勢視差翻頁器

前言

原文:原生實(shí)現(xiàn)一個(gè)手勢視差翻頁器 #147

一句話需求

移動(dòng)端實(shí)現(xiàn)一個(gè)輪播器,在輪播器上層有一個(gè)靜止圖層汤求,不會(huì)隨著輪播器翻頁而偏移卖丸。
以下是兩個(gè)已經(jīng)實(shí)踐的兩個(gè)項(xiàng)目:

[Github]洋满,歡迎各方Gay Man來Fork與StarP⒑铡!几迄!

?

需求分析

以上需求可以拆分成兩個(gè)小的需求:

  • 實(shí)現(xiàn)一個(gè)輪播器表蝙;
  • 做個(gè)靜止圖層。

問題

輪播器作為一個(gè)圖層乓旗,靜止圖層是一個(gè)。
那么那個(gè)圖層在上集索,那個(gè)在下屿愚?

  1. 輪播器在上汇跨,則靜止圖層被輪播器遮蓋;
  2. 靜止圖層在上妆距,則綁定在輪播器上的手勢事件由于被靜止圖層在上而無法觸發(fā)穷遂。

再細(xì)分需求

為了顯示靜止圖層,那么靜止圖層一定是要在上的娱据。那么就必須解決第2個(gè)問題蚪黑,怎么觸發(fā)輪播器的手勢。
我們可以通過監(jiān)聽靜止圖層或者兩圖層容器的手勢中剩,然后映射到輪播器忌穿。

?

實(shí)現(xiàn)

1 實(shí)現(xiàn)一個(gè)輪播器

由于網(wǎng)上的輪播器插件的都是封裝好的,觸發(fā)事件比較麻煩结啼,因此選擇自己使用原生js實(shí)現(xiàn)一個(gè)掠剑。
實(shí)現(xiàn)輪播器的方案有很多中,比如改變translate屬性的郊愧,也有先實(shí)現(xiàn)一個(gè)scroll-view朴译,然后改變scroll-viewscrollLeft。我選用后者(單純是因?yàn)槲覜]有這樣做過属铁,而且好像很有趣)眠寿。

那么就先實(shí)現(xiàn)一個(gè)scroll-view

一般scroll-view都是縱向的焦蘑,根據(jù)以上需求盯拱,我們就需要實(shí)現(xiàn)一個(gè)橫向的scroll-view,具體實(shí)現(xiàn)如下

<div id="app">
  <dl class="story">
    <dt></dt><dt></dt>
    <dt></dt><dt></dt><dt></dt>
    <dt></dt><dt></dt>
  </dl>
</div>
*{ margin: 0;padding: 0; }
html,body{ height: 100%; }
::-webkit-scrollbar{ display: none; }

#app{ height: 100%;position: relative; }

#app > .story{ position: absolute;top: 0;right: 0;bottom: 0;left: 0; }

#app > .story{ 
  z-index: 1000; border: 1px solid;
  overflow-x: auto;overflow-y: hidden; white-space: nowrap;font-size: 0;letter-spacing: 0;
  /* translateZ是是為了開啟硬件加速 */
  -webkit-transform: translateZ(0);
  -moz-transform: translateZ(0);
  -ms-transform: translateZ(0);
  -o-transform: translateZ(0);
  transform: translateZ(0);
}

.story > dt{ height: 100%;width: 100%;display: inline-block;vertical-align: top;position: relative; }
.story > dt:nth-of-type(1){ background-color: red; }
.story > dt:nth-of-type(2){ background-color: orange; }
.story > dt:nth-of-type(3){ background-color: yellow; }
.story > dt:nth-of-type(4){ background-color: green; }
.story > dt:nth-of-type(5){ background-color: cyan; }
.story > dt:nth-of-type(6){ background-color: blue; }
.story > dt:nth-of-type(7){ background-color: purple; }

核心css為

overflow-x: auto;overflow-y: hidden; white-space: nowrap;

禁用塊級元素的換行喇肋,依次達(dá)到橫排的效果坟乾。
[查看源碼:jsbin]
[查看源碼:jsfiddle]
(ps:要看效果的gay man請點(diǎn)擊[查看源碼:jsbin],并使用移動(dòng)設(shè)備模式查看蝶防, 下同)

?

2 實(shí)現(xiàn)靜止圖層并監(jiān)聽其上的手勢

先向#app中添加靜止圖層

<div id="app">
  <dl class="mash">
    <img src="http://ohi69gup6.bkt.clouddn.com/005TGG6vly1fes9jc0kk0g30b40b40tv.gif">
  </dl>
  ...
</div>

然后甚侣,來分析一下輪播器的功能

  1. 向左,向右滑動(dòng)翻頁间学;
  2. 輪播器卡片手勢跟隨殷费。

第1點(diǎn)的實(shí)現(xiàn)思路

獲取手指 “觸碰屏幕那刻” 和 “離開屏幕那刻” 的位置,根據(jù)以上兩個(gè)位置判斷翻頁方向低葫,要獲取這兩位置详羡,就需要分別注冊touchstarttouchend事件,通過事件的回調(diào)參數(shù)( event )獲取嘿悬。

// 觸碰屏幕那刻橫向位置
var touchStartX = e.changedTouches[0].pageX;
// 離開屏幕那刻橫向位置
var touchEndX = e.changedTouches[0].pageX;

判斷滑動(dòng)方向

var isNext = touchEndX - touchStartX < 0;

知道翻頁方向就可以進(jìn)行翻頁
翻頁如上文所說实柠,通過改變卡片容器的scrollLeft。這里的容器是#app > .story善涨。

document.querySelector("#app > .story").scrollLeft

想要實(shí)現(xiàn)翻頁窒盐,還必須知道兩個(gè)屬性:

  1. 每頁寬度:可以通過story.clientWidth獲炔菰颉;
  2. 當(dāng)前頁:使用全局變量記錄當(dāng)前是第幾頁蟹漓。

具體實(shí)現(xiàn)如下:

var touchStartX = 0;
var currentPage = 1;
var mash = document.querySelector("#app > .mash");
var story = document.querySelector("#app > .story");
var screenWidth = story.clientWidth;

mash && mash.addEventListener('touchstart', function(e){
  var touch = e.changedTouches[0];
 touchStartX = touch.pageX;
}, false);

mash && mash.addEventListener('touchend', function(e){
  var touch = e.changedTouches[0];
  var touchEndX = touch.pageX;
  var isNext = touchEndX - touchStartX < 0;
  var oldCurrentPage = currentPage;
  var maxPage = story.children.length;
  var minPage = 1;

  currentPage = isNext ? currentPage + 1 : currentPage - 1;
  currentPage = currentPage > maxPage ? maxPage : currentPage;
  currentPage = currentPage < minPage ? minPage : currentPage;

  var targetScrollLeft = (currentPage - 1) * screenWidth;
  
  story.scrollLeft = targetScrollLeft;
}, false);

以上代碼基本實(shí)現(xiàn)翻頁炕横,但是僅僅是通過每次 “增加/減少 一個(gè)screenWidth的距離” 達(dá)到翻頁的目的,是不夠的葡粒,因?yàn)槟銜?huì)發(fā)現(xiàn)翻頁效果很生硬的份殿,僅僅只有一幀。

下面讓我們把翻頁效果做得更加順滑嗽交。
怎么做卿嘲,使用定時(shí)器(setTimeout)?
不轮纫!不腔寡!不!當(dāng)然不會(huì)掌唾!
我們使用requestAnimationFrame放前,至于為什么使用requestAnimationFrame而不使用setTimeout?請自行Google糯彬。接著凭语,讓我們先實(shí)現(xiàn)一個(gè)動(dòng)畫函數(shù)

function animate(callback) {
  var _animate = function(){
    var isValid = callback && callback();
    isValid && requestAnimationFrame(_animate);
  };

  requestAnimationFrame(_animate);
}

ok,然后要做的就是將它運(yùn)用到翻頁的代碼中

// 設(shè)置每幀改變scrollLeft距離
var step = 30;
// ...

mash && mash.addEventListener('touchend', function(e){
  // ...
  animate(function() {
    var scrollLeft = story.scrollLeft;

    scrollLeft = isNext ? scrollLeft + step : scrollLeft - step;

    if(isNext && scrollLeft > targetScrollLeft) {
      story.scrollLeft = targetScrollLeft;
      return false;
    } else if(!isNext && scrollLeft < targetScrollLeft){
      story.scrollLeft = targetScrollLeft;
      return false;
    } else {
      story.scrollLeft = scrollLeft;
      return true;
    }
  });
}, false);

[查看源碼:jsbin]
[查看源碼:jsfiddle]

第2點(diǎn)的實(shí)現(xiàn)思路

第二點(diǎn)需要實(shí)現(xiàn)的功能是:輪播器卡片手勢跟隨撩扒。
假如你有試過以上代碼實(shí)現(xiàn)的輪播器似扔,你會(huì)發(fā)現(xiàn)一個(gè)問題:手指在未離開屏幕前,無論手指怎么滑動(dòng)都不會(huì)有任何響應(yīng)搓谆,這明顯不是我們想要的炒辉。標(biāo)準(zhǔn)的輪播器應(yīng)該是:當(dāng)前卡片應(yīng)該對手勢做出響應(yīng),卡片跟隨手指移動(dòng)泉手。

要實(shí)現(xiàn)以上效果黔寇,就需要監(jiān)聽手指未離開屏幕的前的動(dòng)作,注冊touchmove事件斩萌,獲取手指每次移動(dòng)的位置缝裤!

mash && mash.addEventListener('touchmove', function(e){
  var touch = e.changedTouches[0];
  var moveingfPageX = touch.pageX;
  var distance = -(moveingfPageX - touchStartX);
  var currentPageScrollLeft = (currentPage - 1) * screenWidth;

  story.scrollLeft = currentPageScrollLeft + distance;
}, false);

思路也是比較簡單的,監(jiān)聽手指所在位置與初始位置touchStartX的距離颊郎,然后動(dòng)態(tài)地改變story.scrollLeft憋飞。

另外,現(xiàn)在已經(jīng)實(shí)現(xiàn)卡片的手勢跟隨姆吭,隨之又可能有這么一種情況:
用戶已經(jīng)滑動(dòng)卡片榛做,但是在滑動(dòng)后用戶不想翻頁了,轉(zhuǎn)而想繼續(xù)留在當(dāng)前頁,即 “后悔功能”瘤睹。然后再看看當(dāng)前我們的邏輯:

// ...
var isNext = touchEndX - touchStartX < 0;
// ...
currentPage = isNext ? currentPage + 1 : currentPage - 1;
currentPage = currentPage > maxPage ? maxPage : currentPage;
currentPage = currentPage < minPage ? minPage : currentPage;

以上邏輯是:如果不是上一頁就是下一頁升敲。并沒有實(shí)現(xiàn)后悔功能。

為實(shí)現(xiàn)后悔功能轰传,我做如下修改

// 設(shè)置最小有效翻頁距離(在手指離開屏幕時(shí)所偏移的橫向距離),換言之瘪撇,偏移距離小于30就觸發(fā)后悔功能
var validFlipDistance = 30;

//  ...

mash && mash.addEventListener('touchend', function(e){
  //  ...
  var isValid = Math.abs(touchEndX - touchStartX) >= validFlipDistance;
  //  ...
  if(isValid) {
    currentPage = isNext ? currentPage + 1 : currentPage - 1;
    currentPage = currentPage > maxPage ? maxPage : currentPage;
    currentPage = currentPage < minPage ? minPage : currentPage;
  } else {
    // 觸發(fā)后悔功能获茬,改變滑動(dòng)方向,由animate函數(shù)將卡片反向滑回初始位置
    isNext = !isNext;
  }
});

[查看源碼: jsbin]
[查看源碼: jsfiddle]

?

寫在最后

我知道自己的實(shí)現(xiàn)并不是最佳實(shí)踐倔既,但是我想說的是:造輪子真爽恕曲!

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市渤涌,隨后出現(xiàn)的幾起案子佩谣,更是在濱河造成了極大的恐慌,老刑警劉巖实蓬,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件茸俭,死亡現(xiàn)場離奇詭異,居然都是意外死亡安皱,警方通過查閱死者的電腦和手機(jī)调鬓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酌伊,“玉大人腾窝,你說我怎么就攤上這事【幼” “怎么了虹脯?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奏候。 經(jīng)常有香客問我循集,道長,這世上最難降的妖魔是什么鼻由? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任暇榴,我火速辦了婚禮,結(jié)果婚禮上蕉世,老公的妹妹穿的比我還像新娘蔼紧。我一直安慰自己,他們只是感情好狠轻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布奸例。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪查吊。 梳的紋絲不亂的頭發(fā)上谐区,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音逻卖,去河邊找鬼宋列。 笑死,一個(gè)胖子當(dāng)著我的面吹牛评也,可吹牛的內(nèi)容都是我干的炼杖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盗迟,長吁一口氣:“原來是場噩夢啊……” “哼坤邪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起罚缕,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤艇纺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后邮弹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黔衡,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年肠鲫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了员帮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡导饲,死狀恐怖捞高,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渣锦,我是刑警寧澤硝岗,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站袋毙,受9級特大地震影響型檀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜听盖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一胀溺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧皆看,春花似錦仓坞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春嫉称,著一層夾襖步出監(jiān)牢的瞬間侦镇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工织阅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壳繁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓荔棉,卻偏偏與公主長得像氮趋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子江耀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件诉植、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,105評論 4 62
  • 原文鏈接:https://github.com/opendigg/awesome-github-android-u...
    IM魂影閱讀 32,940評論 6 472
  • 【說明:本文是從Principle官網(wǎng)翻譯過來的祥国,因個(gè)人能力和水平有限,部分術(shù)語可能不準(zhǔn)確晾腔,對軟件功能的理解也可能...
    shea閱讀 42,358評論 23 117
  • 葉子被車輪碾過舌稀, 有簌簌的響聲, 像極了你向我奔來的腳步聲灼擂。
    林春夏閱讀 274評論 0 0
  • 每天看了很多文章壁查,理財(cái)?shù)模芾頃r(shí)間的剔应,每次看了之后都覺得給自己打了滿滿的雞血睡腿,然并卵。還是沒什么進(jìn)步峻贮,也沒什么措施...
    日之夏閱讀 206評論 0 0