native react 图片裁剪_图片裁剪上传示例(node + react)

本文介绍了一个基于Egg.js、React和相关库实现的图片上传和裁剪功能。通过Egg处理文件流,配合前端React组件进行图片裁剪,最后将裁剪后的图片上传至服务器并返回URL。
摘要由CSDN通过智能技术生成

场景

因为公司内部平台非常多,很多开发的站点地址没有一个统一的入口,所以作者基于 egg + mongodb + redies + umi +antd 搭建了一个简单的入口平台。

由于各个平台各有特点如果能输入名字的话还是不太好区分,logo上传必然是一个必须的功能。

一起来看一下整个前后端功能实现的过程。

node部分

依赖安装

yarn add await-stream-ready stream-wormhole

复制代码await-stream-ready 异步二进制 写入流

stream-wormhole 管道读入一个虫洞。

路由声明

module.exports = app => {

const { router, controller } = app;

router.get(‘/api/file/upload’, controller.file.upload)

};

复制代码

Egg声明路由

controller

'use strict';

//node.js 文件操作对象

const fs = require('fs');

//node.js 路径操作对象

const path = require('path');

//故名思意 异步二进制 写入流

const awaitWriteStream = require('await-stream-ready').write;

//管道读入一个虫洞。

const sendToWormhole = require('stream-wormhole');

//当然你也可以不使用这个 哈哈 个人比较赖

//还有我们这里使用了egg-multipart

const Controller = require('egg').Controller;

class FileController extends Controller {

async upload() {

const ctx = this.ctx;

//egg-multipart 已经帮我们处理文件二进制对象

// node.js 和 php 的上传唯一的不同就是 ,php 是转移一个 临时文件

// node.js 和 其他语言(java c#) 一样操作文件流

const stream = await ctx.getFileStream();

//新建一个文件名

const filename = stream.filename

// const filename = md5(stream.filename) + path

// .extname(stream.filename)

// .toLocaleLowerCase();

//文件生成绝对路径

//当然这里这样市不行的,因为你还要判断一下是否存在文件路径

const target = path.join(this.config.baseDir, 'app/public/uploads', filename);

//生成一个文件写入 文件流

const writeStream = fs.createWriteStream(target);

try {

//异步把文件流 写入

await awaitWriteStream(stream.pipe(writeStream));

} catch (err) {

//如果出现错误,关闭管道

await sendToWormhole(stream);

throw err;

}

const url = `/public/uploads/${filename}`

//文件响应

ctx.body = { url };

}

}

module.exports = FileController;

复制代码

首先 egg-multipart 已经帮我们处理了二进制对象,在前端发起请求姿势没有问题的情况下,后端部分只要调用ctx.getFileStream 就可以得到一个流

const stream = await ctx.getFileStream();

复制代码

然后指定写入的文件夹,注意这边如果没有找到文件夹会直接报错!

const filename = stream.filename

//当然这里这样市不行的,因为你还要判断一下是否存在文件路径

const target = path.join(this.config.baseDir, 'app/public/uploads', filename);

复制代码

然后创建一个文件写入流

const writeStream = fs.createWriteStream(target);

复制代码

接下来我们引用的两个库就派上用场了,一个是用来异步完成写入流,另外一个是用来报错的时候关闭管道,这部分不了解的请移步node。

try {

//异步把文件流 写入

await awaitWriteStream(stream.pipe(writeStream));

} catch (err) {

//如果出现错误,关闭管道

await sendToWormhole(stream);

throw err;

}

复制代码

等待文件写入流结束之后,文件就在目标文件夹下了,就可以把文件的地址返回给前端

const url = `/public/uploads/${filename}`

//文件响应

ctx.body = { url };

复制代码

总结

Egg部分已经帮我们把处理二进制对象处理完了,我们需要做的事情其实很简单,拿到文件流,指定写入的文件夹,创建文件写入流,等待写入流结束之后返回文件写入的地址给前端。

前端部分

依赖安装

