你不知道的HTML:input 之 cancel 事件

214 阅读2分钟

背景

我们在做web 端文件上传的时候,可以明确的知道打开 文件选择器,但是之前是不知道用户是否选择了文件,可能会通过一些 其他的检测手段达到我们目的。

取消选择 是一种用户行为,我们可能想要做一些额外的业务上的处理,比如弹出提示, 数据埋点(埋用户取消行为的逻辑)等等

const e = document.createElement('input')

e.type = 'file'
e.oncancel = () => {
}

// 或者
e.addEventListener('cancel', () => {
  
})

现在,我们可以通过 oncancel 或者 addEventListener 方式监听

image.png

这个行为跟 原生的 dialog 的取消是一样的,只是 浏览器支持的比较晚, dialog 原生对 cancel 事件支持非常好

image.png

(input cancel事件)


image.png

(Dialog cancel事件)

小结

  • 用户点击 文件选择器弹窗的取消 会触发
  • 键盘的ESC 也会触发
  • 用户重新选择之前选择的相同文件时,文件输入也会触发它 (用户第一次选择A文件,再次打开,继续选择B文件,也会触发) 这种情况一般是非期望的,在 onchange 事件中,清除 input元素的 value 值就可以避免
  • 另外: 按照下面 文档所说,不同浏览器对于 cancel 取消行为 可能会存在不一致,不同浏览器对于取消这个动作实现都可以有自己的标准。html.spec.whatwg.org/multipage/i…

image.png

番外

在阅读 @vue/use 源码 - 【useFileDialog】的时候,也发现对这个事件的支持

@vue/use - useFileDialog 源码地址

export function useFileDialog(options: UseFileDialogOptions = {}): UseFileDialogReturn {
  const {
    document = defaultDocument,
  } = options

  const files = ref<FileList | null>(null)
  const { on: onChange, trigger: changeTrigger } = createEventHook()
  const { on: onCancel, trigger: cancelTrigger } = createEventHook()
  let input: HTMLInputElement | undefined
  if (document) {
    input = document.createElement('input')
    input.type = 'file'

    input.onchange = (event: Event) => {
      const result = event.target as HTMLInputElement
      files.value = result.files
      changeTrigger(files.value)
    }
    
    // 这里 😊😊😊😊
    input.oncancel = () => {
      cancelTrigger()
    }
  }

  const reset = () => {
    files.value = null
    if (input && input.value) {
      input.value = ''
      changeTrigger(null)
    }
  }

  const open = (localOptions?: Partial<UseFileDialogOptions>) => {
    if (!input)
      return
    const _options = {
      ...DEFAULT_OPTIONS,
      ...options,
      ...localOptions,
    }
    input.multiple = _options.multiple!
    input.accept = _options.accept!
    // webkitdirectory key is not stabled, maybe replaced in the future.
    input.webkitdirectory = _options.directory!
    if (hasOwn(_options, 'capture'))
      input.capture = _options.capture!
    if (_options.reset)
      reset()
    input.click()
  }

  return {
    files: readonly(files),
    open,
    reset,
    onCancel,
    onChange,
  }
}

参考链接