当前位置: 首页 > news >正文

Bros!使用 focus 和 blur 事件时别忽略了这一点!

在这里插入图片描述

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

最近小伙伴又遇到一个需求,那就是给复选框选中时添加一个边框样式,大致如下:

原本的效果:

image.png

现在需要的效果:

image.png

这个外边框的隐藏时机为:

  • 复选框未选中
  • 点击 复选框内容外 的区域

06CE110E.gif

小伙伴很快就完成了需求,但 测试同学反馈了如下的 BUG

  • 正常速度点击

    1.gif

  • 快速来回点击

    2.gif

很明显快速点击时 外部边框 并没有按预期效果进行 展示/隐藏,为了更好的复现问题,接下来重新实现一遍功能,然后再解决问题。

08671C34.jpg

实现 checkbox 组件

由于这个 checkbox 使用的是内部业务组件,并且是通过 JSX 语法 来实现,所以这里就不使用 template 模版 语法了。

核心分析

内容比较简单,要自己实现一个 checkbox 核心无非以下几点。

使用 <label> 关联 <input />

  • 为了实现点击 文字部分 也能达到直接点击 checkbox 的效果,就可以通过 <label> 标签来实现,而关联方式又分为两种
    • <input>id<label>for 属性保持一致
        <label for="cheese">Do you like cheese?</label><input type="checkbox" name="cheese" id="cheese" />
      
    • <label> 直接包裹 <input>
        <label>Click me <input type="text" /></label>
      

自定义 checkbox 样式

为了统一展示样式,因此都会使用 span 元素 来替换 原始的 checkbox,并且将 原始 checkbox 隐藏起来,例如 Ant DesignElement UI 中的复选框。

image.png

支持 v-model 双向绑定

自行封装的组件为了方便外部使用,需要支持 v-model 数据双向绑定的形式,而在组件内部只需要将对应的 propsemits 事件进行定义即可,如下:

export default defineComponent({props: {modelValue: {type: Boolean,default: false}},emits: ["update:modelValue"],...
})

效果展示

如下的效果中没有展示鼠标点击位置,实际点击位置包括:

  • checkbox 本身,展示选中样式,包括 外边框填充样式
  • checkbox 文字部分,隐藏 外边框
  • checkbox 外的区域,隐藏 外边框

3.gif

代码大致如下:

