Giter Club home page Giter Club logo

blog's People

Watchers

 avatar

blog's Issues

移动端 fastclick 引入导致 antd select 需要双击才能触发

起因

移动端项目中公司框架默认引入了 fastclick.js。因为业务需要,同时引入了 ant-design 中的 select 组件,导致 select 组件需要双击才能弹出选项。

通过对这个问题进行深入研究,发现是 fastclick 导致的问题。

原因

DOM 事件触发顺序

首选需要了解一下 DOM 中 click,鼠标事件 mouse 以及触摸事件 touch 的触发顺序:

onTouchStart => (onTouchmove) => onTouchEnd => mousedown => (mousemove) => mouseup => click

fastclick 机制

通过查看 fastclick 源码得知:

  1. fastclick 会在 onTouchEnd 中调用了 event.preventDefault() 阻止默认事件(会阻止后续的 mouseclick 事件的触发)。
  2. 并创建并触发自定义的 click 事件(对于原生的 select 元素则触发 mousedown 事件)

对于 onTouchStartonTouchEnd 中调用 event.preventDefault() 阻止的默认事件,可参考:Touch event -- mdn

ant-design select 是如何触发事件的

通过查看 ant-design 使用到的 rc-select 得知是模拟了原生的 selectmousedown 事件触发弹出选项事件的,但内部并没有使用 select 元素:

const onInternalMouseDown: React.MouseEventHandler<HTMLDivElement> = (event, ...restArgs) => {
    // xxxx
    if (onMouseDown) {
        onMouseDown(event, ...restArgs);
    }
};
// dom 结构

return (
  <div
      className={mergedClassName}
      {...domProps}
      ref={containerRef}
      onMouseDown={onInternalMouseDown}
      onKeyDown={onInternalKeyDown}
      onKeyUp={onInternalKeyUp}
      onFocus={onContainerFocus}
      onBlur={onContainerBlur}
      >
      {mockFocused && !mergedOpen && (
          <span
              style={{
                  width: 0,
                  height: 0,
                  display: 'flex',
                  overflow: 'hidden',
                  opacity: 0,
              }}
              aria-live="polite"
			  >
              {/* Merge into one string to make screen reader work as expect */}
			  {`${mergedRawValue.join(', ')}`}
			  </span>
	  )}
      {selectorNode}
      {arrowNode}
      {clearNode}
  </div>
)

组件的实现位于 react-component/select 中,文件地址:https://github.com/react-component/select/blob/master/src/generate.tsx#L906

fastclick 不能识别 ant-design 的 select ,导致 mousedown 事件不会触发,进而单击无反应。

为何双击可以触发

通过进一步查看源码,了解到 fastclick 对双击事件进行了特殊处理,当两次点击低于延迟 250ms(fastclick 默认是否为双击判断时间),当双击后会触发 fastclick 对双击事件进行处理、首先,在 onTouchStart 中:

FastClick.prototype.onTouchStart = function(event) {
    // xxx
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
        event.preventDefault();
    }

}

虽然 onTouchStart 中调用了 event.preventDefault(),但是并不能阻止后续事件的触发。移动端为了让滚动能够更快的响应,所以浏览器对于 onTouchStart 事件默认设置 passive: true,即调用 event.preventDefault() 会被忽略(chrome 56+)。

具体内容参考:Making touch scrolling fast by default

在后续的 onTouchEnd 中,也对双击进行了判断:

FastClick.prototype.onTouchEnd = function(event) {
    if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
        this.cancelNextClick = true;
        return true;
    }
    // xxx
}

onTouchEnd 通过 return true 阻止了后续自定义事件的触发,导致后续原生的 mousedown 事件能够触发,进而 ant-design 的触发 selectonMouseDown 事件。

如何解决这个问题

因为项目不需要兼容老旧的浏览器,并且 <header> 中已经设置了:

<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">

项目中不需要 fastclick 来进行兼容,所以最后直接干掉了 fastclick

更多移动端 300ms 解决方案:5 way prevent 300ms click delay mobile devices

前端模块化 commonJS & ESM

commonJS

什么是 commonJS

[Modules: CommonJS modules | Node.js v22.2.0 Documentation]

commonJS 是一种前端模块化规范

  1. 每个文件有自己单独的作用域,在其中定义的所有变量、函数、类都是私有的

  2. 通过 module.exports 来对外暴露值、通过 require 来加载 module.exports 暴露的值

  3. 如果想要多文件共享,可以把功能定义到全局变量上(不推荐的🙅‍)

module 对象

每一个模块都有一个 module 对象,有以下属性:

