基于業(yè)務(wù)場(chǎng)景下的圖片/文件上傳方案總結(jié)
前言
圖片/文件上傳組是企業(yè)項(xiàng)目開(kāi)發(fā)中必不可少的環(huán)節(jié)之一, 但凡涉及到用戶模塊的都會(huì)有圖片/文件上傳需求, 在很多第三方組件庫(kù)(ant desigin, element ui)中它也是基礎(chǔ)組件之一. 接下來(lái)筆者就來(lái)帶大家從零實(shí)現(xiàn)一款圖片/文件上傳組件以及擴(kuò)展出更強(qiáng)大的上傳組件.
你將收獲
- 常用的圖片上傳功能實(shí)現(xiàn)方案
- 手寫(xiě)一個(gè)圖片/文件上傳組件
- 如何將裁剪功能集成到上傳組件中
- 內(nèi)容平臺(tái)/可視化平臺(tái)下的圖片自治方案
- 如何擴(kuò)展出更強(qiáng)大的圖片上傳方案
正文
作為一名前端工程師, 解決項(xiàng)目問(wèn)題是我們的基本職責(zé)之一, 我們可以利用已掌握的知識(shí)去解決項(xiàng)目開(kāi)發(fā)中的問(wèn)題和需求, 這也是我們職業(yè)生涯必將經(jīng)歷的第一個(gè)階段,即——適應(yīng)期. 如果我們想繼續(xù)晉升, 我們就需要不斷的打怪升級(jí),掌握各種技能, 這樣我們才能在未來(lái)遇到問(wèn)題時(shí)采用最佳的方案高效的解決問(wèn)題, 也就是第二個(gè)階段——發(fā)展期.
為了更快的進(jìn)入發(fā)展期, 我們需要不斷的提升自己的技術(shù)深度和廣度, 能縱向考慮到問(wèn)題的本質(zhì)也能橫向的對(duì)問(wèn)題提出多種解決方案, 最終選擇一種最優(yōu)方案來(lái)實(shí)現(xiàn). 要實(shí)現(xiàn)這一點(diǎn),我們需要對(duì)問(wèn)題做深度思考和復(fù)盤(pán), 接下來(lái)筆者將介紹幾種常用的圖片上傳方案,來(lái)擴(kuò)展大家的廣度.
1. 常用的圖片上傳方案
從web1.0時(shí)代開(kāi)始, 我們用的最多的上傳方案就是form表單, 我們只需要在form內(nèi)寫(xiě)好各種input(輸入型元素), 并定義好上傳的服務(wù)器地址(action)即可.形式類(lèi)似如下:
- <form action="/xuxiaoxi/form/post">
- <div class="form-item"><input type="text" /></div>
- <div class="form-item"><input type="passward" /></div>
- <div class="form-item"><input type="file" /></div>
- <div class="form-item"><input type="submit" /></div>
- </form>
在XHR技術(shù)還沒(méi)普及時(shí), 我們大多會(huì)選擇上述方案, 唯一的缺點(diǎn)就是提交之后會(huì)刷新頁(yè)面, 用戶體驗(yàn)不太好, 還可能造成局部數(shù)據(jù)丟失, 但仍然有解決方案, 就是form + iframe技術(shù).
1.1 form + iframe方案
form + iframe方案的基本思路就是我們提交動(dòng)作是在父頁(yè)面觸發(fā), 但是form表單指向?yàn)閕frame, 這樣可以實(shí)現(xiàn)局部刷新, 現(xiàn)在有些場(chǎng)景仍然在使用該方案, 具體原理如下:
以上兩種方案都可以實(shí)現(xiàn)傳統(tǒng)form提交下的局部刷新功能, 不過(guò)方案一需要單獨(dú)維護(hù)iframe表單, 所以我呢一般采用方案二, 而且兼容性都可以達(dá)到IE9(雖然現(xiàn)在來(lái)說(shuō)兼容IE瀏覽器意義不大, 但是還是要了解一下)
1.2 ajax + formData方案
在XHR盛行之后,我們可以輕松使用ajax來(lái)實(shí)現(xiàn)異步請(qǐng)求了, 對(duì)于文件上傳, 我們也可以更靈活的使用ajax和formData來(lái)實(shí)現(xiàn), 逐漸脫離了對(duì)原生form表單的依賴(lài).
- FormData 對(duì)象用以將數(shù)據(jù)編譯成鍵值對(duì),以便用XMLHttpRequest來(lái)發(fā)送數(shù)據(jù)。其主要用于發(fā)送表單數(shù)據(jù),但亦可用于發(fā)送帶鍵數(shù)據(jù)(keyed data),而獨(dú)立于表單使用。如果表單enctype屬性設(shè)為multipart/form-data ,則會(huì)使用表單的submit()方法來(lái)發(fā)送數(shù)據(jù),從而,發(fā)送數(shù)據(jù)具有同樣形式。
我們先來(lái)看一個(gè)簡(jiǎn)單的使用formData上傳文件的例子:
- let formData = new FormData();
- // HTML 文件類(lèi)型input,由用戶選擇
- formData.append("userfile", fileInputElement.files[0]);
- let request = new XMLHttpRequest();
- request.open("POST", "http://http://io.nainor.com/h5/form");
- request.send(formData);
以上短短5行代碼就實(shí)現(xiàn)了將文件通過(guò)formData的方式上傳給了服務(wù)器, 是不是很簡(jiǎn)單呢? 筆者之前的文章 基于react/vue開(kāi)發(fā)一個(gè)專(zhuān)屬于程序員的朋友圈應(yīng)用就采用了該方案, 感興趣的可以學(xué)習(xí)研究一下.
如果要實(shí)現(xiàn)多文件上傳也非常簡(jiǎn)單, 這里我們以axios為例, 具體實(shí)現(xiàn)如下:
- const formData = new FormData()
- for(let i=0; i< files.length; i++) {
- formData.append(`file_${i+1}`, files[i].file)
- }
- axios({
- method: 'post',
- url: '/files/upload/tx',
- data: formData,
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
- });
這里要注意多文件上傳要在請(qǐng)求的http header中設(shè)置 Content-Type 為 multipart/form-data . 當(dāng)然大家還可以基于以上原理實(shí)現(xiàn)更符合自身業(yè)務(wù)需求的文件上傳組件, 比如預(yù)覽, 限流等.
1.3 第三方組件實(shí)現(xiàn)
為了更高效快速的開(kāi)發(fā)業(yè)務(wù), 我們有時(shí)候也可以選擇第三方比較成熟的方案, 比如antd的upload組件, 比如element ui的上傳組件, 這里筆者總結(jié)了幾個(gè)比較好用且強(qiáng)大的方案, 大家可以感受一下:
- antd/element 的 upload 組件
- FilePond 可以上傳任何內(nèi)容,并能夠優(yōu)化圖像以加快上傳速度,同時(shí)提供順暢的用戶體驗(yàn)
- Web Uploader 百度WebFE(FEX)團(tuán)隊(duì)開(kāi)發(fā)的一個(gè)簡(jiǎn)單的以HTML5為主,F(xiàn)LASH為輔的現(xiàn)代文件上傳組件
- vue-simple-uploader 基于vue的強(qiáng)大美觀的文件上傳組件
我們可以通過(guò)上述提供的第三方組件庫(kù), 結(jié)合自己服務(wù)端的配置,就可以輕松實(shí)現(xiàn)強(qiáng)大的上傳組件了.
2. 將裁剪功能集成到圖片上傳組件
對(duì)于圖片上傳組件來(lái)說(shuō), 我們往往不能確定用戶上傳的到底是什么, 所以我們要提前約束, 比如說(shuō)對(duì)圖片大小, 圖片格式, 圖片比例等進(jìn)行限制以符合我們的業(yè)務(wù)標(biāo)準(zhǔn). 圖片大小和圖片格式的限制非常好實(shí)現(xiàn), 但是對(duì)于圖片比例, 這個(gè)我們不能期望用戶自己來(lái)處理, 因?yàn)檫@樣會(huì)極大的增加用戶使用網(wǎng)站的負(fù)擔(dān), 所以我們可以提供一種功能, 讓用戶在線切圖. 如下圖所示:
以上截圖來(lái)自于H5-Dooring在線編輯器的圖片上傳組件, 在用戶上傳之后我們會(huì)出現(xiàn)圖片裁切界面, 我們會(huì)指定圖片的比例, 讓用戶自由裁切. 筆者將基于antd的upload組件配合antd-img-crop來(lái)帶大家實(shí)現(xiàn)在線切圖功能. 具體代碼實(shí)現(xiàn)如下:
- import React, { useState } from 'react';
- import { Upload } from 'antd';
- import ImgCrop from 'antd-img-crop';
- const Demo = () => {
- const [fileList, setFileList] = useState([
- {
- uid: '-1',
- name: 'image.png',
- status: 'done',
- url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
- },
- ]);
- const onChange = ({ fileList: newFileList }) => {
- setFileList(newFileList);
- };
- const onPreview = async file => {
- let src = file.url;
- if (!src) {
- src = await new Promise(resolve => {
- const reader = new FileReader();
- reader.readAsDataURL(file.originFileObj);
- reader.onload = () => resolve(reader.result);
- });
- }
- const image = new Image();
- image.src = src;
- const imgWindow = window.open(src);
- imgWindow.document.write(image.outerHTML);
- };
- return (
- <ImgCrop rotate>
- <Upload
- action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
- listType="picture-card"
- fileList={fileList}
- onChange={onChange}
- onPreview={onPreview}
- >
- {fileList.length < 5 && '+ Upload'}
- </Upload>
- </ImgCrop>
- );
- };
- ReactDOM.render(<Demo />, mountNode);
以上只是一個(gè)基本的裁切并上傳圖片的例子, 當(dāng)然antd-img-crop還提供了更多靈活的配置來(lái)方便我們?cè)O(shè)計(jì)更靈活強(qiáng)大的裁切效果. 當(dāng)然我們還可以使用react-cropper來(lái)實(shí)現(xiàn), 它提供了更靈活的裁切控制以及裁切實(shí)時(shí)預(yù)覽功能, 如下圖所示:
3. 內(nèi)容平臺(tái)/可視化平臺(tái)下的圖片自治
對(duì)于內(nèi)容平臺(tái)或者可視化平臺(tái)而且, 單純的上傳圖片還不能滿足用戶的需求, 因?yàn)閮?nèi)容/可視化平臺(tái)更加注重圖片的選擇和使用, 對(duì)圖片要求也很高, 用戶自己上傳畢竟資源有限, 往往不能達(dá)到用戶對(duì)內(nèi)容發(fā)布的需求或者可視化設(shè)計(jì)的需求, 所以往往在這類(lèi)平臺(tái)中會(huì)提供圖片素材庫(kù)這一功能, 用戶可以在素材庫(kù)中搜索海量圖片以滿足自己的需求, 而往往這樣, 才更能留住用戶, 增加用戶粘性.
基于以上場(chǎng)景產(chǎn)品經(jīng)理往往會(huì)提出這樣的需求: 能不能提供可選方案, 用戶既能自己上傳圖片, 也能使用我們提供的圖片庫(kù)資源呢? 這個(gè)時(shí)候有經(jīng)驗(yàn)的前端往往會(huì)說(shuō)一句: 安排!
在設(shè)計(jì)該功能之前我們往往要先參考其他已有實(shí)現(xiàn), 這里我們舉幾個(gè)例子, 如下圖所示:
以上案例中我們可以發(fā)現(xiàn)在用戶上傳圖片的時(shí)候都會(huì)提供兩個(gè)可選選項(xiàng), 一個(gè)是本地上傳, 一個(gè)是直接在圖片庫(kù)中選擇, 所以我們的方案也類(lèi)似, 可以統(tǒng)一將圖片庫(kù)封裝到文件上傳組件中作為通用功能, 也可以組合式封裝, 各自可以獨(dú)立使用也可以組合使用.
對(duì)于H5-Dooring對(duì)圖片庫(kù)的封裝, 使用了將其作為通用服務(wù)來(lái)實(shí)現(xiàn), 也就是但凡使用了上傳組件,一定會(huì)出現(xiàn)可選的從圖片庫(kù)選擇按鈕. 實(shí)現(xiàn)方案也很簡(jiǎn)單, 就是在upload組件中擴(kuò)展一層, 使用Modal+Tab來(lái)做圖片選擇的界面, 當(dāng)選擇完成后將圖片的地址手動(dòng)設(shè)置到upload組件中即可. 代碼如下:
- handleImgSelected= () => {
- const fileList = [
- {
- uid: uuid(8, 16),
- name: 'h5-dooring圖片庫(kù)',
- status: 'done',
- url: this.state.curSelectedImg,
- },
- ];
- this.props.onChange && this.props.onChange(fileList);
- this.setState({ fileList, wallModalVisible: false });
- };
這里用了antd的form組件的受控模式.
4. 圖片上傳組件擴(kuò)展
上面介紹的方案對(duì)于基本使用場(chǎng)景完全夠用了, 但是如果是內(nèi)容網(wǎng)站或者可視化搭建平臺(tái), 由于我們的配置可能會(huì)隨時(shí)分發(fā)到公網(wǎng), 這就會(huì)涉及到內(nèi)容安全的問(wèn)題, 如果一旦用戶配置了違法的圖片信息, 那么對(duì)于平臺(tái)提供上可能會(huì)受到牽連, 所以我們還需要提供一套完善的審核機(jī)制, 比如用戶配置好或者發(fā)布好內(nèi)容后, 需要進(jìn)過(guò)審核才能正式發(fā)布到線上, 但是完全依賴(lài)人工審核效率又比較低, 所以這個(gè)時(shí)候我們就需求找到機(jī)器自動(dòng)化審核方案了. 比如阿里云和騰訊云等都提供了圖片鑒別等服務(wù), 我們可以將這些服務(wù)集成到我們的組件中, 來(lái)實(shí)現(xiàn)真正的業(yè)務(wù)自治能力, 這樣才能更安全的進(jìn)行企業(yè)化經(jīng)營(yíng)和開(kāi)發(fā).
還有一個(gè)需求就是用戶對(duì)于上傳的圖片有編輯需求, 我們還可以提供對(duì)圖片的在線編輯功能, 類(lèi)似于如下方案:
我們能讓用戶有能力對(duì)自己選擇的圖片進(jìn)行自行設(shè)計(jì), 加水印等能力, 這樣是不是更有意思呢?
5. 總結(jié)
以上教程筆者已經(jīng)集成到H5-Dooring中,對(duì)于一些更復(fù)雜的交互功能,通過(guò)合理的設(shè)計(jì)也是可以實(shí)現(xiàn)的,大家可以自行探索研究。
本文轉(zhuǎn)載自微信公眾號(hào)「趣談前端」