// ChcekBox.tsx
import { defineComponent, ref } from 'vue'export default defineComponent({name: 'ChcekBox',props: {modelValue: {type: Boolean,default: false}},emits: ["update:modelValue"],setup(props, { slots, emit }) {const blur = ref(false);const onChange = (e) => {emit('update:modelValue', e.target.checked)}const onFocus = () => {blur.value = false;}const onBlur = () => {blur.value = true;}return () => (<label class="checkbox-wrapper"><span class={["checkbox", props.modelValue && !blur.value && "checkbox-checked"]}><inputclass="checkbox-input"type="checkbox"name="checkbox"checked={props.modelValue}onBlur={onBlur}onFocus={onFocus}onChange={onChange}/><span class={["checkbox-inner", props.modelValue && "checkbox-inner-checked"]}></span></span><span class="checkbox-label">{slots.default ? slots.default() : ''}</span></label>)}
});// ChcekBox.less
.checkbox-wrapper {display: flex;align-items: center;cursor: pointer;.abs {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);height: 16px;width: 16px;}.checkbox {position: relative;margin-right: 5px;height: 16px;width: 16px;padding: 10px;border-radius: 4px;border: 1.5px solid transparent;&-checked{border-color: #1677ff;}&-inner {.abs();border: 1px solid #1677ff;border-radius: 4px;&-checked {background-color: #1677ff;border-color: #1677ff;}&::after {box-sizing: border-box;position: absolute;top: 50%;inset-inline-start: 21.5%;display: table;width: 6px;height: 9px;border: 2px solid #fff;border-top: 0;border-inline-start: 0;content: " ";transform: rotate(45deg) scale(1) translate(-50%, -50%);transition: all 0.2s cubic-bezier(0.12, 0.4, 0.29, 1.46) 0.1s;}}&-input {.abs();opacity: 0;}}
}

分析/解决 外边框展示 BUG

再来回顾下 快速来回点击 时的展示效果:

2.gif

然后回顾以上实现的代码中,不难发现是想通过 checkbox 上的 onFocusonBlur 事件被触发时来控制 外边框显示/隐藏

那么现在 显示/隐藏 出现问题,很明显和 onFocusonBlur 事件是有关系的。

focus 和 blur 事件

focus 事件在元素 获取焦点 时触发,该事件 不可取消,也 不会冒泡

blur 事件在一个元素 失去焦点 时被触发,该事件 不可取消,也 不会冒泡

还有一个最容易被忽略的点,那就是它们必须由 另一状态 变化到 当前状态 时才会被触发,例如:

  • 本身处于 聚焦状态 时,继续进行 聚焦动作 时,不会触发 focus 事件
  • 本身处于 失焦状态 时,继续进行 失焦动作 时,不会触发 blur 事件

分析问题

首先从布局上来讲 元素 <input class="checkbox-input" />元素 <span class="checkbox-inner"> 都使用了 绝对定位 position,并且 后者 会覆盖在 前者 之上。

事件冒泡

那么我们现在的点击操作实际上直接操作的是 元素 <span class="checkbox-inner">,那么为什么还能触发在 元素 <input class="checkbox-input" /> 绑定的 focusblur 事件呢?

这个倒也不难,因为有 事件冒泡,所以这个点击操作能被传递到底层的 <input /> 元素,自然就可以触发相应的事件。

focus 和 blur 不触发

当进行 快速点击 时,会发现 focusblur 事件都不会触发,如下:

5.gif

很明显就是因为这两个事件没有执行而导致展示有问题。

071C7D88.gif

那么疑问是什么这两个事件没有被执行呢?

如果从状态变更上来说,最后一次触发的是 blur 事件,那么后续 blur 事件不触发是可以理解的,毕竟状态没有被改变,但是 focus 事件却没有被执行,这一点是不应该的。

关于这个问题暂未查询相关资料,有知道的掘友可以在评论区分享见解。

解决问题

知道了原因,那么就很容易进行调整了,虽然 focusblur 不能一直被触发,但是 change 事件却不受影响,如下:

6.gif

因此,只需要将 外边框的显示/隐藏 相关逻辑迁移到 onChange 中即可,如下:

  • 为了保证 onBlur 事件能够被正常执行,在 onChange 被触发时我们应该通过 e.target.focus() 去触发 外边框显示逻辑,目的是改变 checkbox 中的 聚焦/失焦 状态,正如前面说的只有 当前状态下一状态 不同才能触发相应事件

既然 onChange 事件不受影响,那还要 onBluronFocus 干嘛?

这个也是评论区掘友提出的疑问,为了避免大家都有这个疑问,统一在这里解释。

在回头简单看下需求:

  • 点击 checkbox 本身,展示选中样式,包括 外边框填充样式
  • 点击 checkbox 外的区域,隐藏 外边框

那么我们知道,当点击 checkbox 本身 时是会一直触发 onChange 事件,从这个角度来讲确实不需要 onBluronFocus

但值得注意的是,当需要再点击 checkbox 外的区域 隐藏外边框时,onChange 事件就无法执行了,因为此时 checkbox 选中状态是没有发生变更的,而这个操作就很适合 onBlur 事件的触发时机,所以我们需要 onBlur 事件。

由于需要 onBlur 事件,但是又因为前面提到当快速点击时会存在不触发 onFocus 和 onBlur 事件的问题,因此我们才需要在 onChange 中去手动触发 checkboxonFocus 事件,只有这样,当我们在点击 checkbox 外的区域 时才能触发其 onBlur 事件。

import { defineComponent, ref } from 'vue'export default defineComponent({name: 'ChcekBox',props: {modelValue: {type: Boolean,default: false}},emits: ["update:modelValue"],setup(props, { slots, emit }) {const blur = ref(props.modelValue);const onChange = (e) => {e.target.focus();emit('update:modelValue', e.target.checked)}const onFocus = () => {blur.value = false;}const onBlur = () => {blur.value = true;}return () => (<label class="checkbox-wrapper"><span class={["checkbox", props.modelValue && !blur.value && "checkbox-checked"]}><inputclass="checkbox-input"type="checkbox"name="checkbox"checked={props.modelValue}onBlur={onBlur}onFocus={onFocus}onChange={onChange}/><span class={["checkbox-inner", props.modelValue && "checkbox-inner-checked"]} onClick={()=>console.log('click in checkbox-inner')}></span></span><span class="checkbox-label">{slots.default ? slots.default() : ''}</span></label>)}
})

最终效果如下:

7.gif

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

综上所述,当需要在项目中使用 focusblur 事件实现相关需求时,要格外注意,特别是在 safari 浏览器IOS 设备 等环境中都可能会存在 focusblur 事件不生效的情况。

希望本文对你有所帮助!!!

074F3CCD.jpg


http://www.mrgr.cn/news/57464.html

相关文章:

  • CentOS7安装RabbitMQ-3.13.7、修改端口号
  • Java项目-基于springboot框架的逍遥大药房管理系统项目实战(附源码+文档)
  • 关于容器docker使用基本命令
  • 苍穹外卖学习笔记(三十二最终篇)
  • scala 抽象类
  • 如何让别人喜欢你的代码
  • CentOS 6 修改 yun 源
  • 【Linux】 su 和 sudo 的区别剖析
  • C#,自动驾驶技术,ASAM OpenDRIVE BS 1.8.0 规范摘要与C# .NET Parser
  • 农业自动气象监测站的工作原理
  • 深入解析MySQL数据库:从基础到进阶的全面剖析
  • 哥德巴赫猜想渐行渐远
  • 《1024:致敬程序员的数字乐章》
  • Mitre ATTCK攻击技术-权限维持-定时任务
  • Flutter鸿蒙next 刷新机制的高级使用【衍生详解】
  • 【.Net】【C#】Program.cs通用代码模板
  • 企业办公文件加密软件推荐!10款企业常用文件加密软件排行榜!
  • Clickhouse 笔记(一) 单机版安装并将clickhouse-server定义成服务
  • angular-electron调用java
  • 企业团队经典的激励理论:期望理论、赫茨伯格双因素理论、马斯洛需求层次理论、X理论和Y理论
  • 分布式系统中的Session管理:实现跨服务器的用户会话共享
  • 【数据结构】顺序表和链表
  • 【1024程序员节】之C++系列完结篇:Web编程
  • Java8项目如何升级到Java21?有啥坑?
  • 今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 10月24日,星期四
  • 代码随想录算法训练营第53天|107. 寻找存在的路径(并查集)