爲了模擬原生應用的觸控效果,大量的「H5」應用使用了手指跟隨的滑動效果,也就是用手指滑動幻燈片的效果, 什麼是手指跟隨,如圖;
css
網上滑動插件有很多,但好像沒一個好用的(不是bug太多,就是不靈活)這裏用原生JS實現了該功能,不只代碼量很少,邏輯也較簡單。移動端H5頁面的觸控觸發事件在我以前的一篇博客中寫了挺多.原博地址原生JS實現觸控滑動(swipe)圖片輪播 (裏面大體羅列了HTML5中touch事件的使用方法)html
這裏寫的PageSlide的使用的方法是將HTML結構寫好後往裏傳參就能夠了.它接受全部滑動頁面對象(在這裏是document.querySelector('#pages') ) 和要設定的方向(用X,Y表示橫向或者縱向)以及一個可選的擴展函數.
DEMO在此(使用模擬器或者移動設備打開預覽):
移動端原生JS實現手指跟隨的觸控滑動(縱向)
移動端原生JS實現手指跟隨的觸控滑動(橫向)git
直接下載代碼出門左轉Github? PageSlideDemogithub
掃碼看DEMO(縱向):
這裏將全部的代碼都封裝進一個PageSlide的原型對象中,能夠當成原生JS插件來使用,它所要求的HTML的結構爲:web
<div class='pages' id='pages'> // 全部滑動頁面的容器 <div class='page page1'>content</div> //全部滑動單頁 <div class='page page2'><div class="myAnimation">animation element</div></div> ... </div>
CSS樣式結構爲:segmentfault
/* 注意加html標籤,使得高度100%等於視窗高度 */ html,body{ width:100%; height:100%; margin:0 ; padding:0 ; overflow:hidden; …… } .pages{ width: 100%; height: 100%; position: relative; …… } .page { /*滑動頁面的統同樣式 */ position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; transform: translate3d(0px, 100%, 0px); -webkit-transform: translate3d(0px, 100%, 0px); transition: transform .5s ease-out; -webkit-transition: -webkit-transform .5s ease-out; } .page1{……} .page2{……} .page3{……} /* 全部動畫使用類控制 */ .play .myAnimation { ... }
要實現手指跟隨的滑動效果, 關鍵在於經過touch事件來設置transform:translate3d(x,y,z)的參數,並在滑動結束(touchend)設置一個最小滑動距離minRange,該距離範圍內的滑動,translate3d的參數等於touchmove的滑動距離,當大於minRange時, 則觸發下一頁(或上一頁)的總體滑動,translate3d的X或Y的參數也就是視窗的寬(橫向滑動時)或者高(縱向滑動時)app
另外,對於一個網頁app,還須要解決一個問題,即每一個頁面中可能有動畫或者其餘的事件須要在該頁面出現時纔開始播放,動畫採用css類控制, 這裏採用在每一個當前頁面中添加一個.play的類做爲標記, 在每一個頁面的CSS動畫設置中,一樣加上.play類名,這樣就實現了當頁面出現纔開始播放本頁動畫的功能。dom
PageSlide的代碼解析以下:ide
// PageSlide接收三個參數:頁面元素,要設定的滑動方向,可選的擴展函數 var PageSlide = function(el, swipe, options) { this.options = options || {} //可選函數 this.current = 0 //當前頁面索引 this.pageX //橫向的手指落點 this.pageY //縱向的手指落點 this.height //設備高度 this.width //設備寬度 this.flag //判斷滑動方向的變量 this.move //滑動的距離 this.$el = el //當前頁面的對象 this.swipe = swipe || 'X' //滑動方向參數 this.resize().init().bindEvents() //初始化 } PageSlide.prototype.init = function(i) { var current = i ? this.$el.children[i] : this.$el.firstElementChild if (!current) throw 'ERROR'; //moving類名做爲當前滑動頁面的標記,也在樣式中做滑動的擴展效果 current.classList.add('moving') current.style.webkitTransform = 'translate3d(0,0,0)' //以swipe的值預設置其餘頁面的寬高,得到流暢的交互效果 for(var i = 1; i <this.$el.children.length ; i++){ this['set' + this.swipe](this.$el.children[i], (this.swipe === 'X' ? this.width : this.height)) } setTimeout(function() { current.classList.remove('moving') current.classList.add('play') }, 3e2) return this } //爲頁面綁定各類事件的綁定函數 PageSlide.prototype.bindEvents = function() { var self = this window.addEventListener('resize orientationchange', this.resize.bind(this), false) 'touchstart touchmove touchend touchcancel'.split(' ').forEach(function(evn) { //將四個觸控函數(申明在後面)綁定到每一個頁面 self.$el.addEventListener(evn, self[evn].bind(self), false) }) } //得到當前觸控的頁面對象 PageSlide.prototype.getCurrent = function() { return this.$el.children[this.current] } //初始化時得到設備的寬高 PageSlide.prototype.resize = function() { this.width = this.$el.parentNode.clientWidth this.height = this.$el.parentNode.clientHeight return this } //到達任意頁面的random()方法 PageSlide.prototype.random = function() { var count = this.$el.children.length var current = this.current var arr = [] var num for (var i = 0; i < count; i++) { if (i !== current) arr.push(i.toString()) } num = Math.floor(Math.random() * arr.length) this.direct(+arr[num]) } // 四個內建的滑動事件函數,與前面綁定函數相呼應 PageSlide.prototype.touchstart = function(e) { var touches = e.touches[0] //touch start initializing this.flag = null this.move = 0 //record coordinates this.pageX = touches.pageX this.pageY = touches.pageY } PageSlide.prototype.touchmove = function(e) { var touches = e.touches[0] var X = touches.pageX - this.pageX var Y = touches.pageY - this.pageY var current = this.getCurrent() var next = current.nextElementSibling var prev = current.previousElementSibling //add moving styled if (!this.flag) { this.flag = Math.abs(X) > Math.abs(Y) ? 'X' : 'Y' if (this.flag === this.swipe) { current.classList.add('moving') next && next.classList.add('moving') prev && prev.classList.add('moving') } } if (this.flag === this.swipe) { e.preventDefault() e.stopPropagation() switch (this.swipe) { case 'X': //swipe horizontal this.move = X this.setX(current, X) next && (this.setX(next, X + this.width)) prev && (this.setX(prev, X - this.width)) break; case 'Y': //swipe vertical this.move = Y this.setY(current, Y) next && (this.setY(next, Y + this.height)) prev && (this.setY(prev, Y - this.height)) break; } } } PageSlide.prototype.touchend = function(e) { var minRange = 50 var move = this.move var current = this.getCurrent() var next = current.nextElementSibling var prev = current.previousElementSibling current.classList.remove('moving') next && next.classList.remove('moving') prev && prev.classList.remove('moving') if (!this.flag) return e.preventDefault() //滑動結束前往下一頁面,next()方法調用了go()方法 if (move < -minRange && next) return this.next() if (move > minRange && prev) return this.prev() this.reset() } PageSlide.prototype.touchcancel = function(e) { var current = this.getCurrent() var next = current.nextElementSibling var prev = current.previousElementSibling current.classList.remove('moving') next && next.classList.remove('moving') prev && prev.classList.remove('moving') this.reset() } //動態設定translate3d參數方法 PageSlide.prototype.setX = function(el, x, unit) { el && (el.style.webkitTransform = 'translate3d(' + x + (unit || 'px') + ',0,0)') } PageSlide.prototype.setY = function(el, y, unit) { el && (el.style.webkitTransform = 'translate3d(0,' + y + (unit || 'px') + ',0)') } //設置當前觸控頁面translate3d參數爲0的方法 PageSlide.prototype.setCurrent = function(el, i) { el && (el.style.webkitTransform = 'translate3d(0,0,0)') if (i) { this.current = i this.$current = this.$el.children[i] } } //調用go()方法前往下一或上一頁面 PageSlide.prototype.next = function() { this.go(this.current + 1) } PageSlide.prototype.prev = function() { this.go(this.current - 1) } //重置方法,用於初始化以及當前頁面的重置 PageSlide.prototype.reset = function() { var width = this.width var height = this.height var swipe = this.swipe var current = this.getCurrent() var prev = current.previousElementSibling var next = current.nextElementSibling this.setCurrent(current) prev && (this['set' + swipe](prev, -(swipe === 'X' ? width : height))) next && (this['set' + swipe](next, swipe === 'X' ? width : height)) } //去往下一或上一頁面的go方法 PageSlide.prototype.go = function(i) { var onFinish = this.options.onFinish var current = this.getCurrent() var total = this.$el.childElementCount var target = this.$el.children[i] var d = i < this.current ? -1 : 1 if (i === this.current || i < 0 || i >= total) return if (onFinish && (typeof onFinish === 'function')) onFinish.call(this, i) // 滑動完成調用方法 typeof this.options.tranSetionEnd ==='function' && this.options.tranSetionEnd.call(this) this.current = i this['set' + this.swipe](current, -d * (this.swipe === 'X' ? this.width : this.height)) this.setCurrent(target, i) this.finish(current, target) } //滑動完成後刪除當前頁面.play標記以及爲下一頁面添加.play標記 PageSlide.prototype.finish = function(curr, target) { this.flag = null setTimeout(function() { curr && curr.classList.remove('play') target && target.classList.add('play') }, 3e2) } /*direct to a page */ //直達某一頁面的方法, 由於有個項目的須要,寫了這個方法,要從任意頁面開始滑動依然能保持正常的滑動體驗,就須要將直達頁面的前面全部頁面的translate3d參數都設置爲(0,-height,0) PageSlide.prototype.direct = function(i){ if(i&&typeof(i)==='number') { this.go(i) for(var j = 0; j< i ;j++) { this['set' + this.swipe](this.$el.children[j], -1 * (this.swipe === 'X' ? this.width : this.height)) } } else return }
總算寫完了,吃飯~函數