您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關怎么在微信小程序中實現一個懸浮窗功能,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
代碼結構:
index.wxml: <view class="move-view" bindtap="goToHome" catchtouchmove="setTouchMove"> <image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </image> </view> <textarea placeholder='我是textarea組件,用來輸入一些信息'></textarea> <view> 一大段test,占個位,表示下存在感 </view> index.js: Page({ /** * 頁面的初始數據 */ data: { left: 20, top: 250, isIos: true }, /** * 拖拽移動 */ setTouchMove: function (e) { if (e.touches[0].clientX > 0 && e.touches[0].clientY > 0) { this.setData({ left: e.touches[0].clientX - 30, top: e.touches[0].clientY - 30 }) } else { this.setData({ left: 20, //默認顯示位置 left距離 top: 250 //默認顯示位置 top距離 }) } }, /** * 返回首頁 */ goToHome: () => { wx.reLaunch({ url: '/pages/index/index', }) } })
為什么要用cover-view呢?
因為頁面上有個textarea組件,這個組件是原生組件,當懸浮窗移動到這個textarea組件上時,將無法繼續拖動和點擊。
如果懸浮窗一開始就定位在textarea上,那么就更慘了,一開始就不能點擊和拖動了。
這個原因時因為微信小程序的原生組件層級高于非原生組件,不是你修改幾下樣式就能解決的問題。
這里就不講什么原生組件了,如果想進一步了解,可以參考我之前寫的一篇博客:微信小程序在ios下Echarts圖表不能滑動的解決方案。
如果你的頁面上面沒有原生組件,那么像上面的代碼一樣用view做懸浮窗即可。
如果有,那么就可以跟著我繼續踩坑,使用cover-view
這個原生組件層級的組件來做懸浮窗。
安卓下的cover-view
拖動起來,抖得不像帕金森,像是魔鬼的步伐
以下是我們修改為cover-view之后的代碼:
<cover-view class="move-view" bindtap="goToHome" catchtouchmove="setTouchMove"> <cover-image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </cover-image> </cover-view> <textarea placeholder='我是textarea組件,用來輸入一些信息'></textarea> <view> 一大段test,占個位,表示下存在感 </view>
注意這里,我們的image也改為了cover-image
,因為cover-view只支持嵌套 cover-view、cover-image
,不過可在 cover-view 中使用 button。
這樣雖然解決了可在原生組件上自由拖動點擊的問題,但是在安卓上出現了一個很奇怪的現象,以至于我認為已經無法用抖動可以來形容了:
上圖是就是我滑動這個懸浮窗之后的效果,我只是很緩慢地在移動手指,但是這個懸浮窗的表現簡直就像一個受驚的兔子。
當我第一眼看見這個效果的時候一臉懵逼,我都不知道說什么好。
雖然在ios上cover-view移動起來表現良好,但是在安卓上拖動起來的表現簡直沒法看。
勉強能看的補丁方案
安卓上這么挫,還不如原來的呢。
所以來個補丁方案好了,在ios下用cover-view完美拖動,在安卓上用view先跑著。
<cover-view wx-if="{{isIos}}" class="move-view" bindtap="goToHome" catchtouchmove="setTouchMove"> <cover-image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </cover-image> </cover-view> <view wx-if="{{!isIos}}" class="move-view" bindtap="goToHome" catchtouchmove="setTouchMove"> <image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </image> </view> <textarea placeholder='我是textarea組件,用來輸入一些信息'></textarea> <view> 一大段test,占個位,表示下存在感 </view>
當然少不了要在js里面加上這句代碼:
onLoad: function (options) { wx.getSystemInfo({ success: (res) => { if (res.platform == "android") { this.setData({ isIos: false }) } } }) }
不要忘記isIos默認為true哦。
反正ios環境下可以完美使用了,至于安卓下拖到textarea組件上沒法再拖的問題,調整下懸浮框的初始位置就好了。
而且只要不是刻意移動到textarea組件上,拖動著懸浮框經過textarea組件也是沒有問題的嘛。
像我這么聰明的用戶還懂得滑動下面的頁面來使懸浮窗移動到非原生組件的地方,這樣就又可以拖動了嘛。
你又以為你的測試一定能發現這個問題?發現了又怎樣,我已經盡力了,還給你整出這么多理論依據,足夠你把鍋牢牢地按在微信小程序官方的頭上。
使用movable-view:仿佛發現了新大陸,結果發現這個還是個弟弟
甩鍋是一定要甩鍋的,但是段位要高。
所以要遍查官方文檔,探討一切可能性,以免甩鍋的時候被打臉。
我們仔細觀察小程序官方文檔,發現還是有個專門用來拖動的組件叫movable-view
。
這個組件和cover-view擺放在一起仿佛很厲害的樣子,緊接著我們在原生組件使用限制文檔中發現了它并不是原生組件。
也就是說這個東西的層級一定還是低于咱們的textarea組件的。
雖然已經很確定這個東西沒什么用了,但是最后還是試探一把,結果發現是個真弟弟,這里就不給出代碼了。
我寫這個弟弟方案放在這里的目的主要是為了不要浪費你的驗證時間。
理論上行得通的方案:將拖動事件的捕獲放在父級
現在我們確認的最優甩鍋方案里,已經實現了功能和甩鍋兩不誤。
那么作為一名有追求的技術人員,還是需要去探討以下這個問題到底有沒有完美的解決方案。
因為我最開始是把這個懸浮窗做成了一個組件,那么作為組件來講,這個東西就只能做到這個地步了。
不過如果你是像我現在的例子一樣直接做在了頁面里,那么實現起來也不是說沒有辦法的。
我們將拖動的事件放在父級上就可以了,請看接下來的代碼:
index.wxml: <view bindtouchmove="setTouchMove"> <view class="move-view" bindtap="goToHome"> <image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </image> </view> <textarea placeholder='我是textarea組件,用來輸入一些信息'></textarea> <view> 一大段test,占個位,表示下存在感 </view> </view> index.js: Page({ /** * 頁面的初始數據 */ data: { left: 20, top: 250 }, /** * 拖拽移動 */ setTouchMove: function (e) { const MOVE_VIEW_RADIUS = 30 // 懸浮窗半徑 const touchPosX = e.touches[0].clientX const touchPosY = e.touches[0].clientY const moveViewCenterPosX = this.data.left + MOVE_VIEW_RADIUS const moveViewCenterPosY = this.data.top + MOVE_VIEW_RADIUS // 確保手指在懸浮窗上才可以移動 if (Math.abs(moveViewCenterPosX - touchPosX) < MOVE_VIEW_RADIUS + 60 && Math.abs(moveViewCenterPosY - touchPosY) < MOVE_VIEW_RADIUS + 60) { if (touchPosX > 0 && touchPosY > 0) { this.setData({ left: touchPosX - MOVE_VIEW_RADIUS, top: touchPosY - MOVE_VIEW_RADIUS }) } else { this.setData({ left: 20, // 默認顯示位置 left距離 top: 250 // 默認顯示位置 top距離 }) } } }, /** * 返回首頁 */ goToHome: () => { wx.reLaunch({ url: '/pages/index/index', }) } })
關鍵代碼就是這塊了:
// 確保手指在懸浮窗上才可以移動 if (Math.abs(moveViewCenterPosX - touchPosX) < MOVE_VIEW_RADIUS + 60 && Math.abs(moveViewCenterPosY - touchPosY) < MOVE_VIEW_RADIUS + 60) { }
只要確保手指在懸浮窗的范圍內就可以觸發移動了,這里的60是為了確保你的手指太大,或者移動得比較快時超出了懸浮窗區域依然可以觸發拖動,這個可以自己設定數值。
這個方案在理論上很合理,并且還加上了60這個緩沖區域,但是實際在拖動的時候你仍然會面臨下面三個問題:
1.如果懸浮窗下方有滾動區域,那么拖動的時候就會滾動頁面,效果會顯得比較奇怪。
2.實際移動沒法移動太順暢,只能拖著懸浮窗亦步亦趨,要不然很容易超過60這個緩沖區域,導致拖動不繼續觸發。
2.如果將緩沖區域設置過大,那么又會出現一種比較奇怪的場景:明明不準備拖動懸浮窗,只是準備滑動頁面,懸浮窗卻跳到自己手指這里了。
進階解決方案:禁止冒泡的拖動 + 理論方案
這個解決方案基于我們的最初方案,并且使用我們的理論方案作為補充。
先上代碼:
index.wxml: <view bindtouchmove="handleSetMoveViewPos"> <view class="move-view" bindtap="goToHome" catchtouchmove="handleTouchMove"> <image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </image> </view> <textarea placeholder='我是textarea組件,用來輸入一些信息'></textarea> <view> 一大段test,占個位,表示下存在感 </view> </view> index.js: Page({ /** * 頁面的初始數據 */ data: { left: 20, top: 250 }, /** * 拖拽移動(補丁) */ handleSetMoveViewPos: function (e) { const MOVE_VIEW_RADIUS = 30 // 懸浮窗半徑 const touchPosX = e.touches[0].clientX const touchPosY = e.touches[0].clientY const moveViewCenterPosX = this.data.left + MOVE_VIEW_RADIUS const moveViewCenterPosY = this.data.top + MOVE_VIEW_RADIUS // 確保手指在懸浮窗上才可以移動 if (Math.abs(moveViewCenterPosX - touchPosX) < MOVE_VIEW_RADIUS+30 && Math.abs(moveViewCenterPosY - touchPosY) < MOVE_VIEW_RADIUS+30 ) { if (touchPosX > 0 && touchPosY > 0) { this.setData({ left: touchPosX - MOVE_VIEW_RADIUS, top: touchPosY - MOVE_VIEW_RADIUS }) } else { this.setData({ left: 20, // 默認顯示位置 left距離 top: 250 // 默認顯示位置 top距離 }) } } }, /** * 拖拽移動 */ handleTouchMove: function (e) { const MOVE_VIEW_RADIUS = 30 // 懸浮窗半徑 const touchPosX = e.touches[0].clientX const touchPosY = e.touches[0].clientY if (touchPosX > 0 && touchPosY > 0) { this.setData({ left: touchPosX - MOVE_VIEW_RADIUS, top: touchPosY - MOVE_VIEW_RADIUS }) } else { this.setData({ left: 20, //默認顯示位置 left距離 top: 250 //默認顯示位置 top距離 }) } }, /** * 返回首頁 */ goToHome: () => { wx.reLaunch({ url: '/pages/index/index', }) } })
這個方案的核心點在于:catchtouchmove="handleTouchMove"
。
當我們正常拖動懸浮窗時,通過catchtouchmove,我們可以捕獲在懸浮窗上的滑動事件,并且不冒泡到父元素,那么我們綁在父層級的滑動事件就不會觸發。
而當我們拖動在原生組件之上的懸浮窗時,因為點不到這個懸浮窗,就不會觸發handleTouchMove函數,只會觸發綁定在父元素上的handleSetMoveViewPos
函數。
另外如果你細心的話,就會發現在handleSetMoveViewPos函數這里我縮小了那個60的緩沖區域為30,這樣做的目的是因為觸發這個函數只會在原生組件上,所以多番權衡距離之后,盡量避免近距離滑動操作就觸發拖動懸浮框。
通過我們的方案,我們可以在非原生組件上自由拖動,在原生組件上比較順暢地拖動。
本來我是準備將這個方案作為最終方案的,但是ios下,懸浮窗在原生組件上時,在父元素上的滑動事件竟然不觸發。
棋差一招,棋差一招啊!
最終解決方案:更多的補丁,更多的快樂
這個最終解決方案,當然是把我們之前所有的補丁方案全部結合起來。
代碼如下:
index.wxml: <view bindtouchmove="handleSetMoveViewPos"> <view wx-if="{{!isIos}}" class="move-view" bindtap="goToHome" catchtouchmove="handleTouchMove"> <image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </image> </view> <cover-view wx-if="{{isIos}}" class="move-view" bindtap="goToHome" catchtouchmove="handleTouchMove"> <cover-image class="img" src="https://ss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=4294841024,3545417298&fm=179&app=42&f=PNG?w=56&h=56"> </cover-image> </cover-view> <textarea placeholder='我是textarea組件,用來輸入一些信息'></textarea> <view> 一大段test,占個位,表示下存在感 </view> </view> index.js: Page({ /** * 頁面的初始數據 */ data: { left: 20, top: 250, isIos: true }, /** * 生命周期函數--監聽頁面加載 */ onLoad: function (options) { wx.getSystemInfo({ success: (res) => { if (res.platform == "android") { this.setData({ isIos: false }) } } }) }, /** * 拖拽移動(補丁) */ handleSetMoveViewPos: function (e) { // 在ios下永遠都不會走這個方案,以免引起無用的計算 if (!ios) { const MOVE_VIEW_RADIUS = 30 // 懸浮窗半徑 const touchPosX = e.touches[0].clientX const touchPosY = e.touches[0].clientY const moveViewCenterPosX = this.data.left + MOVE_VIEW_RADIUS const moveViewCenterPosY = this.data.top + MOVE_VIEW_RADIUS // 確保手指在懸浮窗上才可以移動 if (Math.abs(moveViewCenterPosX - touchPosX) < MOVE_VIEW_RADIUS && Math.abs(moveViewCenterPosY - touchPosY) < MOVE_VIEW_RADIUS) { if (touchPosX > 0 && touchPosY > 0) { this.setData({ left: touchPosX - MOVE_VIEW_RADIUS, top: touchPosY - MOVE_VIEW_RADIUS }) } else { this.setData({ left: 20, // 默認顯示位置 left距離 top: 250 // 默認顯示位置 top距離 }) } } } }, /** * 拖拽移動 */ handleTouchMove: function (e) { const MOVE_VIEW_RADIUS = 30 // 懸浮窗半徑 const touchPosX = e.touches[0].clientX const touchPosY = e.touches[0].clientY if (touchPosX > 0 && touchPosY > 0) { this.setData({ left: touchPosX - MOVE_VIEW_RADIUS, top: touchPosY - MOVE_VIEW_RADIUS }) } else { this.setData({ left: 20, //默認顯示位置 left距離 top: 250 //默認顯示位置 top距離 }) } }, /** * 返回首頁 */ goToHome: () => { wx.reLaunch({ url: '/pages/index/index', }) } })
關于怎么在微信小程序中實現一個懸浮窗功能就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。