textarea 在安卓上删除字符时会执行2次input事件
小程序仿微信聊天按住说话功能
实现:按住说话有录音动画,上滑可取消发送,松开发送录音
- 首先进行录音授权判断
# .wxml
<view class="btn {{touchBtn?'hoverBtn':''}}" catch:touchend="onTouchEnd" bind:touchstart="record"
bind:longpress="onLongpress" catch:touchmove="onTouchMove">{{touchBtn?'松开 结束':'按住说话'}}</view>
复制代码
// # ontouchstart
record() {
const scopeRecord = app.globalData.scopeRecord;
// 无授权时获取授权
if (!scopeRecord) {
getScopeRecord();
return;
}
this.data.touched = true;
// 触发父组件录音事件,由父组件调用录音API
this.triggerEvent('startRecord')
},
复制代码
- 上滑取消发送,利用滑动距离判断是否取消,
onTouchMove(e) {
if (e.touches[0].clientY < 520) {
// # 取消发送
this.data.cancelRecord = true;
wx.showToast({
title: '松开,取消发送',
duration: 10000,
image: '/page/common/resource/image/cancel.png'
})
} else {
// # 不取消
this.showMicAni(1);
this.data.cancelRecord = false;
}
},
复制代码
- 松开发送
bug:快速点击按钮,一直显示录音中 原因:调起录音API需要一定时间,会出现touchstart->touchend->调起录音API,touchend事件在录音前触发了,无法再调起停止录音API
思路:可以在调用录音后用个变量a标识,在!a时执行touchend后则用变量b标识,在调用录音后若有b则再立即停止录音。但该方法涉及太多变量传递(父传子,子传父),就偷了下懒用了个400ms的延时
onTouchEnd(e) {
if (!this.data.touched) return;
this.data.touched = false;
wx.hideToast();
// 延时处理 防止录音未调起就触发停止
setTimeout(() => {
this.triggerEvent('stopRecord', {
// # 判断是否取消发送
send: !this.data.cancelRecord
});
}, 400);
}
},
复制代码
- 除了说话中,上滑取消外,还要加多个说话时间太短的提示
- 方案一:我用了小程序的
longpress
事件做判断,未触发该事件则属于说话太短;同时,如果在没在触发Longpress前触发了上滑也默认开启录音 - 方案二:touchstart时开始计时,touchend结束计时,用时间来判断
// # 利用长按判断录音是否太短
onLongpress() {
this.data.recording = true;
},
// # 上滑时默认录音中
onTouchMove(e) {
this.data.recording = true;
if (e.touches[0].clientY < 520) { ... } else { ... }
},
onTouchEnd(e) {
if (this.data.recording) {
// # 触发停止...
} else {
wx.showToast({
title: '说话时间太短',
duration: 500,
image: '/page/common/resource/image/warn-b.png'
})
// # 触发停止...
}
},
复制代码
- 录音动画,想实现微信聊天录音中可根据音量大小改麦克风音量提示的动画的toast。
- 问题:官方API中录音回调没提供音量数据,无法根据音量来做动画。而且我们使用了
wx.showToast
,image无法使用动图,也无法自定义动画 - 思路:既然无法获取音量数据,又需要用户感知有在录音中,还无法做动画。我想了个方法,能不能每隔一段时间换一张image直到录音停止,而且更换图片的顺序不能是固定的,这样看起来很假,必须随机更换图片
- 注意:
touchend
和touchmove
要清空定时器,touchmove
手指回到按钮则要再次调用showMicAni
showMicAni(i) {
// # 初始化间隔时间
let delay = 500;
let that = this;
wx.showToast({
title: '上滑,取消发送',
duration: 60000,
// # 准备5张麦克风不同状态的png
image: '/page/common/resource/image/mic0' + i + '.png'
})
that.data.time1 = setTimeout(() => {
// util.getRandomInt公共方法,随机生成【1,5】中的整数
i = util.getRandomInt(1, 5)
that.showMicAni(i)
}, delay);
},
// touchstart
record() {
// # 开始录音...
this.showMicAni(1);
},
onTouchEnd() {
clearTimeout(this.data.time1)
},
onTouchMove(e) {
clearTimeout(this.data.time1)
if (e.touches[0].clientY < 520) {
// # 取消发送...
} else {
this.showMicAni(1);
this.data.cancelRecord = false;
}
},
复制代码
以下是完整代码
// # .js
import wlogin from '../../../common/resource/js/login.js';
import util from '../../../../utils/util'
let app = getApp();
const getScopeRecord = wlogin.recordInpower();
Component({
properties: {},
data: {
touchBtn: false,
},
ready() {},
methods: {
// 利用长按判断录音是否太短
onLongpress() {
this.data.recording = true;
},
// touchstart
record() {
const scopeRecord = app.globalData.scopeRecord;
if (!scopeRecord) {
getScopeRecord();
return;
}
this.data.recording = false;
this.data.touched = true;
this.data.cancelRecord = false;
this.setData({
touchBtn: true
})
this.triggerEvent('startRecord')
this.showMicAni(1);
},
showMicAni(i) {
let delay = 500;
let that = this;
wx.showToast({
title: '上滑,取消发送',
duration: 60000,
image: '/page/common/resource/image/mic0' + i + '.png'
})
that.data.time1 = setTimeout(() => {
// 随机生成
i = util.getRandomInt(1, 5)
that.showMicAni(i)
}, delay);
},
onTouchMove(e) {
this.data.recording = true;
clearTimeout(this.data.time1)
if (e.touches[0].clientY < 520) {
this.data.cancelRecord = true;
wx.showToast({
title: '松开,取消发送',
duration: 10000,
image: '/page/common/resource/image/cancel.png'
})
} else {
this.showMicAni(1);
this.data.cancelRecord = false;
}
},
onTouchEnd(e) {
clearTimeout(this.data.time1)
if (this.data.recording) {
if (!this.data.touched) return;
this.data.touched = false;
wx.hideToast();
setTimeout(() => {
this.triggerEvent('stopRecord', {
send: !this.data.cancelRecord
});
}, 400);
} else {
wx.showToast({
title: '说话时间太短',
duration: 500,
image: '/page/common/resource/image/warn-b.png'
})
// 延时处理 防止录音未调起就触发停止
setTimeout(() => {
this.triggerEvent('stopRecord', {send: false});
}, 400);
}
this.setData({touchBtn: false})
},
})
复制代码
原生组件层级过高
canvas
- 在组件内使用画布,第二个参数必须带
wx.createCanvasContext(id, this);
复制代码
- 画面层级最高不能控制,若须被覆盖,可以用
环形实时进度条
录音授权
wx.pageScrollTo bug
- 在部分安卓机型中,例如华为等机型会在滚动的时候,触发一下拉刷新,也就是说,在触发wx.pageScrollTo之后,会先滚动到最顶端,触发刷新,再从最顶端滚动到我们需要的位置。(这是十分影响用户体验的,整体效果非常差)
- 在iphone机型中,在触发wx.pageScrollTo时,如果页面中有fixed定位的元素,定位的元素会跟随页面滚动,再回到位置上
- 将pageScrollTo的滚动动画时间duration设置为0后,在真机上,都不会出现抖动的问题,因为这个过程其实是直接跳过了,但是直接出来整体的体验也十分糟糕.