如何像掘金编辑器一样粘贴图片即可上传服务器

415 阅读7分钟

以前写了一篇文章 《前端粘贴复制还能这样玩》,其中涉及到DataTransfer,下面我们来了解一下DataTransfer对象。

DataTransfer对象存在哪些事件对象中

目前就发现拖拽相关事件和paste事件的事件对象中包含DataTransfer对象。 image.png

image.png

DataTransfer对象介绍

在mdn中介绍DataTransfer对象用于保存拖动并放下(drag and drop)过程中的数据。但是paste事件对象中clipboardData也是一个DataTransfer

  • dropEffect 获取当前选定的拖放操作类型或者设置的为一个新的类型。值必须为 none(项目可能禁止拖放), copy(在新位置生成源项的副本), link(在新位置建立源项目的链接) 或 move(将项目移动到新位置)。它设置的属性表示鼠标拖动的视觉效果,所以我们可以在dragover, dragenter中进行设置该值。
  • effectAllowed 设置源元素(被拖动的元素)允许的拖动效果。none (此项表示不允许放下)、copy (源项目的复制项可能会出现在新位置。)、copyLink (允许 copy 或者 link 操作。)、copyMove (允许 copy 或者 move 操作。)、link (可以在新地方建立与源的链接。)、linkMove (允许 link 或者 move 操作。)、move (一个项目可能被移动到新位置。)、all (允许所有的操作。)、uninitialized (效果没有设置时的默认值,则等同于 all。) 设置对应的属性有对应的效果。即可以限制我们拖动的结果。所以一般在dragstart中进行设置。

effectAllowed和dropEffect相互影响。二者的值需要设置交集。否则拖动是没有效果的。 想要查看效果可以看mdn demo

  • files 获取拖动和粘贴的文件(File)对象列表。这里的文件数据也会保存到items中,以DataTransferItem对象形式存在。
  • items 拖动和粘贴所产生的数据(DataTransferItem)集合。内部包含kind, type两个属性。
  • types 拖动和粘贴所产生的数据的类型。例如文本(text/plain), 图片,文件资源(Files)
  • setData(type, value) 设置拖动所需要的数据源,会加入到items属性集合中。注意设置相同类型type的数据会覆盖。

image.png

image.png

  • getData(type) 通过指定的type获取对应的数据源。
  • clearData(type) 清除指定type的数据源,如果不指定type则清空所有数据,该方法只有在dragstart事件中使用有效。
  • setDragImage(imgElement, xOffset, yOffset) 修改拖动时的图像,一般没啥用,除非你用到了。

DataTransferItem(DataTransfer.items每一项)对象介绍

最主要的是我们应该关注items中的数据对象,即DataTransferItemList对象,它内部包含着粘贴和拖拽的DataTransferItem对象。

我们来看下DataTransferItem对象中属性和方法

  • kind 拖拽或者粘贴项的种类,string 或是 file
  • type 拖拽或者粘贴项的类型,一般是一个 MIME 类型。

image.png

image.png

  • getAsFile() 如果DataTransferItem是一个文件,那 DataTransferItem.getAsFile()  方法将返回拖拽或者粘贴项数据的File对象。如果拖拽项的数据不是一个文件,则返回 null

image.png

  • getAsFileSystemHandle() 返回一个Promise, 可以用于判断当前粘贴和拖拽本地磁盘资源项是文件(FileSystemFileHandle)还是文件夹(FileSystemDirectoryHandle)。相比较直接通过kind, type可以更具体的参看类型。

image.png

image.png

如果是文件可以调用getFile()方法获取文件对象。如果是文件夹可以调用getFileHandle(),返回指定名称的FileSystemFileHandle对象然后再进行处理。

  • getAsString(callback) 如果 DataTransferItem 是一个字符串,可以在回调中获取粘贴或者拖拽的字符串的值。如果是文件资源那么回调将不会执行。

image.png

image.png

粘贴的测试代码

  <div contenteditable="true">
    我是可输入文本的div
  </div>
  <script>
    const div = document.getElementsByTagName("div")[0]

    // 这个事件只作用于可编辑的dom元素上。
    // 如果元素都没有设置contenteditable="true",那么将作用于整个网页。
    div.addEventListener("paste", async function (e) {
      console.log("粘贴", e.clipboardData.items[0])
      // console.log("查看当前粘贴本地的内容是文件还是文件夹", await e.clipboardData.items[0].getAsFileSystemHandle())
      // e.clipboardData.items[0].getAsString((res => {
      //   console.log("获取粘贴的字符串值", res)
      // }))
      // console.log("粘贴文件返回文件对象", e.clipboardData.items[0].getAsFile())
      const fileOrDir = await e.clipboardData.items[0].getAsFileSystemHandle()
      if(fileOrDir.kind === "file") {
        console.log("获取文件对象", await fileOrDir.getFile())
      }
    })
  </script>

