原生 JS 實現移動端 Touch 滑動反彈

原生 JS 實現移動端 Touch 滑動反彈

什麼是 Touch滑動?就是相似於 PC端的滾動事件,可是在移動端是沒有滾動事件的,因此就要用到 Touch事件結合 js去實現,效果以下:javascript

img

1. 準備工做

什麼是移動端的 Touch事件?在移動端 Touch事件能夠細分紅三種,分別是: touchstarttouchmovetouchend,而且 touch事件必需要用 addEventListener去監聽。css

  • touchStart當手指觸碰到屏幕的時候觸發
  • touchmove當手指在屏幕上不斷移動的時候觸發
  • touchend當手指離開屏幕的時候觸發

Touch 事件觸發的 Event 對象html

// 手指觸碰到屏幕時觸發
element.addEventListener('touchstart', function (e) {
	console.log(e); // 打印的事件對象
})

changedTouchestargetTouchestouches都是僞數組,裏面裝的是手指列表。java

三種返回對象的區別segmentfault

其實這三種返回的對象,都是表示用戶觸摸事件時的手指信息,之因此是一個僞數組,是由於有可能出現多指同時觸摸,可是在實際工做中通常不去考慮多指的狀況。而它們惟一的區別就是在 touchstarttouchmove事件的時候, changedTouchestargetTouchestouches都能獲取到手指的信息,可是在 touchend事件的時候, targetTouchestouches對象是不能返回離開屏幕時的手指信息,只有 changedTouches對象能返回。數組

有哪些手指信息?瀏覽器

咱們能夠看下上面的圖片,在 changedTouche[0]中,有些值:ide

clientX:74    // 觸摸點相對於瀏覽器的 viewport 左邊緣的 x 座標,不會包括左邊的滾動距離。
clientY:73    // 觸摸點相對於瀏覽器的 viewport 上邊緣的 Y 座標,不會包括上邊的滾動距離。
screenX:2202  // 觸摸點相對於屏幕左邊緣的 x 座標。
screenY:327   // 觸摸點相對於屏幕上邊緣的 Y 座標。
pageX:65      // 觸摸點相對於 document 的左邊緣的 x 座標,包括左邊的滾動距離
pageY:18      // 觸摸點相對於 document 的上邊緣的 Y 座標,包括左邊的滾動距離

2. 基本結構

此案例模擬的是移動端的一種滑動菜單效果。ui

HTML部分spa

<aside class="main">
  <div class="draw" id="draw">
    <ul>
      <li style="background:orange">列表一</li>
      <li style="background:yellowgreen">列表二</li>
      <li style="background:yellow">列表三</li>
      <li style="background:cyan">列表四</li>
      <li style="background:orangered">列表五</li>
      <li style="background:pink">列表六</li>
      <li style="background:red">列表七</li>
      <li style="background:purple">列表八</li>
      <li style="background:violet">列表九</li>
      <li style="background:brown">列表十</li></ul>
  </div>
</aside>

css部分

在列表的父盒子上設定一個 overflow:hidden屬性,使超出盒子部分的列表暫時隱藏掉,後面會經過 js去實現滑動。

/* 樣式初始化 */* {
	margin:0;
	padding:0;
}
html,body {
	width:100%;
}
aside {
	height:100%;
	width:100%;
}
/* 列表的父盒子,限制寬高 *//* 注意設置overflow:hidden;
	樣式後,超出這個盒子的ul將不會顯示 */.draw {
	width:60px;
	height:500px;
	border:2px solid #ccc;
	overflow:hidden;
	position:fixed;
	left:10px;
	top:50%;
	transform:translateY(-50%);
}
/* li 設置了浮動, 因此 ul 要清除浮動 */ul:after {
	content:"";
	display:block;
	visibility:hidden;
	height:0;
	clear:both;
}
ul {
	zoom:1;
}
li {
	list-style:none;
	float:left;
	width:60px;
	height:60px;
	line-height:60px;
	text-align:center;
}

效果圖

img

3. 首次滑動

手指觸摸到列表向下滑動的時候,列表應該跟着向下滑動,當手指離開屏幕的時候,列表應該停在滑動的位置。這裏就會用到上面準備階段的知識點了,不明白的能夠參考上面的概念。

