记录--移动端的双击事件好不好用?

news2024/12/27 21:35:46

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

2023年了,我不允许还有人不会自己实现移动端的双击事件。

过来,看这里,不足 50 行的代码实现的双击事件。

听笔者娓娓道来。

dblclick

js原生有个dblclick双击事件,但是几乎不支持移动端。

 而且,该dblclick事件在pc端鼠标双击时,会触发两次click与一次dblclick

window.addEventListener('click', () => {
    console.log('click')
});
window.addEventListener('dblclick', () => {
    console.log('dblclick')
});

// 双击页面,打印:click✖️2 dblclick

我们期望可以在移动端也能有双击事件,并且隔离单击与双击事件,双击时只触发双击事件,只执行双击回调函数,让注册双击事件像注册原生事件一样简单。

点击穿透

简单聊聊移动端的点击穿透。

在移动端单击会依次触发touchstart->touchmove->touchend->click事件。

有这样一段逻辑,在touchstart时出现全屏弹框,在click弹框时关闭弹框。实际上,在点击页面时,弹框会一闪而过,并没有出现正确的交互。在移动端单击时touchstart早于click,当弹框出现了,后来的click事件就落在了弹框上,导致弹框被关闭。这就是点击穿透的一种表现。

笔者的业务需求是双击元素,出现全屏弹框,单击弹框时关闭弹框。因此基于这样的业务需求与现实的点击穿透问题,笔者选择采用click事件来模拟双击事件,并且适配pc端使用。大家也可以选择解决点击穿透问题,并采用touchstart模拟双击事件,可以更快地响应用户操作。

采用touchstart模拟时,可以再考虑排除双指点击的情况。

在实现上与下文代码除了事件对象获取位置属性有所不同外,其它代码基本一致,实现思路无差别。

模拟双击事件

采用click事件来模拟实现双击。

双击事件定义:2次点击事件间隔小于200ms,并且点击范围小于10px的视为双击。这里的双击事件是自定义事件,为了区分原生的 dblclick,又优先满足移动端使用,则事件名定义为 dbltouch,后续可以使用window.addEventListener('dbltouch', ()=>{})来监听双击事件。

这个间隔与位移限制大家可以根据自己的业务需求调整。通常采用的是300ms的间隔与10px的位移,笔者业务中发现200ms间隔也可使用。

自定义事件名大家可以随意设置,满足语义化即可。

  1. 监听click事件,并在捕获阶段监听,目的是为了后续能够阻止click事件传播。

window.addEventListener('click', handler, true);

监听函数中,第1次点击时,记录点击位置,并设置200ms倒计时。如果第2次点击在200ms后,则重新派发当前事件,让事件继续传播,使其它的监听函数可以继续处理对应事件。

// 标识是否在等待第2次点击
let isWaiting = false;

// 记录点击位置
let prevPosition = {};

function handler(evt) {
    const { pageX, pageY } = evt;
    prevPostion = { pageX, pageY };
    // 阻止冒泡,不让事件继续传播
    evt.stopPropagation();
    // 开始等待第2次点击
    isWaiting = true;
    // 设置200ms倒计时,200ms后重新派发当前事件
    timer = setTimeout(() => {
        isWaiting = false;
        evt.target.dispatchEvent(evt);
    }, 200)
}

注意: 倒计时结束时evt.target.dispatchEvent(evt)派发的事件仍是原来的事件对象,即仍是click事件,会触发继续handler函数,进入了循环。

这里需要破局,已知Event事件对象下有一个 isTrusted 属性,是一个只读属性,是一个布尔值。当事件是由用户行为生成的时候,这个属性的值为 true ,而当事件是由脚本创建、修改、通过 EventTarget.dispatchEvent()派发的时候,这个属性的值为 false 。

因此,此处脚本派发的事件是希望继续传递的事件,不用handler内处理。

function handler(evt) {
    // 如果事件是脚本派发的则不处理,将该事件继续传播
    if(!evt.isTrusted){
        return;
    }
}