本示例涉及到图片裁剪,如果没有这个需求的请略过

yarn add react-image-crop

复制代码

PlatformModal.jsx

upload(上传模块)

这里直接使用的是antd upload的组件,如果你后端部分写好了,直接贴入代码,updateUrl 为你上传文件的api接口。这边接口响应之后的格式根据你的情况定义,拿到的url可以直接写在

复制代码

既可。

到了这边一个图片上传的示例就结束了,后面我们将裁减模块。

renderUpdate = () => {

const uploadProps = {

name: 'icon',

action: updateUrl,

onChange: (info) => {

if (info.file.status !== 'uploading') {

console.log(info.file, info.fileList);

}

if (info.file.status === 'done') {

message.success(`${info.file.name} LOGO 上传成功!`);

this.setState({

iconUrl: info.file.response.data.url,

crop: {}

})

} else if (info.file.status === 'error') {

message.error(`${info.file.name} LOGO 上传失败!`);

}

}

}

return

选择图片

}

复制代码

renderReactCrop(裁减模块)

图片裁减部分我们引用了 react-image-crop 这个react组件,这部分功能的一个思路是这样的。

图片地址设置

裁减图片动作触发并带有裁减范围的参数

通过canvas将图片根据裁减范围转化为base64

将 base64 数据暂时存起来

在提交的时候,将base64转化为文件

通过 formData 创建文件对象提交到后端接口

后端返回新的url地址

更改平台的图片地址

import ReactCrop from 'react-image-crop'

import 'react-image-crop/dist/ReactCrop.css'

function getBlobBydataURI(dataURI, type) {

var binary = atob(dataURI.split(',')[1]);

var array = [];

for (var i = 0; i < binary.length; i++) {

array.push(binary.charCodeAt(i));

}

return new Blob([new Uint8Array(array)], { type: type });

}

复制代码renderReactCrop = () => {

const { iconUrl, crop } = this.state

const loadImage = imgSrc =>

new Promise((resolve, reject) => {

const img = new Image()

img.setAttribute('crossOrigin', 'anonymous')

img.src = imgSrc

img.onload = e => {

resolve(img)

}

})

const cropImage = async (imgSrc, crop) => {

const img = await loadImage(imgSrc)

let canvas, cropX, cropY, cropWidth, cropHeight

// return this.loadImage(imgSrc, cropAfterLoad.bind(this))

const imageWidth = img.naturalWidth

const imageHeight = img.naturalHeight

cropX = (crop.x / 100) * imageWidth

cropY = (crop.y / 100) * imageHeight

cropWidth = (crop.width / 100) * imageWidth

cropHeight = (crop.height / 100) * imageHeight

canvas = document.createElement('canvas')

canvas.width = cropWidth

canvas.height = cropHeight

const _2d = canvas.getContext('2d')

_2d.drawImage(img, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight)

return canvas.toDataURL('image/jpeg')

}

const handleCropComplete = (crop, pixelCrop) => {

cropImage(iconUrl, crop)

.then(result => {

message.success('裁剪成功!')

this.setState({

iconBase64: result,

crop,

})

})

.catch(err => {

message.error(err.message)

})

}

const handleCropChange = (crop) => {

this.setState({ crop })

}

return

src={iconUrl}

onComplete={handleCropComplete.bind(this)}

onChange={handleCropChange}

crop={crop}

/>

}

复制代码

然后是提交的时候的一个处理方式,将base64 转化为一个blob对象,然

将base64 转化为一个blob对象

创建一个 formData 用于提交

向fornData里面添加文件,注意这边图片名称记得带上时间戳

用fetch向文件上传接口发起请求

拿到url

const blob = getBlobBydataURI(iconBase64, 'image/png')

let formData = new FormData();

formData.append('files', blob, `${name}_${Date.parse(new Date())}_icon.png`)

fetch(updateUrl, {

method: 'POST',

body: formData

})

复制代码

结束

部分细节代码参考了网上的,一些细节没有深入研究,整理了以下整个流程。希望对别人有帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值