s***@outlook.com
s***@outlook.com
  • 发布:2023-08-22 19:04
  • 更新:2025-04-30 17:17
  • 阅读:620

【报Bug】长按按钮,有时候touchend 事件不触发

分类:uni-app

产品分类: uniapp/H5

PC开发环境操作系统: Mac

PC开发环境操作系统版本号: 1731.140.2.0.0 (iBridge: 19.16.16066.0.0,0)

浏览器平台: Safari

浏览器版本: 15.6.1

项目创建方式: CLI

CLI版本号: 3.0.0-alpha-3081120230719001

App下载地址或H5⽹址: https://sta9-stage.acat.ai/pages/chatRecord/index?game=20

操作步骤:

长按按钮,有时候touchend 事件不触发

预期结果:

松开以后触发 touchend 事件

实际结果:

touchend 有时候会不触发,频率较高

bug描述:

<button  
    @touchstart="longpress"  
    @touchcancel="cancel"  
    @touchend="cancel"  
    class="btn bg-transparent"  
    :class="[  
      recording  
        ? 'ballon relative transform-gpu bg-[linear-gradient(0deg,#ffffff_0%,#ffe778_50%,_#ffffff_100%)] p-0.5 drop-shadow-[0_0px_5px_#FFE778] rem:h-[112px] rem:w-[310px] rem:rounded-[56px]'  
        : ''  
    ]"  
  >  
    <view  
      :class="[recording ? 'transform-gpu cursor-pointer drop-shadow-[0_0px_5px_#FFFFFF]' : '']"  
      class="flex items-center justify-center bg-[#000000] rem:h-[112px] rem:w-[310px] rem:rounded-[56px]"  
    >  
      <image  
        v-if="!recording"  
        class="rem:h-[70px] rem:w-[50px]"  
        src="@/asstes/img/chat/audio.png"  
        mode="scaleToFill"  
        lazy-load="false"  
      />  

      <image  
        v-else  
        class="rem:h-[70px] rem:w-[50px]"  
        mode="scaleToFill"  
        lazy-load="false"  
        src="@/asstes/img/chat/audio_active.png"  
      />  
      <text class="text-white"></text>  
    </view>  
    <view v-if="recording" class="circle"></view>  
    <view v-if="recording" class="circle"></view>  
    <view v-if="recording" class="circle"></view>  
  </button>
2023-08-22 19:04 负责人:无 分享
已邀请:
Diligent_UI

Diligent_UI - 【插件开发】【专治疑难杂症】【多款插件已上架:https://ext.dcloud.net.cn/publisher?id=193663(微信搜索飘逸科技UI小程序直接体验)】【骗子请绕道】问题咨询请加QQ群:120594820,代表作灵感实用工具小程序

从文档来看,这些鼠标事件好像都没有写,用css伪类试试实现需求

叫啥好呢

叫啥好呢

解决了吗?

d***@163.com

d***@163.com

服了这玩意了

DCloud_UNI_JBB

DCloud_UNI_JBB

我长按松开手指之后并没有出现不触发 touchEnd 事件的情况,可以提供更多复现信息吗

7***@qq.com

7***@qq.com

<template>
<view>
<!-- #ifdef APP-PLUS ||MP-WEIXIN -->

    <view class="record-btn" @longpress="startRecord" @touchstart="touchStart" @touchmove="touchMove"  
        @touchend="endRecord" hover-class="record-btn-hover" hover-start-time="200" hover-stay-time="150" :style="[  
            btnStyle,  
            {  
                '--btn-hover-fontcolor': btnHoverFontcolor,  
                '--btn-hover-bgcolor': btnHoverBgcolor  
            }  
        ]">  
        <slot name="icon"></slot>  
        <text v-if="!recordPopupShow">  
        {{ btnTextContent }}</text>  
        <view class="voice-line-wrap" v-if="recordPopupShow" :style="{  
                    '--line-height': upx2px(lineHeight),  
                    '--line-start-color': lineStartColor,  
                    '--line-end-color': lineEndColor  
                }">  
                <view class="voice-line one"></view>  
                <view class="voice-line two"></view>  
                <view class="voice-line three"></view>  
                <view class="voice-line four"></view>  
                <view class="voice-line five"></view>  
                <view class="voice-line six"></view>  
                <view class="voice-line seven"></view>  
                <view class="voice-line six"></view>  
                <view class="voice-line five"></view>  
                <view class="voice-line four"></view>  
                <view class="voice-line three"></view>  
                <view class="voice-line two"></view>  
                <view class="voice-line one"></view>  
            </view>  
    </view>  
    <!-- #endif -->  
    <!-- #ifdef H5 -->  
    <view class="record-btn" @click="startRecord" hover-class="record-btn-hover" hover-start-time="200"  
        hover-stay-time="150" :style="[  
            btnStyle,  
            {  
                '--btn-hover-fontcolor': btnHoverFontcolor,  
                '--btn-hover-bgcolor': btnHoverBgcolor  
            }  
        ]">  
        <slot name="icon"></slot>  
        <text v-if="!recordPopupShow">  
            {{ btnTextContent }}</text>  
        <view class="voice-line-wrap" v-if="recordPopupShow" :style="{  
                    '--line-height': upx2px(lineHeight),  
                    '--line-start-color': lineStartColor,  
                    '--line-end-color': lineEndColor  
                }">  
                <view class="voice-line one"></view>  
                <view class="voice-line two"></view>  
                <view class="voice-line three"></view>  
                <view class="voice-line four"></view>  
                <view class="voice-line five"></view>  
                <view class="voice-line six"></view>  
                <view class="voice-line seven"></view>  
                <view class="voice-line six"></view>  
                <view class="voice-line five"></view>  
                <view class="voice-line four"></view>  
                <view class="voice-line three"></view>  
                <view class="voice-line two"></view>  
                <view class="voice-line one"></view>  
            </view>  
    </view>  
    <!-- #endif -->  
    <view   
v-if="recordPopupShow" class="record-popup"

style="{ color: recording ? '#1E1F23' : 'red' }"

{{ recording ? '松手发送,上移取消' : '松手取消' }}
</view>

</view>
</template>
<script>
var that;
// #ifdef APP-PLUS || MP-WEIXIN
const recorderManager = uni.getRecorderManager();
// #endif

// #ifdef H5
import speech from '../../js_sdk/h5-speech/speech.js';

const recorderManager = new speech(8000);
// #endif

// #ifdef APP-PLUS
// 引入权限判断
import permision from '../../js_sdk/wa-permission/permission.js';
// #endif
export default {
name: 'nbVoiceRecord',
/**

  • 录音交互动效组件
  • @property {Object} recordOptions 录音配置
  • @property {Object} btnStyle 按钮样式
  • @property {Object} btnHoverFontcolor 按钮长按时字体颜色
  • @property {String} btnHoverBgcolor 按钮长按时背景颜色
  • @property {String} btnDefaultText 按钮初始文字
  • @property {String} btnRecordingText 录制时按钮文字
  • @property {Boolean} vibrate 弹窗时是否震动
  • @property {String} popupTitle 弹窗顶部文字
  • @property {String} popupDefaultTips 录制时弹窗底部提示
  • @property {String} popupCancelTips 滑动取消时弹窗底部提示
  • @property {String} popupMaxWidth 弹窗展开后宽度
  • @property {String} popupMaxHeight 弹窗展开后高度
  • @property {String} popupFixBottom 弹窗展开后距底部高度
  • @property {String} popupBgColor 弹窗背景颜色
  • @property {String} lineHeight 声波高度
  • @property {String} lineStartColor 声波波谷时颜色色值
  • @property {String} lineEndColor 声波波峰时颜色色值
  • @event {Function} startRecord 开始录音回调
  • @event {Function} endRecord 结束录音回调
  • @event {Function} cancelRecord 滑动取消录音回调
  • @event {Function} stopRecord 主动停止录音
    */
    props: {
    recordOptions: {
    type: Object,
    default () {
    return {
    duration: 600000
    }; // 请自行查看各端的的支持情况,这里全部使用默认配置
    }
    },
    btnStyle: {
    type: Object,
    default () {
    return {
    width: '300rpx',
    height: '80rpx',
    borderRadius: '20rpx',
    backgroundColor: '#fff',
    border: '1rpx solid whitesmoke',
    permisionState: false
    };
    }
    },
    btnHoverFontcolor: {
    type: String,
    default: '#000' // 颜色名称或16进制色值
    },
    btnDefaultText: {
    type: String,
    default: '长按开始录音'
    },
    btnRecordingText: {
    type: String,
    default: '录音中'
    },
    vibrate: {
    type: Boolean,
    default: true
    },
    // #ifdef APP-PLUS || MP-WEIXIN
    popupTitle: {
    type: String,
    default: '正在录制音频'
    },
    popupDefaultTips: {
    type: String,
    default: '左右滑动后松手完成录音'
    },
    // #endif
    // #ifdef H5
    popupTitle: {
    type: String,
    default: '点击录制音频'
    },
    popupDefaultTips: {
    type: String,
    default: '点击完成录音'
    },
    // #endif
    popupCancelTips: {
    type: String,
    default: '松手取消录音'
    },
    popupMaxWidth: {
    type: Number,
    default: 600 // 单位为rpx
    },
    popupMaxHeight: {
    type: Number,
    default: 300 // 单位为rpx
    },
    popupFixBottom: {
    type: Number,
    default: 200 // 单位为rpx
    },
    popupBgColor: {
    type: String,
    default: 'whitesmoke'
    },
    lineHeight: {
    type: Number,
    default: 50 // 单位为rpx
    },
    lineStartColor: {
    type: String,
    default: 'royalblue' // 颜色名称或16进制色值
    },
    lineEndColor: {
    type: String,
    default: 'indianred' // 颜色名称或16进制色值
    }
    },
    data() {
    return {
    btnHoverBgcolor:'#0076fb',
    stopStatus: true, // 是否已被父页面通知主动结束录音
    btnTextContent: '按住说话',
    startTouchData: {},
    popupHeight: '0px', // 这是初始的高度
    recording: false, // 修改初始值为 false
    recordPopupShow: false,
    recordTimeout: null, // 录音定时器
    h5start: false
    };
    },
    created() {
    that = this;

    // 请求权限  
    this.checkPermission();  
    // #ifdef APP-PLUS || MP-WEIXIN  
    recorderManager.onStop((res) => {  
        // 判断是否用户主动结束录音  
        if (that.recordTimeout !== null) {  
            // 延时未结束,则是主动结束录音  
            clearTimeout(that.recordTimeout);  
            that.recordTimeout = null; // 恢复状态  
        }  
        // 继续判断是否为取消录音  
        if (that.recording) {  
            that.$emit('endRecord', res);  
        } else {  
        this.btnHoverBgcolor = '#0076fb'; // 每次开始录音时重置为蓝色  
            // 用户向上滑动,此时松手后响应的是取消录音的回调  
            that.recording = true; // 恢复状态  
            that.$emit('cancelRecord');  
        }  
        that.recording = false; // 无论是正常结束还是取消,都设置为 false  
    });  
    recorderManager.onStart((err) => {  
        console.log('开始:', err);  
    });  
    recorderManager.onError((err) => {  
        console.log('err:', err);  
    });  
    // #endif  

    },
    computed: {},
    methods: {
    upx2px(upx) {
    return uni.upx2px(upx) + 'px';
    },
    async checkPermission() {
    var that = this;
    // #ifdef APP-PLUS
    // 先判断os
    let os = uni.getSystemInfoSync().osName;
    if (os == 'ios') {
    this.permisionState = await permision.judgeIosPermission('record');
    } else {
    this.permisionState = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
    }
    if (this.permisionState !== true && this.permisionState !== 1) {
    uni.showToast({
    title: '请先授权使用录音',
    icon: 'none'
    });
    return;
    }
    // #endif
    // #ifdef H5
    if (!window.navigator?.mediaDevices?.getUserMedia) {
    this.permisionState = false;
    uni.showToast({
    title: '请先授权使用录音',
    icon: 'none'
    });
    return;
    } else {
    this.permisionState = true;
    }
    // #endif

        // #ifdef MP-WEIXIN  
        uni.authorize({  
            scope: 'scope.record',  
            success(e) {  
                that.permisionState = true;  
                // that.startRecord();  
            },  
            fail() {  
                uni.showToast({  
                    title: '请授权使用录音',  
                    icon: 'none'  
                });  
            }  
        });  
        // #endif  
    },  
    
    startRecord() {  
        if (!this.permisionState) {  
            this.checkPermission();  
            return;  
        }  
        // #ifdef H5  
        if (this.h5start) {  
            this.h5start = false;  
            this.endRecord();  
            return;  
        }  
        this.h5start = true;  
        // #endif  
        this.stopStatus = false;  
        this.recording = true; // 开始录音时设置为 true  
        this.btnHoverBgcolor = '#0076fb'; // 每次开始录音时重置为蓝色  
        setTimeout(() => {  
            this.popupHeight = this.upx2px(this.popupMaxHeight);  
            setTimeout(() => {  
                this.recordPopupShow = true;  
                if (this.vibrate) {  
                    // #ifdef APP-PLUS  
                    // 震动  
                    plus.device.vibrate(35);  
                    // #endif  
                    // #ifdef MP-WEIXIN  
                    uni.vibrateShort();  
                    // #endif  
                }  
                // 开始录音  
                recorderManager.start(this.recordOptions);  
                // 设置定时器  
                this.recordTimeout = setTimeout(  
                    () => {  
                        // 如果定时器先结束,则说明此时录音时间超限  
                        this.stopRecord(); // 结束录音动画(实际上录音的end回调已经先执行)  
                        this.recordTimeout = null; // 重置  
                    },  
                    this.recordOptions.duration ? this.recordOptions.duration : 600000  
                );  
    
                this.$emit('startRecord');  
            }, 100);  
        }, 200);  
    },  
    endRecord() {  
        var that = this;  
        if (this.stopStatus) {  
            return;  
        }  
        this.popupHeight = '0px';  
        this.recordPopupShow = false;  
        // #ifdef APP-PLUS || MP-WEIXIN  
        recorderManager.stop();  
        setTimeout(() => {  

    this.recording = false;
    }, 100);
    // #endif
    // #ifdef H5
    const res = recorderManager.stop();
    if (that.recordTimeout !== null) {
    // 延时未结束,则是主动结束录音
    clearTimeout(that.recordTimeout);
    that.recordTimeout = null; // 恢复状态
    }

        // 继续判断是否为取消录音  
        if (that.recording) {  
            that.$emit('endRecord', res);  
        } else {  
            this.btnHoverBgcolor = '#0076fb'; // 每次开始录音时重置为蓝色  
            // 用户向上滑动,此时松手后响应的是取消录音的回调  
            that.recording = true; // 恢复状态  
            that.$emit('cancelRecord');  
        }  
        // #endif  
    },  
    stopRecord() {  
        // 用法如你录音限制了时间,那么将在结束时强制停止组件的显示  
        this.endRecord();  
        this.stopStatus = true;  
    },  
    touchStart(e) {  
        this.startTouchData.clientX = e.changedTouches[0].clientX; //手指按下时的X坐标  
        this.startTouchData.clientY = e.changedTouches[0].clientY; //手指按下时的Y坐标  
    },  
    touchMove(e) {  
        let touchData = e.touches[0]; //滑动过程中,手指滑动的坐标信息 返回的是Objcet对象  
        let moveX = touchData.clientX - this.startTouchData.clientX;  
        let moveY = touchData.clientY - this.startTouchData.clientY;  
        if (moveY < -50) {  
            if (this.vibrate && this.recording) {  
                // #ifdef APP-PLUS  
                plus.device.vibrate(35);  
                // #endif  
                // #ifdef MP-WEIXIN  
                uni.vibrateShort();  
                // #endif  
            }  
            this.recording = false;  
            this.btnHoverBgcolor = '#ff0000'; // 向上滑动时变红  
        } else {  
            this.recording = true;  
            this.btnHoverBgcolor = '#0076fb'; // 恢复蓝色  
        }  
    }  

    }
    };
    </script>
    <style lang="scss">
    .record-btn {
    color: #000;
    font-size: 24rpx;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: 0.25s all;
    border: 1rpx solid whitesmoke;
    }

