前言
一個月前還是用vue做微信H5,后面公司業務發展,入坑小程序,做了幾款小程,跑了不少坑, 也會陸續在后面幾節跟大家分享。
在這節給大家分享這個 小程序圖片輪播實現方案
初步的實現思路
我要實現的效果
能看到 左右兩邊如果有圖,會顯示一部分出來,本來也想用 小程序自帶的swiper 組件,無法達到,左右兩邊 看到上(下)圖片的效果,無法改變他組件原來的設置(坑)
在開始之前,先想想h5是怎么實現的,用原生寫,可以在某個固定框的盒子里,再用一個div 包住 x 張圖,然后改變 ontouchmove 時改變 left(或者translateX) ,判斷用戶手指滑動足夠距離,就滑到下(上)一張,
最后判斷到最后一張或者第一張,不滑動就好(在這里不做無縫滾動)。
jquery,vue 很多庫都能夠實現
我們只要 實現了第一張圖的滑動,記錄這個偏移量 tranX1 = 0 + offsetX, 第二張的滑動距離 tranX2 = tranX1 + offsetX ,第三張的滑動距離 tranX3 = tranX2 + offsetX ...
相反方向 也就 遞減 tranX1 = tranX2 - offsetX ...
然后判斷臨界值就OK了
了解了大概的滑動過程現在就可以掀起袖子干了~
具體技術實現
寫了個 swiper 的方法,用原形鏈繼承的方式,與小程序的page({})里面代碼盡量分開, 用慣了Vue的雙向綁定,特別討厭小程序的 this.setData 而且是異步的(坑),this有時候要用變量保留,
先定義了幾個變量,初始化一些必要參數,
記錄 touch 過程中 滑動開始的坐標,滑動過程中,和滑動結束時的坐標,
構造 swiper 函數里面
opt 配置
swiperIndex 用於記錄 滑動到的圖片索引,默認值為0
singleOffsetX 滑一張的tranX 偏移量
boxTranslateX 盒子容器總偏移量 默認0
_x_start (單位px)記錄touchstart里的e.touchs[0].pageX
_y_start (單位px)記錄touchstart里的e.touchs[0].pageY
_x_move (單位px)記錄touchsmove里的e.touchs[0].pageX
_x_offset 臨時變量,記住 _x_move-_x_start 手指移動距離
_x_end (單位px)記錄touchend里的 e.changedTouches[0].pageX
_x_realOffset (單位px)臨時變量,根據臨界值 圖片真實移動
screenWidth = 750 屏幕寬度默認 iphone6 750rpx
screenHeight = 1210 屏幕高度默認 iphone6 1210rpx
pixelRatio 根據微信api里 systemInfo設備屏幕寬度 計算比例
maxTranX 根據圖片的數量計算最大偏移量,用於臨界判斷
width 盒子的總寬度(用於view 上面綁定 單位:rpx )根據圖片數量計算盒子的總寬度 = 首末兩邊(100)x 2+ 圖片與圖片之間的間距(60)* (圖片數量 -1)+ 圖片寬度 x 圖片數量
(this.width = 100 2 + (this.opt.imgLength - 1) 60 + 550 * this.opt.imgLength)
然后執行函數 computedScreenRatio 根據寬轉換比例
代碼如下
'use strict';
var swiper = function(opt){
// 保存設置
this.opt =opt
// 當前 移動塊停留索引
this.swiperIndex = 0
// 盒子容器總偏移量
this.boxTranslateX = 0
// 每移動一張圖會增加的偏移量
this.singleOffsetX = -610
// 觸摸
this._x_start = 0
this._y_start = 0
// 移動
this._x_move = 0
// 離開
this._x_end = 0
// 偏移量
this._x_offset = 0
// move 過程中真實偏移
this._x_realOffset = 0
this.screenWidth = 750
this.screenHeight = 1210
this.pixelRatio = 2
// rpx
this.maxTranX = this.singleOffsetX * (this.opt.imgLength -1 )
// 容器寬度 width 100 是兩邊靠牆
this.width = 100 * 2 + (this.opt.imgLength - 1) *60 + 550 * this.opt.imgLength
// 計算寬轉換比例
this.computedScreenRatio(opt.systemInfo)
}
rpx(responsive pixel): 可以根據屏幕寬度進行自適應。規定屏幕寬為750rpx。如在 iPhone6
上,屏幕寬度為375px,共有750個物理像素,則750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
iphone6 1rpx = 0.5px , iphone5 1rpx = 0.42px 根據寬度做兼容
// 計算寬轉換比例
swiper.prototype.computedScreenRatio = function (systemInfo){
this.pixelRatio = systemInfo.pixelRatio
this.screenWidth = systemInfo.screenWidth
this.screenHeight = systemInfo.screenHeight
//this.singleOffsetX = -610
// 奇怪的i5 按照算法會 多偏移2 px
if (systemInfo.model.indexOf('iPhone 5') > -1 ){
this.singleOffsetX = -(610 * (this.screenWidth / 750) -2 ).toFixed()
}
else{
this.singleOffsetX = -610 * (this.screenWidth / 750)
}
this.maxTranX = this.singleOffsetX * (this.opt.imgLength - 1)
// 回調綁定vm ,渲染盒子寬度
this.opt.success && this.opt.success(this.width)
}
扯一下小程序
- bindtouchstart : ontouchstart
- bindtouchmove: ontouchmve
- bindtouchend : ontouchend 一樣是有e.touches e.changedTouches
下面是從滑動開始到結束的處理過程代碼分析
keepStartX(touchstart): 記錄開始狀態 比較簡單就不講了
moveBox (touchend)是一個手指滑動,所有圖片(盒子)跟着手指滑動的過程
- 1.手指向右滑,_x_offset > 0 ,滑向上一張圖
- 2.手指向左滑,_x_offset < 0 ,滑向下一張圖
- 按邏輯來說,手指水平方向上pageX 的距離, 圖片盒子就應該跟着向(左)或者向右偏移一樣的量, 但是 從最后一張 開始 向左滑,后面已經沒有圖了,(或者 從第一張圖開始已經向右滑,前面已經沒有圖了 ),這時候就要做臨界值判斷了,結果就有手指滑動距離 _x_offset ,還有盒子滑動應該滑的准確距離 lastTranX
touchEnd
computedTranX:
- 根據 滑動方向,臨界值 計算出 手指滑動時,盒子的應該滑動的方向與距離 tranX (注: 這里tranX 從第一張開始是0 ,偏移量是負數,所以_x_realOffset永遠是 <0 )
計算最后手指離開的坐標,_x_realOffset用來保存計算 真實的在x方向上的距離R
// bindtouchstart
swiper.prototype.keepStartX = function(e, callback){
this._x_start = e.touches[0].pageX
this._y_start = e.touches[0].pageY
callback && callback()
}
// bindtouchmove
/*
函數說明 moveBox
@params e 事件
@params {function} callback 圖片盒子的偏移量改變,綁定到vm 上面,視圖發生變化
*/
swiper.prototype.moveBox = function (e, callback) {
this._x_move = e.touches[0].pageX
this._x_offset = this._x_move - this._x_start
var lastTranX = this.computedTranX(this._x_offset)
this._x_realOffset = lastTranX
callback && callback(lastTranX)
}
/*
函數說明 根劇移動偏移量臨界值計算出真實位移用
滑下一張 判斷是否超出臨界值,超出則等於臨界值
若不超過則偏移量 = 手指(touch)移動距離 + 當前盒子偏移量
*/
swiper.prototype.computedTranX = function ( offsetX ){
// 滑向上一張
if( offsetX > 0 ){
return offsetX + this.boxTranslateX
}
// 滑動下一張
else if (offsetX < 0 ){
if ( Math.abs(offsetX - this.boxTranslateX) > Math.abs(this.maxTranX - 50) ){
return this.maxTranX
}else{
return offsetX + this.boxTranslateX
}
}
}
// bindtouchend
swiper.prototype.ontouchEnd = function(e, callback){
// 記錄手指離開屏幕的坐標
this._x_end = e.changedTouches[0].pageX
this._x_realOffset = this._x_end - this._x_start
// 判斷滑動距離超過 指定值 輪播一張圖
var canExchangeImgFromOffset = Math.abs(this._x_realOffset) >= 50 ? true : false
// 滑動距離達到 換圖的要求
if ( canExchangeImgFromOffset ){
// 變量說明 計算總偏移
var lastTotalTranlateX ;
// 滑向上一張
if (this._x_offset > 0) {
console.log('// 滑向上一張')
// 判斷是否已經在第一張圖,若不是,則滑動上一張
if (Math.abs(this.boxTranslateX) > 0) {
// lastTotalTranlateX 遞增
// this.singleOffsetX 為負數 -- 得 +
this.swiperIndex -= 1
lastTotalTranlateX = this.boxTranslateX - this.singleOffsetX
}
else {
// 已在第一張圖 ,動畫恢復原位
this.swiperIndex = 0
lastTotalTranlateX = 0
}
}
// 滑向下一張
else {
console.log('// 滑向下一張')
if ( Math.abs(this.boxTranslateX) < Math.abs(this.maxTranX) ) {
// lastTotalTranlateX 遞增
this.swiperIndex += 1
lastTotalTranlateX = this.boxTranslateX + this.singleOffsetX
} else {
console.log('到底圖了')
this.swiperIndex = this.opt.imgLength -1
lastTotalTranlateX = this.maxTranX
}
}
this.boxTranslateX = lastTotalTranlateX
/* callback 說明
@參數1 是否執行動畫
@參數2 若參數1為true, 則盒子的tranX 應該變成 lastTotalTranlateX,動畫飛到對應的圖
小程序 執行 wx.createAnimation
*/
callback && callback(true, lastTotalTranlateX)
// reset 所有 與touch 相關參數
this.resetParams()
}
else{
callback && callback(false)
// reset 所有 與touch 相關參數
this.resetParams()
}
}
源碼地址 https://gitee.com/adfasdfasdfas/XiaoChengXu-swiper-LunBodemo
本文轉載於:猿2048⇒https://www.mk2048.com/blog/blog.php?id=h0i2cbh0cib