需求
產(chǎn)品要求在每個頁面上都加一個浮動按鈕,上面一個電話圖標(biāo),點擊后給VIP聯(lián)系人撥打電話。我想這個簡單,一兩個小時應(yīng)該就實現(xiàn)了吧。于是我折騰了差不多兩天。
初步構(gòu)想
記得以前看小程序組件文檔時有一個movable-view,可以拿來用一下。結(jié)果真正使用時才發(fā)現(xiàn),movable-view外面需要套一個movable-area才可以。沒關(guān)系,套上好了。
首先,建一個組件,叫phone好了,最外層是movable-area,里面是movable-view,再里面是一個普通view,加一個hover-class可以實現(xiàn)點擊狀態(tài)。把這個組件加個每一個頁面上。
movable-view的移動范圍是根據(jù)外層的movable-area來確定的,所以movable-area應(yīng)該鋪滿全屏,但這樣一來就把頁面上其他內(nèi)容全部擋住了。那就利用touchstart/touchend事件判斷是浮動按鈕是否被按下,按下的狀態(tài)就設(shè)置movable-area大小為按鈕大小,位置為按鈕的位置(通過設(shè)置transform樣式實現(xiàn)),而沒有按下的狀態(tài),再讓它恢復(fù)成全屏大小,位置為(0,0)(transform:none).
試驗了一下,初始位置調(diào)為左上角,第一次拖動是正常的,第二次拖就不對勁了。雖然第二次拖動movable-view的初始位置是正確的,但它的change事件中得到的坐標(biāo)卻是從(0,0)也就是被點擊那一瞬間開始算的,雖說此時已經(jīng)把movable-area的位置、大小恢復(fù)了。
通用方案
既然movable-view不好用,那就改成普通view算了,通過touchmove事件改變其位置,這樣應(yīng)該比較簡單,雖說可能不如movable-view拖起來流暢。但對于支持下拉刷新的頁面,拖動浮動按鈕時,往往就下拉刷新了。此時即使catch頁面上某些veiw的touchmove方法,也是不管用的。因為下拉刷新不是view的下拉,而是整個頁面的下拉,也沒有辦法阻止冒泡什么的。自定義Modal的mask層也有同樣的問題,但好在一般用戶不會去拖動mask層,但浮動按鈕卻是必拖無疑問的。
如果可以忍的話,這個方案是可行的。但是,不能忍啊。于是我開始了無休止的糾結(jié),甚至一度想干脆讓這個浮動按鈕直接固定在右下角算了。
最終實現(xiàn)
又經(jīng)過長時間的反復(fù)試驗,我發(fā)現(xiàn)movable-view直接用也是可以的,但直接用的話change事件不太靈敏,干脆不讓它change,能不讓頁面下拉刷新也是好的。不過呢,direction設(shè)為none以后,它的表現(xiàn)和普通view一樣,拖動時頁面照樣會下拉刷新。最后只得設(shè)成了vertical,這樣會有一個小小的bug,讓最終的方案不是那么完美。
下面是我的代碼:
- components/phone/index.wxml
<movable-view direction="vertical"
inertia="true"
friction="1.2"
class="make-phone-call-movable {{mobile ? '' : 'hidden'}}"
style="left:{{left}}px;top:{{top}}px"
catchtouchmove="onTouchMove"
catchtouchstart="onTouchStart"
catchtouchend="onTouchEnd"
catchtouchcancel="onTouchEnd">
<view class="make-phone-call"
hoverClass="make-call-phone-hover" />
</movable-view>
- components/phone/index.wxss
.make-phone-call-movable {
display: block;
position: fixed;
width: 108rpx;
height: 108rpx;
z-index: 1301;
}
.make-phone-call-movable.hidden {
display: none;
}
.make-phone-call {
background-color: #C00000;
background-image: url('data:image/png;base64,...');
background-repeat: no-repeat;
background-position: 28rpx 28rpx;
background-size: 52rpx 52rpx;
border-radius: 50%;
display: block;
position: absolute;
left: 0;
top: 0;
width: 108rpx;
height: 108rpx;
z-index: 1301;
}
.make-phone-call-hover {
background-color: #B40000;
}
- components/phone/index.js
const app = getApp()
const util = require('../../utils/util.js')
const POSITION_KEY = 'phoneCallButtonPosition'
Component({
properties: {
hasTabbar: {
type: Boolean,
value: false
}
},
data: {
name: '',
mobile: '',
rectangless: {
left: 8,
top: 8,
right: 313,
bottom: 486
},
touched: false,
left: 0,
top: 0
},
methods: {
onTouchMove(e) {
const currentX = e.touches[0].pageX
const currentY = e.touches[0].pageY
let left = this.lastLeft + currentX - this.lastX
let top = this.lastTop + currentY - this.lastY
left = Math.min(Math.max(left, this.data.rectangle.left), this.data.rectangle.right)
top = Math.min(Math.max(top, this.data.rectangle.top), this.data.rectangle.bottom)
this.setData({
left: left,
top: top
})
},
onTouchStart(e) {
this.lastX = e.touches[0].pageX
this.lastY = e.touches[0].pageY
this.lastLeft = this.data.left
this.lastTop = this.data.top
this.setData({
touched: true
})
},
onTouchEnd(e) {
this.setData({
touched: false
})
const leftDiff = Math.abs(this.lastLeft - this.data.left)
const topDiff = Math.abs(this.lastTop - this.data.top)
if (leftDiff === 0 && topDiff === 0) {
this.makePhoneCall()
} else {
this.playAnimation()
}
},
makePhoneCall() {
const name = this.data.name
const mobile = this.data.mobile
const language = app.globalData.language
if (!mobile) return
let message = util.getStrings('app', language).phone_call
message = message.replace(/{contact}/g, name)
util.showConfirmAlert(language, message, () => {
wx.makePhoneCall({
phoneNumber: mobile
})
})
},
getMobile() {
app.doGet('getMemberInfo', {}).then(res => {
let name = ''
let mobile = ''
if (res && res.contact) {
name = res.contact.name ? res.contact.name : '',
mobile = res.contact.mobile ? res.contact.mobile : ''
}
this.setData({
name: name,
mobile: mobile
})
}).catch(e => {
console.error(e)
this.setData({
name: '',
mobile: ''
})
})
},
getRectangle() {
const systemInfo = util.getSystemInfoSync()
const windowWidth = systemInfo.windowWidth
const windowHeight = systemInfo.windowHeight
const buttonSize = Math.round(windowWidth * 108 / 750)
const tabbarHeight = this.properties.hasTabbar ?
Math.round(windowWidth * 110 / 750) : 0
const margin = 8
const right = windowWidth - margin - buttonSize
const bottom = windowHeight - margin - tabbarHeight - buttonSize
this.setData({
rectangle: {
left: margin,
top: margin,
right: right,
bottom: bottom,
windowWidth: windowWidth,
windowHeight: windowHeight,
buttonSize: buttonSize,
tabbarHeight: tabbarHeight,
margin: margin
}
})
},
getPosition() {
const position = util.getStorageSync(POSITION_KEY)
this.getRectangle()
let left = this.data.rectangle.right
let top = this.data.rectangle.bottom
if (position) {
left = position.left
top = position.top
}
this.setData({
left: left,
top: top
})
},
playAnimation() {
const rectangle = this.data.rectangle
const lastLeft = this.data.left
const lastTop = this.data.top
let left = this.data.left
let top = this.data.top
left = left < (rectangle.windowWidth - rectangle.buttonSize) / 2 ?
rectangle.left : rectangle.right
top = top < rectangle.top + 20 ? rectangle.top :
(top > rectangle.bottom - 20 ? rectangle.bottom : top)
util.playAnimatoin(400, (value) => {
const currentLeft = lastLeft + (left - lastLeft) * value
const currentTop = lastTop + (top - lastTop) * value
this.setData({
left: currentLeft,
top: currentTop
})
})
const position = {
left: left,
top: top
}
util.setStorageSync(POSITION_KEY, position)
}
},
lifetimes: {
attached() {
this.getPosition()
this.getMobile()
}
},
pageLifetimes: {
show() {
this.getPosition()
this.getMobile()
},
resize(size) {
this.getRectangle()
}
},
})
wxss里的make-phone-call-hover樣式?jīng)]有用上,最后沒有點擊態(tài)了。js里的data/touched也沒用上。
bug就是,這個浮動按鈕可能被拖到上邊緣以外,個人覺得尚能接受。目前在模擬器及安卓手機上運行良好。最后來張截圖:
