1. 程式人生 > 其它 >利用uni-transition實現微信小程式底部浮層

利用uni-transition實現微信小程式底部浮層

技術概述

​ 需求是實現一個包含三種狀態,分別是底部、半屏、全屏的浮層,並且浮層支援使用者隨意拖動,同時還要防止使用者過度拖動。技術難點在於對動畫的控制,必須得有一定的過渡動畫否則整體會很突兀。

技術詳述

​ 首先看一下預期的樣子,圖為ios自帶高德地圖的底部浮層。

​ 最開始的動畫我是用CSS的keyframe來寫的,即利用關鍵幀來標記三個狀態,將過渡時間設定為300毫秒,出現的問題就是在程式執行時控制動畫只能通過動態增刪類來實現,十分繁瑣。這樣可以實現簡單的點選事件切換狀態,但是無法讓使用者滑動。

​ 這時發現了uni-app官方的動畫外掛uni-transition,閱讀文件之後發現類似於關鍵幀,就是從一個狀態到另一個狀態的滑動,設定好動畫和過渡時間,可以很方便地用js來控制。

<uni-transition custom-class="location-box" :show="showLocationBox" ref="locationBox">
</uni-transition>

上方程式碼為uni-transition的使用方式。custom-class用於繫結自己寫的類,:show用於繫結浮層的顯示,ref使用者註冊動畫的引用資訊,可以讓我們用this來控制。

​ 結合官方文件,來談談uni-transition的使用方式。

 this.$refs.locationBox.init({
            duration: 1000,
            timingFunction: 'linear',
            transformOrigin: '50% 50%',
            delay: 500
        })

​ 使用init方式可以給動畫初始化,並覆蓋掉原本的動畫引數,這裡我們用不到,但是必須要知道可以用init方法覆蓋原本的動畫。

​ 先從狀態切換開始實現,原理就是用uni-transition標籤包裹浮層內容,使用fixed位置,動畫的引數就是修改其height和top的內容,輔以uni-transition動畫的300毫秒過渡時間,以達到平滑過渡的效果。

//step方式相當於設定一個關鍵幀
this.$refs.locationBox.step({
    //設定要執行到的樣式大小,當呼叫run方法之後,uni-transition就會從當前的height和top以300毫秒過渡變化到
    //height=14vh,top=86vh的狀態
    height: '14vh',
    top: '86vh'
}, {
    duration: 300,
})
//run方法執行動畫
this.$refs.locationBox.run(() => {
	//這是run方法的回撥函式,可以在這裡執行動畫完成之後的操作
})

​ 簡單來說就是將三個狀態封裝為三個函式,每次要狀態變化時就呼叫對應函式,以此能實現基本的繫結事件進行動畫過渡。

​ 為什麼不封裝成一個函式通過引數改變內容?因為狀態的變化同時包含了一些其他狀態的改變需要在函式中一起動態變化,這裡可以自主選擇。

​ 這樣就可以實現繫結事件的效果,如下圖,通過繫結浮層底部的橫條的點選時間和input的聚焦事件,來控制動畫的執行。

​ 接下來就是要讓使用者可以利用黑色橫條拖動整個框的移動,因為網上找到寫可拖動底部浮層的教程都是寫Android的,這裡我並沒有找到比較好的辦法,只能用監聽事件來實現。

​ 利用將@touchend和@touchmove兩個事件繫結到黑色橫條上,監聽使用者對於黑色橫條的移動。

@touchmove:移動時觸發

@touchend:移動結束時觸發

<view @click="showList" @touchmove="touchLocationBox" @touchend="locationBoxReset">
    <!-- 這裡是黑色橫條的內容或樣式 -->
</view>

​ 然後再來看兩個事件繫結函式

///監聽觸控滑動事件
touchLocationBox(res) {
    //利用監聽事件獲取當前使用者點選螢幕的位置
    //而後將其除於整個螢幕的高度獲取top值
	let top = res.changedTouches[0].pageY / this.windowHeight
	//防止頂部過度拖拽
    //當拖拽部分小於10%時禁止使用者繼續拖拽
	if (top < 0.1) {top = 0.1}
    //防止底部過度拖拽
    //同理,禁止拖拽到85%以上
    else if (top > 0.85) {top = 0.85}
    //這裡是動畫執行的關鍵
    //每次函式執行時就執行一個過渡時間為0的動畫
    //動畫內容就是將當前的heiht值和top值過渡到使用者手指拖拽到的值
    this.$refs.locationBox.step({
    top: top * 100 + 'vh',
    height: (1 - top) * 100 + 'vh',
    }, {
    duration: 0,
    })
    this.$refs.locationBox.run(() => {
    })
    //維護一個當前的top值,動態改變
    this.locationBoxTop = top
},

