Skip to content

上传截屏的图片 #248

Open
Open
@yaofly2012

Description

@yaofly2012

一、背景

项目里有个上传图片的功能,但用户经常上传截屏图片,此时用户的操作流程是截屏 -> 保存图片-> 选择图片并上传。为了用户体验也为了实现产品吹的牛皮,需要将3步改成2步,即截屏 ->上传

技术调研

截屏操作获取的图片存储在系统剪切板,所以得利用JS读取剪切板内容。研究下Clipboard API的使用。

测试环境

  1. Chrome V87
  2. FF V95

二、Navigator.clipboard

利用全局属性Navigator.clipboard可以利用JS操作系统剪切板。

使用限制

JS方式读写系统剪切板涉及用户隐私和数据安全问题,这个API使用场景有使用限制:

  1. 安全上下文(HTTPS)
    必须是HTTPS或者localhost域名,否则不会暴露Navigator.clipboard属性:
    image

  2. 用户授权
    image

  3. 文档要处于Focused状态,否则报错:
    image

  4. 文档如果以iframe方式被嵌入其他跨域文档中,则也不能执行

读数据Clipboard.read()

navigator.clipboard.read().then((data) => {
  for (let i = 0; i < data.length; i++) {
    if (!data[i].types.includes("image/png")) {
      alert("Clipboard contains non-image data. Unable to access it.");
    } else {
      data[i].getType("image/png").then((blob) => {
        imgElem.src = URL.createObjectURL(blob);
      });
    }
  }
});

三、粘贴(paste)事件

监听用户触发的粘贴操作。此时粘贴操作是用户主动触发的,所以排除了一些安全问题。

使用限制对比

使用限制 Navigator.clipboard API 粘贴(paste)事件
安全上下文(HTTPS) Y N
用户授权 Y N
文档要处于Focused状态 Y Y
弃用跨域 iframe 中的权限 Y N

ClipboardEvent.clipboardData属性

利用ClipboardEvent.clipboardData属性可以获取系统剪切板的数据。
总结下ClipboardEvent.clipboardData对象的几个比较重要的属性和方法:

属性/方法名称 描述
types属性 剪切板数据的格式列表(一份数据可以存在多个展示格式),比如text/plain, text/html, Files
items属性 剪切板里的数据列表,和types属性的值一一对应
getData方法 获取剪切板数据,但不能获取文件内容(得使用files属性)
files属性 文件内容,比如截图操作

实战

获取文本内容

window.addEventListener('paste', function(e) {
  const clipboardData = e.clipboardData || window.clipboardData;
  // 或者clipboardData.getData('text');
  const data = clipboardData.getData('text/plain');
  console.log(data)          
})

获取富文本内容

即copy带有样式的文本。

window.addEventListener('paste', function(e) {
  const clipboardData = e.clipboardData || window.clipboardData;
  const data = clipboardData.getData('text/html');
  console.log(data)          
})

获取并展示粘贴板数据

根据types属性的值读取剪切板数据。

window.addEventListener('paste', function(e) {
  const clipboardData = e.clipboardData || window.clipboardData;
  // 数据的格式列表(一份数据可以存在多个展示格式)
  const { types } = clipboardData;
  types.forEach(type => {
    console.log(`type=${type}`)
    const data = clipboardData.getData(type);
    // 不携带样式的文本(包含回车,换行等不可见字符)
    if('text/plain' === type) {
      document.getElementById('js-text').innerHTML = data;
      return;
    }
    // 富文本格式
    if('text/html' === type) {
      document.getElementById('js-html').innerHTML = data;
      return;
    }
  })

  // 文件
  if(clipboardData.files.length) {
    clipboardData.files.forEach(file => {
      if(!/^image/.test(file.type)) {
        return;
      }
      const reader = new FileReader();
      reader.addEventListener("load", function () {              
        document.getElementById('js-img-preview').src = reader.result;
      }, false);
      reader.readAsDataURL(file);
    })            
  }
})

兼容性

目前测试Chrome(V87),FF(V95)可以正常使用。详情见兼容性

参考

  1. MDN Element: paste event
  2. MDN Navigator.clipboard
  3. DataTransfer
  4. w3c Clipboard Events
  5. stackoverflow JavaScript get clipboard data on paste event (Cross browser)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions