基于Node的React图片上传组件实现
来源:互联网 发布:剑灵超美龙女捏脸数据 编辑:程序博客网 时间:2024/06/06 00:50
写在前面
红旗不倒,誓把JavaScript
进行到底!今天介绍我的开源项目 Royal
里的图片上传组件的前后端实现原理(React
+ Node
),花了一些时间,希望对你有所帮助。http://blog.csdn.NET/u011413061/article/details/52169321
前端实现
遵循React
组件化的思想,我把图片上传做成了一个独立的组件(没有其他依赖),直接import
即可。
12345678910
import React, { Component } from 'react'import Upload from '../../components/FormControls/Upload/'//......render() {return (<div><Upload uri={'http://jafeney.com:9999/upload'} /></div>)}
uri
参数是必须传的,是图片上传的后端接口地址,接口怎么写下面会讲到。
渲染页面
组件render
部分需要体现三个功能:
- 图片选取(dialog窗口)
- 可拖拽功能(拖拽容器)
- 可预览(预览列表)
- 上传按钮 (button)
- 上传完成图片地址和链接 (信息列表)
主render
函数
1234567891011121314151617181920212223242526272829303132333435363738394041
render() {return (<form action={this.state.uri} method="post" encType="multipart/form-data"><div className="ry-upload-box"><div className="upload-main"><div className="upload-choose"><inputonChange={(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></div><div className={this.state.files.length?"upload-preview":"upload-preview ry-hidden"}>{ this._renderPreview(); // 渲染图片预览列表 }</div></div><div className={this.state.files.length?"upload-submit":"upload-submit ry-hidden"}><button type="button"onClick={()=>this.handleUpload()}class="upload-submit-btn">确认上传图片</button></div><div className="upload-info">{ this._renderUploadInfos(); // 渲染图片上传信息 }</div></div></form>)}
渲染图片预览列表
12345678910111213141516171819202122232425
_renderPreview() {if (this.state.files) {return this.state.files.map((item, idx) => {return (<div className="upload-append-list"><p><strong>{item.name}</strong><a href="javascript:void(0)"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></div>)})} else {return null}}
渲染图片上传信息列表
123456789101112131415
_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
事件来获取 句柄参数 e
1
onChange={(e)=>this.handleChange(e)}
然后做以下处理:
12345678910111213
e.preventDefault()let target = event.targetlet files = target.fileslet count = this.state.multiple ? files.length : 1for (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
里。
1
this.setState({files: this.state.files.concat(files)})
如此,接下来的操作 就可以 通过 this.state.files
取到当前已选中的 图片文件。
利用Promise
处理异步上传
文件上传对于浏览器来说是异步的,为了处理 接下来的多图上传,这里引入了 Promise
来处理异步操作:
1234567891011121314151617181920212223242526272829303132
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
事件添加事件监听:
1234
xhr.upload.addEventListener("progress", (e) => {// 处理上传进度this.handleProgress(file, e.loaded, e.total, i);}, false)
说明:idx
参数是纪录多图上传队列的索引
123456
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里显示}
拖拽上传
拖拽文件对于HTML5
来说其实非常简单,因为它自带的几个事件监听机制可以直接做这类处理。主要用到的是下面三个:
123
onDragOver={(e)=>this.handleDragHover(e)}onDragLeave={(e)=>this.handleDragHover(e)}onDrop={(e)=>this.handleDrop(e)}
取消拖拽时的浏览器行为:
1234
handleDragHover(e) {e.stopPropagation()e.preventDefault()}
处理拖拽进来的文件:
1234567891011121314151617
handleDrop(e) {this.setState({progress:[]})this.handleDragHover(e)// 获取文件列表对象let files = e.target.files || e.dataTransfer.fileslet count = this.state.multiple ? files.length : 1for (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)})}
多图同时上传
支持多图上传我们需要在组件调用处设置属性:
12
multiple = { true } // 开启多图上传size = { 50 } // 一次最大上传数量(虽没有上限,为保证服务端正常,建议50以下)
然后我们可以使用 Promise.all()
处理异步操作队列:
1234567
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()
设置 请求的 “允许源” 来允许跨域调用:
1
res.header('Access-Control-Allow-Origin', '*');
设置为 *
说明允许任何 访问源,不太安全。建议设置成 你需要的 二级域名,如 jafeney.com
。
除了 “允许源” ,其他还有 “允许头” 、”允许域”、 “允许方法”、”文本类型” 等。常用的设置如下:
1234567
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");}
ES6下的Ajax请求
ES6
风格下的Ajax
请求和ES5
不太一样,在正式的请求发出之前都会先发一个 类型为 OPTIONS
的请求 作为试探,只有当该请求通过以后,正式的请求才能发向服务端。
所以服务端路由 我们还需要 处理这样一个 请求:
12345678
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
模块,用法很简单:
12345678910111213141516171819
/*使用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
来安装:
1
npm install gm --save
BUG说明
注意:node
的图形操作gm
模块前使用必须 先安装 imagemagick
和 graphicsmagick
,Linux (ubuntu)
上使用apt-get
安装:
12
sudo apt-get install imagemagicksudo apt-get install graphicsmagick --with-webp // 支持webp格式的图片
MacOS
上可以用 Homebrew
直接安装:
12
brew install imagemagickbrew install graphicsmagick --with-webp // 支持webp格式的图片
预设尺寸
有些时候除了原图,我们可能需要把原图等比例缩小作为预览图或者缩略图。这个异步操作还是用Promise
来实现:
12345678910111213141516
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)};});});}
重命名图片
为了方便排序和管理图片,我们按照 “年月日 + 时间戳 + 尺寸” 来命名图片:
12
var _dateSymbol = new Date().toLocaleDateString().split('-').join('');var _timeSymbol = new Date().getTime().toString();
至于图片尺寸 使用 gm
的 size()
方法来获取,处理方式如下:
1234567891011121314151617
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页面,它已经渗透到了互联网应用层面的方方面面,或许,叫 全栈工程师
更为贴切吧。
当然,全栈
两个字的分量很重,不积跬步,无以至千里
,功力低下的我还需要不断修炼和实践!
参考
张鑫旭 《基于HTML5的可预览多图片Ajax上传》
@欢迎关注我的 github 和 个人博客 -Jafeney
- 基于Node的React图片上传组件实现
- 基于Node的React图片上传组件实现
- 基于Vue + Node.js + MongoDB的图片上传组件,实现图片的预览和删除
- React图片上传组件设计
- 自定义React图片上传组件
- node koa2图片上传的实现
- react-core-image-upload:轻量级的图片上传裁剪组件
- 使用jspSmartUpload组件实现图片的上传
- Node实现简单的表单+图片上传+路由
- 【React】 React的优点+实现分页组件
- React Native使用fetch实现图片上传
- React Native使用fetch实现图片上传
- 图片上传功能的实现(FileUpload组件的使用)
- react实现的面板组件
- 基于servlet实现上传图片
- node.js实现多图片上传
- Node.js实现多图片上传
- Node Js 实现图片上传 学习笔记
- 文章标题
- 浅谈caffe中train_val.prototxt和deploy.prototxt文件的区别
- Tensorflow使用过程中的问题
- C语言程序设计(40)
- Jsp数据交互
- 基于Node的React图片上传组件实现
- git命令
- Android 系统稳定性
- php常用函数开篇
- MYSQL常见面试题及基础知识点
- matlab单选按钮
- 谈谈mvp的思想
- share_order数据查询问题记录
- LeetCode 174 Dungeon Game 题解