分享一個圖片上傳元件實作原理(React + Node)的實例教學
本篇文章主要介紹了基於Node的React圖片上傳元件實作實例程式碼,非常具有實用價值,需要的朋友可以參考下
寫在前面
紅旗不倒,誓言把JavaScript進行到底!今天介紹我的開源專案 Royal 裡的圖片上傳元件的前後端實作原理(React + Node),花了一些時間,希望對你有幫助。
前端實作
遵循React 元件化的思想,我把圖片上傳做成了一個獨立的元件(沒有其他依賴),直接import即可。
import React, { Component } from 'react' import Upload from '../../components/FormControls/Upload/' //...... render() { return ( <p><Upload uri={'http://jafeney.com:9999/upload'} /></p> ) }
uri 參數是必須傳的,是圖片上傳的後端介面位址,介面怎麼寫下面會講到。
渲染頁面
元件render部分需要體現三個功能:
圖片選取(dialog視窗)
可拖曳功能(拖曳容器)
#可預覽(預覽清單)
上傳按鈕(button)
上傳完成圖片位址與連結(資訊清單)
主render函數
render() { return ( <form action={this.state.uri} method="post" encType="multipart/form-data"> <p className="ry-upload-box"> <p className="upload-main"> <p className="upload-choose"> <input onChange={(v)=>this.handleChange(v)} type="file" size={this.state.size} name="fileSelect" accept="image/*" multiple={this.state.multiple} /> <span ref="dragBox" onDragOver={(e)=>this.handleDragHover(e)} onDragLeave={(e)=>this.handleDragHover(e)} onDrop={(e)=>this.handleDrop(e)} className="upload-drag-area"> 或者将图片拖到此处 </span> </p> <p className={this.state.files.length? "upload-preview":"upload-preview ry-hidden"}> { this._renderPreview(); // 渲染图片预览列表 } </p> </p> <p className={this.state.files.length? "upload-submit":"upload-submit ry-hidden"}> <button type="button" onClick={()=>this.handleUpload()} class="upload-submit-btn"> 确认上传图片 </button> </p> <p className="upload-info"> { this._renderUploadInfos(); // 渲染图片上传信息 } </p> </p> </form> ) }
渲染圖片預覽清單
_renderPreview() { if (this.state.files) { return this.state.files.map((item, idx) => { return ( <p className="upload-append-list"> <p> <strong>{item.name}</strong> <a href="javascript:void(0)" rel="external nofollow" className="upload-delete" title="删除" index={idx}></a> <br/> <img src={item.thumb} className="upload-image" /> </p> <span className={this.state.progress[idx]? "upload-progress": "upload-progress ry-hidden"}> {this.state.progress[idx]} </span> </p> ) }) } else { return null } }
渲染圖片上傳資訊清單
_renderUploadInfos() { if (this.state.uploadHistory) { return this.state.uploadHistory.map((item, idx) => { return ( <p> <span>上传成功,图片地址是:</span> <input type="text" class="upload-url" value={item.relPath}/> <a href={item.relPath} target="_blank">查看</a> </p> ); }) } else { return null; } }
#前端要實作圖片上傳的原理就是透過建構FormData
物件,把檔案物件append()到該對象,然後掛載在
XMLHttpRequest物件上send( ) 到服務端。
取得檔案物件
取得檔案物件需要藉助input 輸入框的change
事件來取得句柄參數eonChange={(e)=>this.handleChange(e)}
e.preventDefault() let target = event.target let files = target.files let count = this.state.multiple ? files.length : 1 for (let i = 0; i < count; i++) { files[i].thumb = URL.createObjectURL(files[i]) } // 转换为真正的数组 files = Array.prototype.slice.call(files, 0) // 过滤非图片类型的文件 files = files.filter(function (file) { return /image/i.test(file.type) })
這時files 就是我們需要的檔案物件組成的數組,把它concat 到原有的files裡。
this.setState({files: this.state.files.concat(files)})
upload(file, idx) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest() if (xhr.upload) { // 上传中 xhr.upload.addEventListener("progress", (e) => { // 处理上传进度 this.handleProgress(file, e.loaded, e.total, idx); }, false) // 文件上传成功或是失败 xhr.onreadystatechange = (e) => { if (xhr.readyState === 4) { if (xhr.status === 200) { // 上传成功操作 this.handleSuccess(file, xhr.responseText) // 把该文件从上传队列中删除 this.handleDeleteFile(file) resolve(xhr.responseText); } else { // 上传出错处理 this.handleFailure(file, xhr.responseText) reject(xhr.responseText); } } } // 开始上传 xhr.open("POST", this.state.uri, true) let form = new FormData() form.append("filedata", file) xhr.send(form) }) }
利用XMLHttpRequest物件發非同步請求的好處是可以計算請求處理的進度,這是fetch所不具備的。
我們可以為xhr.upload 物件的progress 事件新增事件監聽:
xhr.upload.addEventListener("progress", (e) => { // 处理上传进度 this.handleProgress(file, e.loaded, e.total, i); }, false)
說明:idx參數是紀錄多圖上傳佇列的
索引 #handleProgress(file, loaded, total, idx) {
let percent = (loaded / total * 100).toFixed(2) + '%';
let _progress = this.state.progress;
_progress[idx] = percent;
this.setState({ progress: _progress }) // 反馈到DOM里显示
}
來說其實非常簡單,因為它自帶的幾個事件監聽機制可以直接做這類處理。主要用到的是下面三個:
onDragOver={(e)=>this.handleDragHover(e)} onDragLeave={(e)=>this.handleDragHover(e)} onDrop={(e)=>this.handleDrop(e)}
取消拖曳時的瀏覽器行為:
handleDragHover(e) { e.stopPropagation() e.preventDefault() }
處理拖曳進來的檔案:handleDrop(e) {
this.setState({progress:[]})
this.handleDragHover(e)
// 获取文件列表对象
let files = e.target.files || e.dataTransfer.files
let count = this.state.multiple ? files.length : 1
for (let i = 0; i < count; i++) {
files[i].thumb = URL.createObjectURL(files[i])
}
// 转换为真正的数组
files = Array.prototype.slice.call(files, 0)
// 过滤非图片类型的文件
files = files.filter(function (file) {
return /image/i.test(file.type)
})
this.setState({files: this.state.files.concat(files)})
}
:multiple = { true } // 开启多图上传
size = { 50 } // 一次最大上传数量(虽没有上限,为保证服务端正常,建议50以下)
handleUpload() { let _promises = this.state.files.map((file, idx) => this.upload(file, idx)) Promise.all(_promises).then( (res) => { // 全部上传完成 this.handleComplete() }).catch( (err) => { console.log(err) }) }
好了,前端工作已經完成,接下來就是Node的工作了。
後端實作為了方便,後端採用的是express框架來快速建置Http服務和路由。具體項目請見我的github node-image-upload。邏輯雖然簡單,但還是有幾個可圈可點的地方:
跨域呼叫#########本專案後端採用的是express,我們可以透過res .header() 設定請求的「允許來源」 來允許跨域呼叫:###res.header('Access-Control-Allow-Origin', '*');
function allowCross(res) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); }
所以服务端路由 我们还需要 处理这样一个 请求:
router.options('*', function (req, res, next) { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'); res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS'); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8"); next(); });
注意:该请求同样需要设置跨域。
处理上传
处理上传的图片引人了multiparty模块,用法很简单:
/*使用multiparty处理上传的图片*/ router.post('/upload', function(req, res, next) { // 生成multiparty对象,并配置上传目标路径 var form = new multiparty.Form({uploadDir: './public/file/'}); // 上传完成后处理 form.parse(req, function(err, fields, files) { var filesTmp = JSON.stringify(files, null, 2); var relPath = ''; if (err) { // 保存失败 console.log('Parse error: ' + err); } else { // 图片保存成功! console.log('Parse Files: ' + filesTmp); // 图片处理 processImg(files); } }); });
图片处理
Node处理图片需要引入 gm 模块,它需要用 npm 来安装:
npm install gm --save
BUG说明
注意:node的图形操作gm模块前使用必须 先安装 imagemagick 和 graphicsmagick,Linux (ubuntu)上使用apt-get 安装:
sudo apt-get install imagemagick sudo apt-get install graphicsmagick --with-webp // 支持webp格式的图片
MacOS上可以用 Homebrew 直接安装:
brew install imagemagick brew install graphicsmagick --with-webp // 支持webp格式的图片
预设尺寸
有些时候除了原图,我们可能需要把原图等比例缩小作为预览图或者缩略图。这个异步操作还是用Promise来实现:
function reSizeImage(paths, dstPath, size) { return new Promise(function(resolve, reject) { gm(dstPath) .noProfile() .resizeExact(size) .write('.' + paths[1] + '@' + size + '00.' + paths[2], function (err) { if (!err) { console.log('resize as ' + size + ' ok!') resolve() } else { reject(err) }; }); }); }
重命名图片
为了方便排序和管理图片,我们按照 “年月日 + 时间戳 + 尺寸” 来命名图片:
var _dateSymbol = new Date().toLocaleDateString().split('-').join(''); var _timeSymbol = new Date().getTime().toString();
至于图片尺寸 使用 gm的 size() 方法来获取,处理方式如下:
gm(uploadedPath).size(function(err, size) { var dstPath = './public/file/' + _dateSymbol + _timeSymbol + '_' + size.width + 'x' + size.height + '.' + _img.originalFilename.split('.')[1]; var _port = process.env.PORT || '9999'; relPath = 'http://' + req.hostname + ( _port!==80 ? ':' + _port : '' ) + '/file/' + _dateSymbol + _timeSymbol + '_' + size.width + 'x' + size.height + '.' + _img.originalFilename.split('.')[1]; // 重命名 fs.rename(uploadedPath, dstPath, function(err) { if (err) { reject(err) } else { console.log('rename ok!'); } }); });
总结
对于大前端的工作,理解图片上传的前后端原理仅仅是浅层的。我们的口号是 “把JavaScript进行到底!”,现在无论是 ReactNative的移动端开发,还是NodeJS的后端开发,前端工程师可以做的工作早已不仅仅是局限于web页面,它已经渗透到了互联网应用层面的方方面面,或许,叫 全栈工程师 更为贴切吧。
【相关推荐】
1. 免费js在线视频教程
3. php.cn独孤九贱(3)-JavaScript视频教程
以上是分享一個圖片上傳元件實作原理(React + Node)的實例教學的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PiNetwork節點詳解及安裝指南本文將詳細介紹PiNetwork生態系統中的關鍵角色——Pi節點,並提供安裝和配置的完整步驟。 Pi節點在PiNetwork區塊鏈測試網推出後,成為眾多先鋒積極參與測試的重要環節,為即將到來的主網發布做準備。如果您還不了解PiNetwork,請參考Pi幣是什麼?上市價格多少? Pi用途、挖礦及安全性分析。什麼是PiNetwork? PiNetwork項目始於2019年,擁有其專屬加密貨幣Pi幣。該項目旨在創建一個人人可參與

PHP、Vue和React:如何選擇最適合的前端框架?隨著互聯網技術的不斷發展,前端框架在Web開發中起著至關重要的作用。 PHP、Vue和React作為三種代表性的前端框架,每一種都具有其獨特的特徵和優勢。在選擇使用哪種前端框架時,開發人員需要根據專案需求、團隊技能和個人偏好做出明智的決策。本文將透過比較PHP、Vue和React這三種前端框架的特徵和使

微信小程式實現圖片上傳功能隨著行動網路的發展,微信小程式已經成為了人們生活中不可或缺的一部分。微信小程式不僅提供了豐富的應用場景,還支援開發者自訂功能,其中包括圖片上傳功能。本文將介紹如何在微信小程式中實作圖片上傳功能,並提供具體的程式碼範例。一、前期準備工作在開始編寫程式碼之前,我們需要先下載並安裝微信開發者工具,並註冊成為微信開發者。同時,也需要了解微信

Java框架與React框架的整合:步驟:設定後端Java框架。建立專案結構。配置建置工具。建立React應用程式。編寫RESTAPI端點。配置通訊機制。實戰案例(SpringBoot+React):Java程式碼:定義RESTfulAPI控制器。 React程式碼:取得並顯示API回傳的資料。

Vue.js適合中小型項目和快速迭代,React適用於大型複雜應用。 1)Vue.js易於上手,適用於團隊經驗不足或項目規模較小的情況。 2)React的生態系統更豐富,適合有高性能需求和復雜功能需求的項目。

React通過JSX與HTML結合,提升用戶體驗。 1)JSX嵌入HTML,使開發更直觀。 2)虛擬DOM機制優化性能,減少DOM操作。 3)組件化管理UI,提高可維護性。 4)狀態管理和事件處理增強交互性。

Vue技術開發中如何實現圖片上傳和裁剪,需要具體程式碼範例在現代Web開發中,圖片上傳和圖片裁剪是常見的需求之一。 Vue.js作為一個流行的前端框架,提供了豐富的工具和插件來幫助我們實現這些功能。本文將介紹如何在Vue技術開發中實現圖片上傳和裁剪,並提供具體的程式碼範例。圖片上傳的實作可以分為兩個步驟:選擇圖片和上傳圖片。在Vue中,可以使用第三方插件來簡化這個

react有事件處理函數、useEffect和useCallback、高階元件等等閉包。詳細介紹:1、事件處理函數閉包:在React中,當我們在元件中定義事件處理函數時,函數會形成一個閉包,可以存取元件作用域內的狀態和屬性。這樣可以在事件處理函數中使用元件的狀態和屬性,實現互動邏輯;2、useEffect和useCallback中的閉包等等。