.record-btn-hover {
color: var(--btn-hover-fontcolor) !important;
background-color: var(--btn-hover-bgcolor) !important;
}

.record-popup {
position: absolute;
width: 100%;
color: rgb(30, 31, 35);
top: -60rpx;
text-align: center;
}

.cancel-icon {
width: 100rpx;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 44rpx;
line-height: 44rpx;
background-color: pink;
border-radius: 50%;
transform: rotate(45deg);
}

.voice-line-wrap {
display: flex;
align-items: center;
position: absolute;
left: 0;
top: 0;
justify-content: center;
width: 100%;
height: 100%;
.voice-line {
width: 5rpx;
height: var(--line-height);
border-radius: 3rpx;
margin: 0 5rpx;
}

.one {  
    animation: wave 0.4s 1s linear infinite alternate;  
}  

.two {  
    animation: wave 0.4s 0.9s linear infinite alternate;  
}  

.three {  
    animation: wave 0.4s 0.8s linear infinite alternate;  
}  

.four {  
    animation: wave 0.4s 0.7s linear infinite alternate;  
}  

.five {  
    animation: wave 0.4s 0.6s linear infinite alternate;  
}  

.six {  
    animation: wave 0.4s 0.5s linear infinite alternate;  
}  

.seven {  
    animation: wave 0.4s linear infinite alternate;  
}  

}

@keyframes wave {
0% {
transform: scale(1, 1);
background-color: var(--line-start-color);
}

100% {  
    transform: scale(1, 0.2);  
    background-color: var(--line-end-color);  
}  

}
</style>

要回复问题请先登录注册