const module {
	id: '.',
	path: 'D:\\work\\commonjs\\tests',
	exports: {}, // 模块对外输出的值
	filename: 'D:\\work\\commonjs\\tests\\index.js',
	loaded: false, // 该模块是否加载完成
	children: [], // require 的模块信息
	parent: [] // 调用该模块的模块信息
	paths: [ // 模块的搜索路径
	  'D:\\work\\commonjs\\tests\\node_modules',
	  'D:\\work\\commonjs\\node_modules',
	  'D:\\work\\node_modules',
	  'D:\\node_modules'
	]
}

exports 对象

exports 对象是对 module.exports 的引用

var exports = module.exports

模块中的 this 也指向这个 exports 对象

require 方法

require 用来读取一个 JS 文件,并返回 exports 对象:

const bar = require('./foo.js');

// 读取 foo.js 并把 exports 赋值给 bar

文件解析规则:

  1. 如果路径以 / 开头,则加载绝对路径的模块文件

  2. 如果以 ./ 开头,则加载相对路径文件

  3. 否则加载内置的模块 或者 根据当前路径,递归查找 node_modules 下的模块文件并结合 package.json 进行解析:

    [Modules: Packages | Node.js v22.1.0 Documentation]

  4. 如果指定的模块文件没有发现,Node 会依次添加 .js.json.node 后缀并再次查找

具体的算法:

[Modules: CommonJS modules | Node.js v22.2.0 Documentation]

代码示例加载逻辑:

Moldule._load = function(request) {
	// 1. 判断是否命中 Module.cache
	// 2. 没命中的话执行 module._load 加载模块
	// 3. 执行 module._compile 执行文件
	// 4. 缓存执行结果,并返回 module.exports
}

Module.prototype._compile = function(content) {
	const module = {
		exports: {},
		...
	}
	// 执行文件
	// (function (exports, require, module, __filename, __dirname) {
	  // content
	// })();
	return module.exports
}

require 对象

const require = [Function: require] { // 既是函数也是对象
	resolve: [Function: resolve] // 返回加载的绝对地址
		{
			paths: [Function: paths] // 获取 path 的查找路径
		},
  main: { // 入口模块
    id: '.',
    path: 'D:\\work\\commonjs\\tests',
    exports: {},
    filename: 'D:\\work\\commonjs\\tests\\index.js',
    loaded: false,
    children: [ [Object] ],
    paths: [
      'D:\\work\\commonjs\\tests\\node_modules',
      'D:\\work\\commonjs\\node_modules',
      'D:\\work\\node_modules',
      'D:\\node_modules'
    ]
  },
  extensions: [Object: null prototype] { // 不同后缀执行的加载方法
    '.js': [Function (anonymous)],
    '.json': [Function (anonymous)],
    '.node': [Function (anonymous)]
  },
  cache: [Object: null prototype] { // 缓存的模块信息
    'D:\\work\\commonjs\\tests\\index.js': {
      id: '.',
      path: 'D:\\work\\commonjs\\tests',
      exports: {},
      filename: 'D:\\work\\commonjs\\tests\\index.js',
      loaded: false,
      children: [Array],
      paths: [Array]
    },
    'D:\\work\\commonjs\\tests\\import.js': {
      id: 'D:\\work\\commonjs\\tests\\import.js',
      path: 'D:\\work\\commonjs\\tests',
      exports: [Object],
      filename: 'D:\\work\\commonjs\\tests\\import.js',
      loaded: true,
      children: [],
      paths: [Array]
    }
  }
}

循环加载

// a.js
exports.x = 'a1';
console.log('a.js ', require('./b.js').x);
exports.x = 'a2';

// b.js
exports.x = 'b1';
console.log('b.js ', require('./a.js').x);
exports.x = 'b2';

// main.js
console.log('main.js ', require('./a.js').x);
console.log('main.js ', require('./b.js').x);

// output

// b.js a1
// a.js b2
// main.js a2
// main.js b2

flex

不同布局的用途

  1. block 布局:用来对文档进行布局
  2. inline 布局:用来对文本进行布局
  3. table 布局:迎来对 2D 数据进行表格布局
  4. position 布局:用来进行明确具体位置信息的布局,而不考虑其他元素
  5. flex 布局:用于更复杂的布局场景,相当于是 block、inline 布局的增强。

flex 容器

设置 display: flex 会创建 flex 容器:

  1. 建立 flex 格式化上下文(等同于块级格式化上下文)
  2. 内部的子元素遵循 flex 布局模型进行布局

flex 布局计算是基于文档流的方向(flow directions)来进行的。主轴在文档流的方向,交叉轴和主轴垂直。
image

在 flex 容器元素中,一些属性不会生效:

  1. clear & float
  2. vertical-align:flex item 的 display 为 block,vertical-align 不生效
  3. :first-line & :first-letter

设置为 inline-flex 在特殊情况下作为 flex 对待,详情:https://www.w3.org/TR/CSS2/visuren.html#dis-pos-flo

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.