​ 這裡要修改前面的動態函式。

this.$refs.locationBox.step({
    height: '14vh',
    top: '86vh'
}, {
    duration: 300,
})
this.$refs.locationBox.run(() => {
	//動畫執行完畢後更新當前的top值
    this.locationBoxTop = 0.86
})

​ 因為需要強制修改top值,所以uni-transition需要新增繫結top引數,並使用!important強制更新。

<uni-transition custom-class="location-box" :show="showLocationBox" ref="locationBox"
:styles="{top:locationBoxTop*100+'vh !important',height:(1-locationBoxTop)*100+'vh !important'}">
</uni-transition>

​ 這裡已經基本實現了使用者拖拽的問題,但是我們需要的僅僅為3個狀態,即底欄、半屏、全屏三個狀態,到這裡使用者拖動後浮層會停在當前位置。這裡就需要使用到@touchend事件,在使用者移動結束時觸發函式,我們在這裡對浮層進行復位。

//拖動結束將地點框復位
locationBoxReset(res) {
    //獲取當前的top值
    let top = this.locationBoxTop
    //自主設定拖動閾值,
    if (top < 0.4) {
        //這裡轉化為全屏狀態
    } else if (top > 0.7) {
        //這裡轉化為底欄狀態
    } else {
        //這裡轉化為半屏狀態
    }
},

​ 然後來看一下最終效果,使用者可以點選橫條實現動畫,還可以通過橫條拖動浮層,並且不會出現拖拽過渡的現象,當用戶拖拽到一定閾值時鬆手,浮層會自動轉換到附近的狀態。

問題和解決

​ 在動畫實現的問題還是遇到了很多問題。先拋開實現的問題不談,目前已經存在的問題就是拖拽時的幀率不夠高,因為一直在執行動畫所以效率不夠高,我個人覺得在小程式上是夠用的,雖然不能想高德地圖那樣流暢,但也並不會感到卡頓。這個問題我並沒有解決,這僅僅是我個人的實現方式,如果看到部落格的人有效率更高的動畫方式歡迎在評論區指教。

​ 然後說說實現上的問題。最開始的問題就是浮層在執行一次動畫之後會將step方法內的引數直接加在整個浮層樣式的尾部,而不是修改他的值,這是uni-transition外掛原始碼的問題,如下圖底部的height和top就是動畫執行後新增的樣式,而CSS的寫在後面的樣式會覆蓋掉寫在前面的樣式,所以我們動態繫結的height和top就無法生效,解決的方式就是在同態繫結時加上!important,給繫結的樣式最高的權重,使其不會被覆蓋。

​ 上面那個問題我真的找了好久才找到,最後是通過看wxml的程式碼變化才找到。所以這個可以提個醒,寫這種動畫可以看看wxml的程式碼變化,注意樣式的覆蓋。

​ 為了函式呼叫起來更加方便,我將三個狀態封裝為一個change方法,在使用使只需要使用change(0)可以直接執行到底欄狀態。

//更改浮層狀態,0:底欄(預設);1:半屏;2:全屏;
change(status) {
    if (status === 0) {
     //執行動畫和其他引數的改變
    } else if (status === 1) {
     //執行動畫和其他引數的改變
    } else if (status === 2) {
     //執行動畫和其他引數的改變
    }
},

​ 最後說一個與動畫無關但很有可能會遇到的問題,如果在浮層內有搜尋框,在動畫移動時可能會出現input內的placeholder移動殘影的問題,即input中的內容總是慢動畫一步移動,極大地影響了使用者體驗。我解決的方式就是在動畫執行時將input框禁用掉,即繫結其disabled屬性,只要input是出於禁用的狀態,就不會出現移動殘影的問題。

總結

​ uni-transition外掛由於其比較小眾,用的人比較少,所以網上能找到的教程並不多,建議最優先摸清官方的文件。基於這種過渡動畫的方式,可以做出很多複雜的動畫。

參考

uni-transition外掛官方連結

javaScript -- touch事件詳解