實現原理

  1. touchstart的時候,獲取手指觸摸的落點 A,經過這個點對象裏面的 clientY屬性,獲取距離頂部可視區的距離;
  2. touchmove的時候,獲取手指的點 B,一樣的獲取移動時距離頂部可視區的距離;
  3. touchmove的時候,還要作另外一件事情,就是獲取兩點的差值( B.clientY-A.clientY),將這個差值動態賦值給 ulul只須要設置向 Y軸方向偏移這個距離,就能實現列表隨手指滑動

先來張示意圖,怎麼經過 js 讓列表滑動起來

示例代碼

var draw = document.querySelector('#draw');
var ul = draw.children[0];
// touchstart 時,記錄手指在 Y 軸上的落點距離可視頂部距離
var startY = 0;
ul.addEventListener('touchstart', function (e) {
	startY = e.changedTouches[0].clientY;
})
// touchmove 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchmove', function (e) {  // 獲取差值  
	var dy = e.changedTouches[0].clientY - startY;  // 設置ul在Y軸上的偏移
	ul.style.transform = 'translateY(' + dy + 'px)';
})

效果圖

img

4. 再次滑動

上面的效果圖,細心的朋友可能已經發現了問題,在第一次的時候,獲得了咱們想要的效果,可是在第二次的時候,咱們繼續向下移動了一段距離,可是 ul並無接着第一次的位置繼續向下,而是瞬間跳了上去。

問題分析

雖然第二次是繼續向下移動了一段距離,可是觸摸結束後,最終是將此時的差值,從新賦值給了 ulY軸偏移,因此視覺上「跳了上去」。

解決方法:

每一次滑動結束以後,都應該記錄下這次滑動的距離,與以前的進行累加,待下一次滑動的時候, ulY軸的偏移值應該是以前的距離加上本次滑動的距離。

  • 新增 touchend事件,在該事件裏一樣的能夠獲取到本次滑動的距離,將它與上一次的距離相加,賦值給一個全局變量;
  • touchmove事件裏有點小改動,就是在給 ul設置偏移值的時候,除了本次滑動的差值還要加上這個上一次的值;

示意圖:

示例代碼

var draw = document.querySelector('#draw');
var ul = draw.children[0];
var startY = 0 // 剛觸碰到屏幕的時的手指信息
var centerY = 0 // 用來記錄每次觸摸時上一次的偏移距離
// touchstart 時,記錄手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchstart', function (e) {  
    startY = e.changedTouches[0].clientY;
})
// touchmove 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchmove', function (e) {  // 獲取差值
    var dy = e.changedTouches[0].clientY - startY;  // 上次的滑動距離加上本次的滑動距離  
    var tempY = centerY + dy;  // 設置 ul 在 Y 軸上的偏移  
    ul.style.transform = 'translateY(' + tempY + 'px)';
})
// touchend 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchend', function (e) {  // 獲取差值  
    var dy = e.changedTouches[0].clientY - startY;  // 記錄移動的距離  
    centerY = centerY + dy;
})

效果圖

img

5. 限制滑動區間

到上面一步,咱們已經能夠實現列表的滑動了,可是也存在一個問題,就是向上或者向下的時候沒有限制,上下能夠無限的滑動,甚至再用點力,就看不到列表了。爲了美觀和實用,這樣確定不行的,須要給它設定一個區間,設定向上或者向下最多隻能留白多少。

限制向下滑動最大區間

設定向下最大區間的值比較簡單,直接設定一個值,當上一次滑動的距離加上本次滑動的距離大於這個值的時候,就不讓它再繼續往下滑了,讓他直接等於這個設定的值。

示例代碼

var maxDown = 50; // 設定一個最大向下滑動的距離
// touchmove 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchmove', function (e) {  // 獲取差值  
    var dy = e.changedTouches[0].clientY - startY;
  // 上次的滑動距離加上本次的滑動距離  
    var tempY = centerY + dy;
  // 當上次滑動的距離加上本次滑動的距離 大於 設定的最大向下距離的時候  
    if (tempY > maxDown) {    // 直接讓偏移的值 等於這個設定值    
        tempY = maxDown;  
    }
  // 設置 ul 在 Y 軸上的偏移 
    ul.style.transform = 'translateY(' + tempY + 'px)';
})

限制向上滑動最大區間

向上滑動時,當 ul的底部距盒子底部的距離大於設定值的時候,不讓其繼續向上滑動,關鍵是這個值怎麼去判斷?

求出向上滑動最大值

注意:由於 ul是向上滑動的,因此求得的距離前面要加上一個負號( -

示例代碼

// 設定一個最大向下滑動的距離
var maxDown = 50;
// 求得一個最大向上滑動的距離
var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown);
// touchmove 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchmove', function (e) {  // 獲取差值  
    var dy = e.changedTouches[0].clientY - startY;
  // 上次的滑動距離加上本次的滑動距離  
    var tempY = centerY + dy;
  // 當上次滑動的距離加上本次滑動的距離 大於 設定的最大向下距離的時候  
    if (tempY > maxDown) {   
        tempY = maxDown;
    }  // 當上次滑動的距離加上本次滑動的距離 小於 設定的最大向上距離的時候   
    else if (tempY < maxUp) {    // 直接讓偏移的值 等於這個設定值    
        tempY = maxUp;  
    }
  // 設置 ul 在 Y 軸上的偏移  
    ul.style.transform = 'translateY(' + tempY + 'px)';
})

效果圖

img

認真觀察上圖,雖然成功的設置了最大滑動區間,可是你有沒有發現,一直往一個方向滑動的時候,雖然列表不會繼續往下滑動,可是接着往相反方向滑動的時候,感受列表滑不動,須要滑一段距離後,列表纔會跟着走,這是爲何呢?由於滑動的過程 centerY是一直變的,列表雖然視覺上不動了,可是在 touchend事件的時候,它的 centerY值一直在累加。解決方法請往下看。

6. 設定反彈區間

「滑動反彈」,這裏的反彈是本篇文章的最後一步,上面說到的問題,就在這裏解決。由於每一次觸發 touchend事件的時候, centerY值就累加一次,因此須要在 touchend事件裏作判斷。咱們設定一個反彈區間,就是當 centerY的值大於或者小於某個值的時候,讓它觸發反彈。

設定向上反彈值

向上的值比較簡單,設置成「 0」。爲何是「 0」呢?咱們限定只要手指離開時,上一次的滑動距離加上本次的距離 >0的時候,就讓它觸發反彈,而且反彈回 0點的位置,也就是兩次滑動的距離和 =0

示例代碼

// 向上反彈
var maxUpBounce = 0;
// touchend 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchend', function (e) {  // 獲取差值  
    var dy = e.changedTouches[0].clientY - startY;  // 記錄移動的距離  
    centerY = centerY + dy;
  // 兩次滑動的距離 大於 設定的 向上 反彈值時  
    if (centerY > maxUpBounce) {    // 讓兩次滑動的距離 等於 設置的值    
        centerY = maxUpBounce;    // 添加過渡    
        ul.style.transition = 'transform .5s';    
        ul.style.transform = 'translateY(' + centerY + 'px)';  
    }
})

設定向下反彈值

向下的值其實跟以前求滑動區間差很少,咱們參考下圖,當列表向上滑動,滑動到列表底部的時候,只要此時再向上滑動,就讓它向下反彈。向下反彈值就是 -(ul.offsetHeight-draw.offsetHeight),只要滑動的差值小於這個設定值,就讓它向下反彈,而且反彈回設定值的位置。

示例代碼

// 向上反彈值
var maxUpBounce = 0;  // 向下反彈值
var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight);
// touchend 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離
ul.addEventListener('touchend', function (e) {  // 獲取差值  
    var dy = e.changedTouches[0].clientY - startY;  // 記錄移動的距離  
    centerY = centerY + dy;
  // 兩次滑動的距離 大於 設定的 向上 反彈值時  
    if (centerY > maxUpBounce) {    // 讓兩次滑動的距離 等於 設置的值    
        centerY = maxUpBounce;    // 添加過渡    
        ul.style.transition = 'transform .5s';    
        ul.style.transform = 'translateY(' + centerY + 'px)';  
    }  // 兩次滑動的距離 小於 設定的 向下 反彈值時  
    else if (centerY < maxDownBounce) {    // 讓兩次滑動的距離 等於 設置的值    
        centerY = maxDownBounce;    // 添加過渡    
        ul.style.transition = 'transform .5s';    
        ul.style.transform = 'translateY(' + centerY + 'px)';  
    }
})

注意:在 touchend事件的時候,給列表添加了 transition屬性纔會有反彈的效果,可是,下一次滑動的時候, touchmove事件的時候,這個屬性還存在,因此就會出現滑動的時候有頓挫感,因此在 touchmove事件的時候,一進來就清一下過渡 ul.style.transition='none';

完成後效果圖

img

7. 完整代碼