处理完第1次点击后,接着处理在200ms内的第2次点击事件。如果满足位移小于10px的条件,则视为双击。

// 标识是否在等待第2次点击
let isWaiting = false;

// 记录点击位置
const prevPosition = {};

function handler(evt) {
    // 如果事件是脚本派发的则不处理,将该事件继续传播
    if(!evt.isTrusted){
        return;
    }
    const { pageX, pageY } = evt;
    if(isWaiting) {
        isWaiting = false;
        const diffX = Math.abs(pageX - prevPosition.pageX);
        const diffY = Math.abs(pageY - prevPosition.pageY);
        // 如果满足位移小于10,则是双击
        if(diffX <= 10 && diffY <= 10) {
            // 取消当前事件传递,并派发1个自定义双击事件
            evt.stopPropagation();
            evt.target.dispatchEvent(
                new PointerEvent('dbltouch', {
                    cancelable: false,
                    bubbles: true,
                })
            )
        }
    } else {
        prevPostion = { pageX, pageY };
        // 阻止冒泡,不让事件继续传播
        evt.stopPropagation();
        // 开始等待第2次点击
        isWaiting = true;
        // 设置200ms倒计时,200ms后重新派发当前事件
        timer = setTimeout(() => {
            isWaiting = false;
            evt.target.dispatchEvent(evt);
        }, 200)
    }
}

以上便实现了双击事件,全局任意地方监听双击。

window.addEventListener('dbltouch', () => {
    console.log('dbltouch');
})
window.addEventListener('click', () => {
    console.log('click');
})
// 使用鼠标、手指双击
// 打印出 dbltouch
// 而且不会打印有click

笔者要在这里说句 但是: 由于200ms的延时,虽不多,但是对于操作迅速的用户来讲,还是会有不好的体验。

优化双击事件

由于是在window上注册的click函数,虽说注册双击事件像单击事件一样简单了,但却也导致整个产品页面的click事件都会推迟200ms执行。

因此,我们应该只对需要处理双击的地方添加双击事件,至少只在局部发生延迟情况。稍微调整下代码,将需要注册双击事件的元素由开发决定,通过参数传递。而且事件处理函数也可以通过参数传递,即可以通过监听双击事件,也可以通过回调函数执行。

以下是完整的代码。

class RegisterDbltouchEvent {
    constructor(el, fn) {
        this.el = el || window;
        this.callback = fn;
        this.timer = null;
        this.prevPosition = {};
        this.isWaiting = false;
        
        // 注册click事件,注意this指向
        this.el.addEventListener('click', this.handleClick.bind(this), true);
    }
    handleClick(evt){
        if(this.timer) {
            clearTimeout(this.timer);
            this.timer = null;
        }
        if(!evt.isTrusted) {
            return;
        };
        if(this.isWaiting){
            this.isWaiting = false;
            const diffX = Math.abs(pageX - this.prevPosition.pageX);
            const diffY = Math.abs(pageY - this.prevPosition.pageY);
            // 如果满足位移小于10,则是双击
            if(diffX <= 10 && diffY <= 10) {
                // 取消当前事件传递,并派发1个自定义双击事件
                evt.stopPropagation();
                evt.target.dispatchEvent(
                    new PointerEvent('dbltouch', {
                        cancelable: false,
                        bubbles: true,
                    })
                );
                // 也可以采用回调函数的方式
                this.callback && this.callback(evt);
            }
        } else {
            this.prevPostion = { pageX, pageY };
            // 阻止冒泡,不让事件继续传播
            evt.stopPropagation();
            // 开始等待第2次点击
            this.isWaiting = true;
            // 设置200ms倒计时,200ms后重新派发当前事件
            this.timer = setTimeout(() => {
                this.isWaiting = false;
                evt.target.dispatchEvent(evt);
            }, 200)
        }
    }
}