拖拽的测试代码

  <div id="img-container"></div>

  <img id="img" src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" alt="" draggable="true" width="100">
  
  <script>
      /**
     * 
     * 
        - dragstart`: 开始拖拽对象时触发。在这里开始传递一些源数据。`e.dataTransfer.setData()`
             * 
        - `dragover`: 当被拖拽元素**未离开**可释放目标元素上时,触发该事件。**在拖拽的过程中。**
        - `dragleave`: 当被拖拽元素**离开**可释放目标元素上时,触发该事件。

        - `drop`: 拖拽元素拖拽到可释放目标对象释放后触发。即在这边获取拖拽元素时传入的一些源数据,做一些其他的逻辑处理。通过`e.dataTransfer.getData()`来获取对应的属性。
     * 
     **/
    const target = document.getElementById("img-container")
    const source = document.getElementById("img")

    source.ondragstart = function(e) {
      console.log("开始")
      e.dataTransfer.setData('text/html', "覆盖拖动时产生的text/html类型数据");
      e.dataTransfer.setData('text/plain', 'plain text');
      // 发送数据
      e.dataTransfer.setData('imgName', "test")
      e.dataTransfer.setData('imgType', "png")
      e.dataTransfer.setData('imgPath', e.target.currentSrc)
      e.dataTransfer.clearData()
    }

    // target.ondragenter = function(e) {
    //   console.log("e", e)
    // }

    // 进入目标元素时触发
    target.addEventListener("dragover", (event) => {
      console.log("进入目标元素移动时触发")
      // prevent default to allow drop
      event.preventDefault();
    });

    // 在使用drop事件之前,需要注册dragover事件。并且需要阻止默认事件。
    target.ondrop = async function(e) {
      console.log("拖拽", e.dataTransfer.items, e.dataTransfer.items[0], e.dataTransfer.items[1], e.dataTransfer.items[2], e.dataTransfer.items[3], e.dataTransfer.items[4])
      console.log("查看当前拖拽本地的内容是文件还是文件夹", await e.dataTransfer.items[0].getAsFileSystemHandle())
      e.dataTransfer.items[0].getAsString((res => {
        console.log("获取拖拽的字符串值0", res)
      }))
      e.dataTransfer.items[1].getAsString((res => {
        console.log("获取拖拽的字符串值1", res)
      }))
      e.dataTransfer.items[2].getAsString((res => {
        console.log("获取拖拽的字符串值2", res)
      }))
      e.dataTransfer.items[3].getAsString((res => {
        console.log("获取拖拽的字符串值2", res)
      }))
      console.log("拖拽数据中的文件", e.dataTransfer.files, e.dataTransfer.files[0])
      console.log("拖拽文件返回文件对象", e.dataTransfer.items[0].getAsFile())
      console.log("通过getData(type)获取数据源", e.dataTransfer.getData('text/html'))
      // console.log("e", e, e.dataTransfer.files[0], e.dataTransfer.items[0], e.dataTransfer.types[0])
      
      // // 在可拖拽的区域拖拽到指定的对象时触发
      // const imgName = e.dataTransfer.getData('imgName')
      // const imgType = e.dataTransfer.getData('imgType')
      // const imgPath = e.dataTransfer.getData('imgPath')
      // // 我们拖动文件到别的文件夹时,只是将文件对象加入到目的文件列表中。
      // const img = document.createElement("img")
      // img.setAttribute("src", imgPath)
      // img.setAttribute("name", imgName)
      // target.appendChild(img)
      // img.style.width = "100px"
      // img.style.height = "100px"
      // document.body.removeChild(source)
    }
  </script>

了解上面的这些操作我们就很容易的拿到文件对象上传给服务器了。

// index.html
  <div contenteditable="true">
    我是可输入文本的div
  </div>
  <script>
    const div = document.getElementsByTagName("div")[0]
    div.addEventListener("paste", async function (e) {
      console.log("粘贴", e.clipboardData.items[0], e.clipboardData.files[0])
      const formData = new FormData()
      formData.append("file", e.clipboardData.files[0])
      let config = {
        headers: {'Content-Type': 'multipart/form-data'}
      }
      axios.post("http://127.0.0.1:3000/login/file", formData, config).then(res => {
        console.log("res",res)
      })
    })

  </script>

后端我们是通过nestjs进行处理文件上传的。这里需要配合multer库去实现。

  @Post('file')
  @UseInterceptors(
    AnyFilesInterceptor({
      storage: diskStorage({
        destination: 'uploads',
        filename: (req, file, cb) => {
          const randomName = Array(32)
            .fill(null)
            .map(() => Math.round(Math.random() * 16).toString(16))
            .join('');
          return cb(null, `${randomName}${extname(file.originalname)}`);
        },
      }),
    }),
  )
  uploadCopyImgs(@UploadedFiles() files: any) {
    console.log(files);
    return `文件上传成功文件名为: ${files[0].filename}`;
  }

复制上传.gif

往期年度总结

往期文章

专栏文章

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏✍️评论,    支持一下博主~

公众号:全栈追逐者,不定期的更新内容,关注不错过哦!