<!DOCTYPE html>
<html lang="en">
 <head> 
  <meta charset="UTF-8" /> 
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" /> 
  <meta http-equiv="X-UA-Compatible" content="ie=edge" /> 
  <title>移動端 Touch 滑動反彈</title> 
  <style>    /* 樣式初始化 */    
       * {      
          margin: 0;      
          padding: 0;    
      	}
        html,body {      
            width: 100%;
        }
        aside {      
            height: 100%;      
            width: 100%;    
        }
        /* 列表的父盒子,限制寬高 */    
		/* 注意設置overflow: hidden;樣式後,超出這個盒子的ul將不會顯示 */    
	.draw {      
            width: 60px;      
            height: 500px;      
            border: 2px solid #ccc;      
            overflow: hidden;      
            position: fixed;      
            left: 10px;      
            top: 50%;      
            transform: translateY(-50%);    
      	}
    /* li 設置了浮動, 因此 ul 要清除浮動 */    
    ul:after {      
        content: "";      
        display: block;      
        visibility: hidden;      
        height: 0;      
        clear: both;    
    }
    ul {      
        zoom: 1;    
    }
    li {      
        list-style: none;      
        float: left;      
        width: 60px;      
        height: 60px;      
        line-height: 60px;      
        text-align: center;    
    }  
</style>
 </head>
 <body>
  <aside class="main"> 
   <div class="draw" id="draw"> 
    <ul> 
     <li style="background:orange">列表一</li> 
     <li style="background:yellowgreen">列表二</li> 
     <li style="background:yellow">列表三</li> 
     <li style="background:cyan">列表四</li> 
     <li style="background:orangered">列表五</li> 
     <li style="background:pink">列表六</li> 
     <li style="background:red">列表七</li> 
     <li style="background:purple">列表八</li> 
     <li style="background:violet">列表九</li> 
     <li style="background:brown">列表十</li> 
    </ul> 
   </div>
  </aside>  
<script>
    var draw = document.querySelector('#draw');
    var ul = draw.children[0];
    var startY = 0; // 剛觸碰到屏幕的時的手指信息
    var centerY = 0; // 用來記錄每次觸摸時上一次的偏移距離
    var maxDown = 50; // 設定一個最大向下滑動的距離
    var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown); // 求得一個最大向上滑動的距離
    var maxUpBounce = 0; // 向上反彈值
    var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight); // 向下反彈值
    // touchstart 時,記錄手指在 Y 軸上的落點距離可視頂部距離
    ul.addEventListener('touchstart', function (e) {      
        startY = e.changedTouches[0].clientY;    
    })
    // touchmove 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離    
    ul.addEventListener('touchmove', function (e) {      // 清除過渡      
        ul.style.transition = 'none';      // 獲取差值      
        var dy = e.changedTouches[0].clientY - startY;
    // 上次的滑動距離加上本次的滑動距離      
        var tempY = centerY + dy;
    // 當上次滑動的距離加上本次滑動的距離 大於 設定的最大向下距離的時候      
        if (tempY > maxDown) {        
            tempY = maxDown;      
        }      // 當上次滑動的距離加上本次滑動的距離 小於 設定的最大向上距離的時候       
        else if (tempY < maxUp) {        
            tempY = maxUp;      
        }
    // 設置 ul 在 Y 軸上的偏移      
        ul.style.transform = 'translateY(' + tempY + 'px)';    
    })
    // touchend 時,記錄此時手指在 Y 軸上的落點距離可視頂部距離    
    ul.addEventListener('touchend', function (e) {      // 獲取差值      
        var dy = e.changedTouches[0].clientY - startY;      // 記錄移動的距離      
        centerY = centerY + dy;
    // 兩次滑動的距離 大於 設定的 向上 反彈值時      
        if (centerY > maxUpBounce) {        // 讓兩次滑動的距離 等於 設置的值        
            centerY = maxUpBounce;        // 添加過渡        
            ul.style.transition = 'transform .5s';        
            ul.style.transform = 'translateY(' + centerY + 'px)';      
        }      // 兩次滑動的距離 小於 設定的 向下 反彈值時      
        else if (centerY < maxDownBounce) {        // 讓兩次滑動的距離 等於 設置的值        
            centerY = maxDownBounce;        // 添加過渡        
            ul.style.transition = 'transform .5s';        
            ul.style.transform = 'translateY(' + centerY + 'px)';      
        }    
    })  
</script>
 </body>
</html>

轉自做者: Levi丶

相關文章
相關標籤/搜索