只为需要实现双击逻辑的元素注册双击事件。可以通过传递回调函数的方式执行业务逻辑,也可以通过监听dbltouch事件的方式,也可以同时使用,it's up to you.

const el = document.querySelector('#dbltouch');
new RegisterDbltouchEvent(el, (evt) => {
    // 实现双击逻辑
})

本文转载于:

https://juejin.cn/post/7274043371731796003

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/972587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大文件上传demo,前端基于Uppy,后端基于koa

前言 文件上传基本上所有的管理系统之类的项目都有这么一个功能。因为使用了Element&#xff0c;可以方便的使用 其提供的Upload组件&#xff0c;对于普通上传来说基本上就够用了。但是有时候会涉及到大文件上传的需求&#xff0c;这时就会面临一些问题&#xff1a;比如文件上…

使用QPixmap显示图片

在QT中&#xff0c;经常需要我们显示图片&#xff08;作为背景&#xff0c;游戏元素&#xff0c;菜单背景&#xff0c;等待&#xff09;。 本文将用最简洁的方法介绍如何将图片素材显示在指定控件的指定位置。 一.基础知识 QPixmap 该类可以加载&#xff08;load&#xff0…

算法:合并两个有序数组---双指针[1]

1、题目&#xff1a; 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&a…

什么是websockret连接

什么是WebSocket WebSocket&#xff0c;是一种网络传输协议&#xff0c;位于 OSI 模型的应用层。可在单个 TCP 连接上进行全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通迅 客户端和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&am…

一种结合白平衡统计信息和曝光信息的软光敏算法专利学习(专利三)

基础理论&#xff1a; 亮度计算&#xff1a; 对于白天模式而言&#xff0c;IR滤光片处于过滤红外光的状态&#xff0c;也就是说&#xff0c;摄像机的感光芯片所感受到的环境光中的红外光几乎为零&#xff1b;而对于夜晚模式而言&#xff0c;IR滤光片处于不过滤红外光的状态&am…

ABB 3BHB002916R0001 UFC721AE模拟输入卡

通道数目&#xff1a; UFC721AE 模拟输入卡通常具有多个输入通道&#xff0c;可以同时监测多个模拟信号。 输入类型&#xff1a; 这种卡片通常支持不同类型的模拟输入&#xff0c;例如电压信号、电流信号或其他传感器信号。 精度&#xff1a; UFC721AE 模拟输入卡通常具有高精…

树莓 LUMA-OLED.EXAMPLE使用

详细介绍在文件目录下的README.rst中 第一步 $ sudo usermod -a -G i2c,spi,gpio pi //好像没什么用 $ sudo apt install python3-dev python3-pip python3-numpy libfreetype6-dev libjpeg-dev build-essential //安装依赖包&#xff0c;树莓派中好像已经有了 $ sudo a…

Seata 笔记

Seata 笔记 分布式事务理论基础 CAP 定理 Consistency 一致性&#xff1a;用户访问分布式系统中的任意节点得到的结果都是一致的Availability 可用性&#xff1a;用户和访问任意健康节点都必须得到响应而不是超时拒绝Partition tolernance 分区容错性&#xff1a;出现独立分…

解决本地jar包导入maven

1、确定是否安装maven 2、输入导入命令 命令说明 <path-to-file>为你jar包所在的路径&#xff08;尽量简单并且不要含中文&#xff09; <group-id>为grouId号&#xff0c;与<artifact-id>组成唯一识别你jar包的坐标&#xff0c;当不在公共资源jar包中&#…

libbpf-bootstrap安卓aarch64适配交叉编译

1.为什么移植 疑惑 起初我也认为&#xff0c;像libbpf-bootstrap这样在ebpf程序开发中很常用的框架&#xff0c;理应支持不同架构的交叉编译。尤其是向内核态的ebpf程序本身就是直接通过clang的-target btf直接生成字节码&#xff0c;各个内核上的ebpf虚拟机大同小异&#xf…

万字解读 Android 车机核心 :CarService 的构成和链路~

