原生JS實(shí)現(xiàn)移動(dòng)端web輪播圖詳解(結(jié)合Tween算法造輪子)
前言
相信大家應(yīng)該都知道,移動(dòng)端的輪播圖是我們比較常見的需求, 我們最快的實(shí)現(xiàn)方式往往是 使用第三方的代碼, 例如 swiper , 但當(dāng)遇到一些比較復(fù)雜的輪播圖需求時(shí), 往往是束手無策,不知道怎么改.
所以我們要嘗試去自己造一些輪子, 以適應(yīng)各種復(fù)雜多變的需求; 另外一點(diǎn), 自己寫的代碼如果有bug是很容易修復(fù)的, 對(duì)自身的提高也很大.
在沒有閱讀swiper源碼的過程下,我嘗試自己實(shí)現(xiàn)一個(gè)簡(jiǎn)易而不失實(shí)用的移動(dòng)端輪播圖, 經(jīng)過幾個(gè)小時(shí)的思考和實(shí)踐終于還是實(shí)現(xiàn)了(如圖):
實(shí)現(xiàn)移動(dòng)端的輪播圖要比pc復(fù)雜一些,主要表現(xiàn)在以下幾個(gè)方面:
1.輪播圖要適應(yīng)不同寬度/dpr的屏幕
2.需要使用 touch相關(guān)的事件
3.不同機(jī)型對(duì) touch事件支持的不太一樣,可能會(huì)有一些兼容性問題
4.手指移動(dòng)圖片一部分距離,剩下的距離需要自動(dòng)完成
5.自動(dòng)完成距離需要有 ease 時(shí)間曲線
但編程解決問題的思路都是差不多的,
我們?cè)谑褂幂啿D的時(shí)候可以仔細(xì)觀察,通過現(xiàn)象看到本質(zhì):
- 我們?cè)谑褂幂啿D的時(shí)候可以仔細(xì)觀察,通過現(xiàn)象看到本質(zhì):
- 手指放在圖片上, 手指向左或者向右移動(dòng), 圖片也隨之移動(dòng);
- 手指移動(dòng)的距離少時(shí),圖片自動(dòng)復(fù)原位置;手指移動(dòng)的距離多時(shí),自動(dòng)切換到下一張;
- 手指向左或者向右移動(dòng)的快時(shí),會(huì)切換到下一張;
- 圖片輪播是無限循環(huán)的, 我們需要采用 3 1 2 3 1的方式來實(shí)現(xiàn), 即 N+2張圖來實(shí)現(xiàn)N張圖的無限循環(huán)輪播
我們通過分析現(xiàn)象,可以提出一個(gè)基本實(shí)現(xiàn)方案:
1. 手指觸摸事件可以通過 touchstart touchmove touchend 3個(gè)事件來實(shí)現(xiàn)
2.在手指 touchstart的時(shí)候我們需要記錄 手指的x坐標(biāo), 可以使用 touch的pageX屬性; 還有 這個(gè)時(shí)間點(diǎn),
3.手指touchmove的時(shí)候我們也需要記錄pageX,并且記錄累計(jì)移動(dòng)的距離 moveX
4.手指離開的時(shí)候,記錄時(shí)間點(diǎn), 根據(jù)前兩步計(jì)算的 x方向移動(dòng)的距離,時(shí)間點(diǎn)之差
5.通過比較x方向移動(dòng)距離來判斷移動(dòng)方向, 以及是否應(yīng)該切換到下一張圖; 根據(jù)時(shí)間判斷用戶是否進(jìn)行了左右掃動(dòng)的操作
6.移動(dòng)圖片可以使用 translate3d來實(shí)現(xiàn),開啟硬件加速
7.移動(dòng)一段距離需要 easeOut效果,我們可以使用 Tween算法中的easeOut來實(shí)現(xiàn)我們每次移動(dòng)的距離; 當(dāng)然也可以使用 js設(shè)置 transition動(dòng)畫
實(shí)現(xiàn)源碼(僅供參考):
head頭部樣式
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=.5,maximum-scale=.5"> <title>移動(dòng)端輪播圖</title> <style> * { box-sizing: border-box; margin: 0; padding: 0 } .banner { overflow: hidden; width: 100%; height: 300px } .banner .img-wrap { position: relative; height: 100% } .banner img { display: block; position: absolute; top: 0; width: 100%; height: 100% } </style> </head>
HTML結(jié)構(gòu)
<div class="banner"> <div class="img-wrap" id="imgWrap"> <img src="images/banner_3.jpg" data-index="-1"> <img src="images/banner_1.jpg" data-index="0"> <img src="images/banner_2.jpg" data-index="1"> <img src="images/banner_3.jpg" data-index="2"> <img src="images/banner_1.jpg" data-index="3"> </div> </div>
JS代碼1, easeOut動(dòng)畫式移動(dòng),
這里的 HTMLElement.prototype.tweenTranslateXAnimate ,是給所有的HTML元素類擴(kuò)展的tweenTranslateXAnimate方法
移動(dòng)一段距離我們需要使用定時(shí)器來幫助我們完成,這個(gè)重復(fù)的操作
<script> HTMLElement.prototype.tweenTranslateXAnimate = function (start, end, callback) { var duration = 50; var t = 0; var vv = end - start; var Tween = { Quad: { easeOut: function (t, b, c, d) { return -c * (t /= d) * (t - 2) + b; } } }; this.timer = setInterval(function () { var dis = start + Tween.Quad.easeOut(++t, 0, vv, duration); this.style.transform = 'translate3d(' + dis + 'px, 0, 0)'; if (vv > 0 && parseInt(this.style.transform.slice(12)) >= end) { this.style.transform = 'translate3d(' + parseInt(dis) + 'px, 0, 0)'; clearInterval(this.timer); callback && callback(); } if (vv < 0 && parseInt(this.style.transform.slice(12)) <= end) { this.style.transform = 'translate3d(' + parseInt(dis) + 'px, 0, 0)'; clearInterval(this.timer); callback && callback(); } }.bind(this), 4); } </script>
touch事件部分
<script> ~function () { var lastPX = 0; // 上一次觸摸的位置x坐標(biāo), 需要計(jì)算出手指每次移動(dòng)的一點(diǎn)點(diǎn)距離 var movex = 0; // 記錄手指move的x方向值 var imgWrap = document.getElementById('imgWrap'); var startX = 0; // 開始觸摸時(shí)手指所在x坐標(biāo) var endX = 0; // 觸摸結(jié)束時(shí)手指所在的x坐標(biāo)位置 var imgSize = imgWrap.children.length - 2; // 圖片個(gè)數(shù) var t1 = 0; // 記錄開始觸摸的時(shí)刻 var t2 = 0; // 記錄結(jié)束觸摸的時(shí)刻 var width = window.innerWidth; // 當(dāng)前窗口寬度 var nodeList = document.querySelectorAll('#imgWrap img'); // 所有輪播圖節(jié)點(diǎn)數(shù)組 NodeList // 給圖片設(shè)置合適的left值, 注意 querySelectorAll返回 NodeList, 具有 forEach方法 nodeList.forEach(function (node, index) { node.style.left = (index - 1) * width + 'px'; }); /** * 移動(dòng)圖片到當(dāng)前的 tIndex索引所在位置 * @param {number} tIndex 要顯示的圖片的索引 * */ function toIndex(tIndex) { var dis = -(tIndex * width); var start = parseInt(imgWrap.style.transform.slice(12)); // 動(dòng)畫移動(dòng) imgWrap.tweenTranslateXAnimate(start, dis, function () { setTimeout(function () { movex = dis; if (tIndex === imgSize) { imgWrap.style.transform = 'translate3d(0, 0, 0)'; movex = 0; } if (tIndex === -1) { imgWrap.style.transform = 'translate3d(' + width * (1 - imgSize) + 'px, 0, 0)'; movex = -width * (imgSize - 1); } }, 0); }); } /** * 處理各種觸摸事件 ,包括 touchstart, touchend, touchmove, touchcancel * @param {Event} evt 回調(diào)函數(shù)中系統(tǒng)傳回的 js 事件對(duì)象 * */ function touch(evt) { var touch = evt.targetTouches[0]; var tar = evt.target; var index = parseInt(tar.getAttribute('data-index')); if (evt.type === 'touchmove') { var di = parseInt(touch.pageX - lastPX); endX = touch.pageX; movex += di; imgWrap.style.webkitTransform = 'translate3d(' + movex + 'px, 0, 0)'; lastPX = touch.pageX; } if (evt.type === 'touchend') { var minus = endX - startX; t2 = new Date().getTime() - t1; if (Math.abs(minus) > 0) { // 有拖動(dòng)操作 if (Math.abs(minus) < width * 0.4 && t2 > 500) { // 拖動(dòng)距離不夠,返回! toIndex(index); } else { // 超過一半,看方向 console.log(minus); if (Math.abs(minus) < 20) { console.log('距離很短' + minus); toIndex(index); return; } if (minus < 0) { // endX < startX,向左滑動(dòng),是下一張 toIndex(index + 1) } else { // endX > startX ,向右滑動(dòng), 是上一張 toIndex(index - 1) } } } else { //沒有拖動(dòng)操作 } } if (evt.type === 'touchstart') { lastPX = touch.pageX; startX = lastPX; endX = startX; t1 = new Date().getTime(); } return false; } imgWrap.addEventListener('touchstart', touch, false); imgWrap.addEventListener('touchmove', touch, false); imgWrap.addEventListener('touchend', touch, false); imgWrap.addEventListener('touchcancel', touch, false); }(); </script>
在觸摸事件中最關(guān)鍵的參數(shù)是 pageX參數(shù), 記錄x的位置.
當(dāng)然這只是一個(gè)demo,還需要進(jìn)一步的優(yōu)化和封裝, 以便于我們用在真實(shí)的項(xiàng)目.
本demo僅僅是提供了一個(gè)解決問題的思路, 有了這個(gè)思路,相信各種復(fù)雜的需求也得以解決...
本文中使用的 tween算法來實(shí)現(xiàn) ease-out效果 ,也可以使用 transtion動(dòng)畫實(shí)現(xiàn), 代碼更加簡(jiǎn)潔,參見輪播圖優(yōu)化篇: http://www.dbjr.com.cn/article/123304.htm
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
html中通過JS獲取JSON數(shù)據(jù)并加載的方法
本篇內(nèi)容主要給大家講了如何通過javascript解析JSON并得到數(shù)據(jù)后添加到HTML中的方法,需要的朋友參考下。2017-11-11基于d3.js/neovis.js/neod3.js實(shí)現(xiàn)鏈接neo4j圖形數(shù)據(jù)庫(kù)的圖像化顯示功能
neovis.js?由vis.js支持的圖形可視化以及來自Neo4j的數(shù)據(jù)。這篇文章主要介紹了基于d3.js/neovis.js/neod3.js實(shí)現(xiàn)鏈接neo4j圖形數(shù)據(jù)庫(kù)的圖像化顯示功能,需要的朋友可以參考下2022-02-02完美解決spring websocket自動(dòng)斷開連接再創(chuàng)建引發(fā)的問題
下面小編就為大家?guī)硪黄昝澜鉀Qspring websocket自動(dòng)斷開連接再創(chuàng)建引發(fā)的問題。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-03-03javascript實(shí)現(xiàn)的textarea運(yùn)行框效果代碼 不用指定id批量指定
今天在寫一個(gè)網(wǎng)頁的時(shí)候用到了N多嵌套在textarea標(biāo)簽里的代碼,定義雙擊運(yùn)行其內(nèi)的代碼段。但是每次創(chuàng)建一個(gè)這樣的可運(yùn)行的實(shí)例都要給textarea元素自定義一個(gè)id值和寫入雙擊事件,好不麻煩。2009-12-12微信小程序?qū)崿F(xiàn)點(diǎn)擊按鈕修改文字大小功能【附demo源碼下載】
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)點(diǎn)擊按鈕修改文字大小功能,涉及微信小程序事件綁定及setData動(dòng)態(tài)修改Page頁面data數(shù)據(jù),進(jìn)而控制頁面元素屬性動(dòng)態(tài)改變的相關(guān)操作技巧,需要的朋友可以參考下2017-12-12不同js異步函數(shù)同步的實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄煌琷s異步函數(shù)同步的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05js實(shí)現(xiàn)移動(dòng)端輪播圖滑動(dòng)切換
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)移動(dòng)端輪播圖滑動(dòng)切換,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12JavaScript Scoping and Hoisting 翻譯
希望這篇文章能夠給JavaScript程序員最容易困惑的部分一些啟示。我盡力寫的全面,以免引起更多的困惑。如果我寫錯(cuò)了或是漏掉了某些重要的東西,請(qǐng)一定讓我知道2012-07-07js實(shí)現(xiàn)刷新頁面后回到記錄時(shí)滾動(dòng)條的位置【兩種方案可選】
本文主要介紹了頁面的div中有滾動(dòng)條,js實(shí)現(xiàn)刷新頁面后回到記錄時(shí)滾動(dòng)條的位置的兩種方案,需要的朋友可以看下2016-12-12