首页 > 其他分享 >记录--移动端的双击事件好不好用?

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

时间:2023-09-04 18:34:31浏览次数:37  
标签:-- 双击 好不好 点击 isWaiting 事件 evt click

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

前言

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

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

 

标签:--,双击,好不好,点击,isWaiting,事件,evt,click
From: https://www.cnblogs.com/smileZAZ/p/17677788.html

相关文章

  • 神策数据 CJO 系列丨解密 CJO:连接体验的下一个前沿趋势
    10余年前,市场营销的焦点聚集在增长黑客如何利用AARRR模型(获取Acquisition、激活Activation、留存Retention、收入Revenue、传播Referral)来推动并加速企业的生长发展。我们曾相信,在AARRR漏斗中,只要我们吸引了足够的目光,就能实现令人满意的转化。然而,如今我们身处一个触点......
  • ChatGLM2 源码解析:`ChatGLMModel`
    #完整的GLM模型,包括嵌入层、编码器、输出层classChatGLMModel(ChatGLMPreTrainedModel):def__init__(self,config:ChatGLMConfig,device=None,empty_init=True):super().__init__(config)#如果设置了`empty_init`,创建任何PyTorch模块时,不初......
  • 利星行汽车携手南凌科技 拥抱网络数字化变革
    随着信息技术的高速发展,很多企业已经意识到数字化转型的必要性。尤其值得注意的是,以往被忽略的网络价值,在近年来门店运营成本急剧增加的压力下愈发受到重视。在中国汽车流通行业百强中名列前茅的经销商集团利星行汽车,一场网络数字化变革正在酝酿。01一场网络数字化的探索之旅在中国......
  • 什么是瓷片电容封装 | 百能云芯
    瓷片电容封装是一种常见的电子元件封装方式,它广泛应用在电子设备中,用于存储和释放电荷,以实现电路的稳定工作。在本文中,我们将详细介绍瓷片电容封装的特点以及用途。瓷片电容封装的特点:瓷片电容是一种以陶瓷材料为基础制成的电容器。其封装通常采用了一些非常特定的工艺......
  • npm install总是到最后不动
    踩坑日记npminstall总是到最后不动,最后发现是npm淘宝镜像源地址更新了,所以我们同步一下即可;解决问题方法,npm淘宝镜像源换最新的//切换新的镜像源npmconfigsetregistryhttps://registry.npmmirror.com......
  • 工作效率
    标题:计算机科技提升工作效率的重要性与方法引言:在当今数字化时代,计算机科技已经成为了改变人们工作方式和提高工作效率的重要工具。计算机的普及和应用使得各行各业都能够更高效地完成任务,加速工作进程,节省时间和资源。本文将探讨计算机科技对提升工作效率的重要性,并介绍一些使用计......
  • ChatGLM2 源码解析:`ChatGLMForConditionalGeneration.forward`
    classChatGLMForConditionalGeneration(ChatGLMPreTrainedModel):def__init__(self,config:ChatGLMConfig,empty_init=True,device=None):super().__init__(config)self.max_sequence_length=config.max_lengthself.transformer=C......
  • 无涯教程-JavaScript - DCOUNTA函数
    描述DCOUNTA函数返回列表或数据库中符合您指定条件的列中非空白单元格的计数。此函数与DCOUNT函数相似,不同之处在于DCOUNTA函数对所有非空白单元进行计数。DCOUNT函数仅计算包含数值的单元格。语法DCOUNTA(database,field,criteria)争论Argument描述Required/Opti......
  • The colossus
    BYSYLVIAPLATHIshallnevergetyouputtogetherentirely,Pieced,glued,andproperlyjointed.Mule-bray,pig-gruntandbawdycacklesProtectedfromyourgreatlips.It'sworsethanabarnyardPehapsyouconsideryourselfanoracle,Mouthpiecep......
  • 直线导轨中高组装和低组装有什么不同?
    直线导轨组合高度类型首要有高组装型和低组装型这2类,顾名思义,高组装型的组合高度(滑轨的底面到滑块的顶面)要高一些,而低组装型要低一些,视规范大小差异在2~7mm之间,造成这个差异的原因是滑块高度规范不同,一般与滑轨无关。高组装直线导轨是一种四列式单圆弧齿形接触直线导轨,也是一种结......