문제 시나리오
도 부동 창이 고정 위치 드래그를 이용하여 마이크로 채널 아이콘 버튼을 클릭 호출된다.
이것은 상대적으로 일반적인 구현 시나리오로 간주됩니다.
왜 커버보기 창을 부동합니까? 기본 구성 요소를 다시 냄비 중 -
처음에 나는 창 커버-볼 수 있지만보기를 부동 사용하지 않았다.
이 코드의 단순화 된 구조입니다 :
index.wxml :
<view class="move-view" style=" top:{{top}}px;left:{{left}}px;" 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',
})
}
})
왜 그것을 커버 있나요?
페이지가 텍스트 영역 구성 요소를 가지고 있기 때문에,이 구성 요소는 부동 창은 텍스트 영역 구성 요소로 이동하는 경우, 드래그 앤 클릭을 계속하지 않습니다 기본 구성 요소이다.
당신은 텍스트 영역에 위치 창을 부동 시작하면, 그것은 시작 부분을 클릭하고 드래그 할 수없는, 더 나쁜 것입니다.
이 마이크로 편지 애플릿 수준의 기본 구성 요소가 아닌 기본 구성 요소에 비해 높기 때문에 때 해결 될 수있는 몇 가지 문제를 수정하는 스타일은 아니다.
여기에 대해 얘기하지 않는 것입니다 무엇을 기본 구성 요소, 당신은, 당신은 내가 블로그를 작성하기 전에를 참조 할 수 있습니다에 대한 자세한 내용을 원한다면 : 마이크로 채널 작은 프로그램 IOS Echarts 차트 솔루션에 슬라이드 수를 .
페이지가 기본 구성 요소 이상이 아닌 경우, 위의 코드처럼 중단 할 수있는보기 창으로 수행 할 수 있도록.
그렇다면, 당신은 부동 창을 수행 할 커버보기 기본 구성 요소 수준 구성 요소를 사용하여, 구덩이에 단계로 계속 나를 따라 할 수 있습니다.
커버-보기 앤드류스는 페이스 악마, 흔들어 파킨슨 달리 끌어서
여기에 우리가 은폐보기를 할 후 수정 된 코드는 다음과 같습니다
<cover-view class="move-view" style=" top:{{top}}px;left:{{left}}px;" 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>
커버 뷰는 중첩 커버보기, 커버 이미지를 지원하기 때문에 우리가 커버 이미지의 이미지를 변경합니다,하지만 당신은 커버 뷰의 버튼을 사용할 수 있습니다.
이 해결 문제가 될 수 있지만 자유롭게 기본 구성 요소를 클릭 드래그하지만,이 안드로이드에 아주 이상한 현상이다, 그래서 그것을 설명하는 데 사용할 수있는 흔들 수 없었다 생각 :
그림은 그냥 손가락에 매우 느리게 이동했지만, 부동 윈도우의 성능은 겁 먹은 토끼 같았다, 나는 부동 창 후 효과를 미끄러 것입니다.
무지 힘을 볼 때 내가 처음이 효과를보고, 내가 무슨 말을 해야할지하지 않습니다.
虽然在ios上cover-view移动起来表现良好,但是在安卓上拖动起来的表现简直没法看。
勉强能看的补丁方案
安卓上这么挫,还不如原来的呢。
所以来个补丁方案好了,在ios下用cover-view完美拖动,在安卓上用view先跑着。
<cover-view wx-if="{{isIos}}" class="move-view" style=" top:{{top}}px;left:{{left}}px;" 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" style=" top:{{top}}px;left:{{left}}px;" 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" style=" top:{{top}}px;left:{{left}}px;" 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" style=" top:{{top}}px;left:{{left}}px;" 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" style=" top:{{top}}px;left:{{left}}px;" 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" style=" top:{{top}}px;left:{{left}}px;" 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',
})
}
})
这个最终解决方案在ios下直接使用cover-view来做悬浮窗,而在android的非原生组件上移动时,使用view来做悬浮窗,不冒泡滑动事件,在原生组件上移动时捕获冒泡的滑动事件来继续移动操作。
总结
虽然问题解决了,但是这仍然只是一个补丁方案。
最好的方式依然是微信小程序官方能修复cover-view在安卓移动时的BUG,但是我发现最早有人反馈这个问题是在2018年11月,到了现在2019年8月都没有结果。
如果不是微信小程序的官方态度有问题,那么只能说明这个问题的解决确实有难度或者优先级并不高,无论是哪一种,暂时都还是得用补丁方案。
这个方案并没有那么完美,他在一些边界的衔接上面可能还是会存在一些小问题,但它至少可用,并且应该是大多数用户可以接受的。