表单上传图片可不止选择这一种方式

大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在

前言

通过表单上传图片发送到后台是挺常见的需求,一般来说有两种交互方式:

1-点击按钮,到文件夹内选取指定的图片后上传

2-将文件夹拖拽到展示区域后上传

最近产品解锁了新的交互方式,在我看来这纯属脱裤子放屁,多此一举,不过我的经验告诉我,可行!!!

需求说明

对应需求在原型的第七条,要求从剪切板读取用户复制的图片并进行上传

image.png

头脑风暴

该功能的实现由以下部分构成:

1-复制图片

复制图片是发生在操作系统的,因此这一个交互动作不需要我们关心

2-读取剪切板

这一定涉及到浏览器的权限问题和版本兼容性问题:对于版本问题,由于是内部系统可以强制要求更新到chrome最新版所以不用考虑。而权限问题则需要进行交互上的优化处理,比如什么时间去请求权限;用户赋权导致了粘贴中断应该如何处理;如果用户拒绝了又该如何处理等

image.png

3-过滤剪切板

由于我们不参与图片复制的过程,也就无法保证用户规规矩矩的复制的是图片,如果是非图片的文件我们应该进行过滤

4-监听剪切

实现粘贴的方式无外乎两种:ctrl+v、鼠标右键。如果存在差异,需要进行特殊处理

调研

  • 读取剪切板

读取剪切板有4种方式:

1-使用navigator的Clipboard对象

2-使用document.execCommand

3-使用window.clipboardData对象

4-使用第三方库

由于方式3是ie浏览器独有的,方式4会导致项目体积变大,故这俩我们直接不考虑。至于方式2,虽然也是原生的,且对浏览器的兼容性比较好,但是由于其是同步行为,所以我们也不考虑。因此我们选择方式一来进行实现

  • 监听剪切

通过监听元素的paste事件可以监听到用户的粘贴行为,这包括ctrl+v和鼠标右键

  • 过滤剪切板

paste事件的回调参数e中的clipboardData下的files可以拿到用户复制的文件,每个file的type可以对非图片进行过滤

实现

  • 封装剪切板读取授权函数

考虑到文本和图片的授权方式不同,我们通过type来进行区分

export default (that, cb, type = 'read') => {
  const env = process.env.NODE_ENV;
  const errTip = '当前浏览器不支持(或用户拒绝授权)读取剪切板';
  if (!window.isSecureContext || ['development', 'test'].includes(env)) {
    that.$Message.warning(errTip);
    return;
  }
  navigator.clipboard[type]()
    .then((text) => {
      if (typeof cb === 'function') {
        cb(text);
      }
    })
    .catch(() => {
      that.$Message.warning(errTip);
    });
};
  • 在组件的created中初始化拉取授权
import getClipboardAuth from '@/utils/getClipboardAuth';
getClipboardAuth(this,null,'read')
  • 在组件的mounted中为图片区域绑定事件

1-将这部分逻辑提取到utils中,避免影响主流程观感

export const bindEvent = (event) => {
  const boxId = 'img-box';
  const box = document.getElementById(boxId);
  box.addEventListener(
    'click',
    (e) => {
      let target = e.target;
      while (target.id !== boxId) {
        target = target.parentNode;
      }
      target.style.border = '1.5px solid green';
      target.addEventListener('paste', event);
    },
    true
  );
  box.addEventListener('mouseleave', (e) => {
    if (e.target.id !== boxId) return;
    e.target.style.border = '1px solid #ccc';
    e.target.removeEventListener('paste', event);
  });
};

2-引入并注册监听

import { bindEvent } from './utils';
bindEvent(this.handlePaste)
  • 读取图片并展示到指定区域

这里我们通过type属性对非图片进行过滤,然后使用FileReader将其作为base64展示到页面中,其实是可以直接通过oss上传得到线上地址的,但是我感觉这样有点浪费资源

methods: {
        handlePaste(e) {
            const clipboardData = e.clipboardData || {}
            let files = clipboardData.files || []
            files = [...files]
            files = files.filter((v) => v.type.startsWith('image'))
            if (files.length) {
                for (let i = 0; i < files.length; i++) {
                    const reader = new FileReader()
                    reader.index = i
                    reader.onload = (ev) => {
                        const url = ev.target.result
                        this.previewImgs.push(url)
                        files[reader.index].base64Url = url
                        this.uploadImgs.push(files[reader.index])
                        this.formModel.prove = 'have'
                    }
                    reader.readAsDataURL(files[i])
                }
                return
            }

            this.$Message.warning("剪切板中不存在图片")
        },
    },
  • 预览图片时处理上传oss

我选择在用户预览时再使用oss进行上传,且只会处理一次,后续再次预览就直接从缓存中读取,这样能一定程度避免浪费资源

methods: {
        async handlePreview(id) {
            const previewItem = this.uploadImgs.find((v) => v.base64Url === id)
            if (previewItem && previewItem.httpUrl) {
                previewImg(previewItem.httpUrl)
                return
            }
            for (let i = 0; i < this.uploadImgs.length; i++) {
                const v = this.uploadImgs[i]
                const base64Url = v.base64Url
                delete v.base64Url
                // eslint-disable-next-line no-loop-func
                this.uploadImg([v], i).then((imgs) => {
                    const urlDas = norImgs(imgs)
                    this.uploadImgs[urlDas[0].index].httpUrl = urlDas[0].url
                    this.uploadImgs[urlDas[0].index].base64Url = base64Url
                    if (base64Url === id) {
                        previewImg(urlDas[0].url)
                    }
                })
            }
        },
},
  • 提交表单时,上传oss

由于业务员可能不会进行预览,所以在提交表单时还需要进行校验是否需要上传到oss,我个人习惯将处理过程提取出来,如下,在处理参数阶段,我对uploadImgs中的httpUrl进行了校验

export async function processParams() {
  const { prove, orderNumber } = this.formModel;
  const params = {
    ...this.formModel,
  }; 
  if (prove === 'have') {
    if (!this.uploadImgs[0].httpUrl) {
      let imgs = await this.uploadImg(this.uploadImgs);
      imgs = norImgs(imgs);
      imgs = imgs.map((v) => v.url);
      params.prove = imgs.join('|');
    } else {
      params.prove = this.uploadImgs.map((v) => v.httpUrl).join('|');
    }
  }
  ...
  return params;
}

总结

本文从需求分析到技术调研再到功能落地,一步步带领大家实现了从剪切板读取图片的完整示例


如果本文对您有用,希望能得到您的点赞和收藏


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容