前言 关于 Android 车机&#xff0c;之前分析过方控上自定义按键的输入机制和中控上旋钮输入的原理&#xff0c;但都局限于 Car Service 内 Input 相关模块。 一文了解 Android 车机如何处理中控的旋钮输入从实体按键看 Android 车载的自定义事件机制 本文将结合 Android 系…

LabVIEW开发感应电机在线匝间短路故障诊断系统

LabVIEW开发感应电机在线匝间短路故障诊断系统 工业中使用的超过85%的电动机是三相感应电动机。它们因其可靠性、设计便利性、高性能和过载能力而被广泛用于不同的应用&#xff0c;例如制造、加工、电力系统、运输等。无论它们的能力如何&#xff0c;它们都被认为是现代工业学…

Consider defining a bean of type问题解决

Consider defining a bean of type问题解决 Consider defining a bean of type问题解决 包之后&#xff0c;发现项目直接报错Consider defining a bean of type。 会有一些包你明明Autowired 但是还是找不到什么bean 导致你项目启动不了 解决方法一: 这个问题主要是因为项目拆包…

安卓 MeasureCache优化了什么?

安卓绘制原理概览_油炸板蓝根的博客-CSDN博客 搜了一下&#xff0c;全网居然没有人提过 measureCache。 在前文中提到过&#xff0c;measure的时候&#xff0c;如果命中了 measureCache&#xff0c;会跳过 onMeasure&#xff0c;同时会设置 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOU…

【已解决】oracle获取最近2学年的数据

已解决 &#xff1a;oracle获取最近2学年的数据 SELECT * FROM (SELECT * FROM xx.JWXT_XSKB WHERE AND xn IN (‘2023-2024’,‘2022-2023’)); 问题 某某系统课表数据过大&#xff0c;要求只获取最近2学年的数据&#xff0c;不能写死。 思路 mysql 在子查询的WHERE子句中…

从0到1学会Git(第二部分):Git的本地操作和管理

写在前面:本文介绍了在本地仓库进行文件的处理以及本地的合并等操作。 前置知识:文件可以处在三个区域&#xff0c;分别为工作区&#xff0c;暂存区和本地仓库&#xff0c;我们此文的目标即是将文件存储在本地仓库中。我们可以将文件的区域理解为&#xff0c;cpu中&#xff0c…

苍穹外卖技术栈

重难点详解 1、定义全局异常 2、ThreadLocal ThreadLocal 并不是一个Thread&#xff0c;而是Thread的一个局部变量ThreadLocal 为每一个线程提供独立的存储空间&#xff0c;具有线程隔离的效果&#xff0c;只有在线程内才能取到值&#xff0c;线程外则不能访问 public void …

linux入门---动静态库的加载

目录标题 为什么会有动态库和静态库静态库的实现动态库的实现动静态库的加载 为什么会有动态库和静态库 我们来模拟一个场景&#xff0c;首先创建两个头文件 根据文件名便可以得知add.h头文件中存放的是加法函数的声明&#xff0c;sub.h头文件中存放的是减法函数的声明&#…

【每日运维】U盘启动盘安装 ESXi 6.7.0 安装卡在 loading /bnxtroce.v00

问题描述 ● ESXi 6.7.0 安装进度卡在loading /bnxtroce.v00 进度处 处理方法 ● 重新制作启动盘&#xff0c;写入方式改为&#xff1a;【USB-ZIPv2】 ● 设置服务器的 bios设置&#xff0c;启动方式改为【UEFI】 ● 重启开机安装即可

蛋白与蛋白互作预测 蛋白互作预测protein

How to prepare structures for HADDOCK? – Bonvin Labhttps://www.bonvinlab.org/software/bpg/structures/RosettaDock: 蛋白-蛋白复合物对接预测 - 知乎 (zhihu.com) 要进行LPR1-SEPP1复合物的结合亲和力预测&#xff0c;您可以按照以下步骤进行&#xff1a; 获取蛋白质结…