247 行
11 KiB
JavaScript

import './upload.css';
import { Component, createRef, Fragment } from "react";
import { apis } from '../../helper/apis';
import Spinner from '../Spinner/Spinner';
export default class UploadUnit extends Component {
imgRef = createRef();
imgWidth;
imgHeight;
constructor(props) {
super(props);
this.state = {
// 0: loading, 1: compressing, 2: compressed, 3: uploading, 4: uploaded
status: -1,
src: null,
file: null,
progress: 0,
width: 0,
isScaled: false,
transform: ''
};
this.upload = this.upload.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.scale = this.scale.bind(this);
this.unscale = this.unscale.bind(this);
}
componentDidMount() {
setTimeout(() => {
this.readFile(this.props.file);
}, 100);
}
componentDidUpdate() {
if (this.props.upload && this.state.status === 2)
this.upload();
}
handleCancel() {
this.setState({ status: -1 });
setTimeout(() => {
this.props.onCancel();
}, 300);
}
readFile(file) {
if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
alert('请不要上传jpg、png、gif格式以外的文件!');
this.handleCancel();
return;
}
let reader = new FileReader();
this.setState({ status: -2 });
setTimeout(() => {
this.setState({ status: 0 });
reader.onload = () => {
var image = new Image();
// 被注释掉的是用来应对ios巨大图片没来得及加载的问题
// image.onload = () => setTimeout(() => this.convert(image), 1000);
image.onload = () => this.convert(image);
image.src = reader.result;
this.setState({ status: 1 });
};
reader.readAsDataURL(file);
}, 300);
}
convert(img) {
if (this.converted) return;
var canvas = document.createElement('canvas');
var { width, height } = img;
if (width > 1600) {
height *= 1600 / width;
width = 1600;
}
if (height > 1200) {
width *= 1200 / height;
height = 1200;
}
canvas.width = width;
canvas.height = height;
this.imgWidth = width;
this.imgHeight = height;
canvas.getContext('2d').drawImage(img, 0, 0, width, height);
canvas.toBlob(blob => {
this.setState({ src: canvas.toDataURL('image/jpeg', 1), status: 2, file: blob, width: width / height * 200 });
}, 'image/jpeg', 1);
}
async upload() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
if (data.code === 200) {
this.setState({ status: 4 });
this.props.onUpload(data.data.url);
}
else {
this.setState({ status: 2 });
this.props.onUploadError(data.msg);
}
}
else {
this.setState({ status: 2 });
this.props.onUploadError('上传失败:服务器出错');
}
}
};
xhr.onerror = () => {
this.setState({ status: 2 });
this.props.onUploadError('请求失败,请检查网络');
}
xhr.onprogress = e => {
if (e.lengthComputable) {
var percent = Math.round(e.loaded * 100 / e.total);
this.setState({ progress: percent });
}
};
xhr.open("POST", apis.uploadImage, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
var fd = new FormData();
fd.append("image", this.state.file, 'image.jpg');
xhr.send(fd);
this.setState({ status: 3, progress: 0 });
}
scale() {
if (this.state.isScaled) return;
let el = this.imgRef.current;
let { x, y } = el.getBoundingClientRect();
let { innerWidth, innerHeight } = window;
console.log(x, y, innerWidth, innerHeight);
let initialScale = 200 / this.imgHeight,
terminalScale = Math.min(1, Math.min(innerWidth / this.imgWidth, innerHeight / this.imgHeight));
this.setState({ transform: `translate(${x - (innerWidth - this.imgWidth * terminalScale) / 2}px, ${y - (innerHeight - this.imgHeight * terminalScale) / 2}px) scale(${initialScale})`, isScaled: true });
setTimeout(() => {
this.setState({ transform: `translate(0, 0) scale(${terminalScale})` });
}, 0);
}
unscale() {
if (!this.state.isScaled) return;
let el = this.imgRef.current;
let { x, y } = el.getBoundingClientRect();
let { innerWidth, innerHeight } = window;
let initialScale = 200 / this.imgHeight,
terminalScale = Math.min(1, Math.min(innerWidth / this.imgWidth, innerHeight / this.imgHeight));
this.setState({ transform: `translate(${x - (innerWidth - this.imgWidth * terminalScale) / 2}px, ${y - (innerHeight - this.imgHeight * terminalScale) / 2}px) scale(${initialScale})`, isScaled: true });
setTimeout(() => {
this.setState({ isScaled: false });
}, 300);
}
render() {
const angle = this.state.progress / 100 * Math.PI * 2;
console.log(this.state.transform);
return (
<div className="upload-wrap">
<div className={this.state.status === -1 ? 'upload-unit' : 'upload-unit open'} style={{ width: this.state.status <= 1 ? '' : `${this.state.width}px` }}>
{
this.state.src
? <img src={this.state.src} alt="压缩后的图片" style={{ filter: this.state.status === -1 ? 'opacity(0)' : '' }} ref={this.imgRef} />
: null
}
{
this.state.status >= 0
? (
<Fragment>
<div className={this.state.status === 2 ? 'upload-unit-mask hide' : 'upload-unit-mask'}>
{
this.state.status === 3
? (
<div className="upload-unit-progress">
<div className="progress-circle">
<svg viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d={
`M 50,3 A 47,47 0 ${this.state.progress < 50 ? 0 : 1},1 ${50 + Math.sin(angle) * 47},${50 - Math.cos(angle) * 47}`
} fill="transparent" stroke="#FFF" strokeWidth="6" strokeLinecap="round"></path>
</svg>
</div>
<div className="progress-detail">
上传中
</div>
</div>
)
: null
}
{
this.state.status <= 1
? (
<div className="upload-unit-progress">
<Spinner />
<div className="progress-detail">
{this.state.status === 0 ? '加载中' : '压缩中'}
</div>
</div>
)
: null
}
{
this.state.status === 4
? (
<div style={{ color: '#FFF', fontSize: '16px' }}>上传成功</div>
)
: null
}
</div>
<div className="upload-btns">
{
this.state.status === 2
? (
<button className="btn btn-circle btn-ok" onClick={this.upload}>
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M939.717 106.665l-304.049 324.529-78.769 85.071-225.28 240.246-123.668-115.003-129.182-121.305-78.769 84.283 129.969 121.305 33.083 31.508 129.969 121.305 44.111 40.96 304.049-324.529 78.769-85.071 304.049-324.529-84.283-78.769z" fill="#ffffff"></path></svg>
</button>
)
: null
}
{
this.state.status === 2 || this.state.status === 4
? (
<button className="btn btn-circle btn-primary" onClick={this.scale}>
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M959.488 920.32l-39.168 39.168a27.676444 27.676444 0 0 1-39.139556 0l-162.133333-162.133333a27.648 27.648 0 0 1 0-39.168l39.139556-39.139556a27.648 27.648 0 0 1 39.168 0l162.133333 162.104889c10.808889 10.837333 10.808889 28.359111 0 39.168zM169.358222 712.419556c-149.959111-149.959111-149.959111-393.102222 0-543.061334 149.959111-149.959111 393.073778-149.959111 543.061334 0 149.959111 149.959111 149.959111 393.102222 0 543.061334-149.987556 149.959111-393.102222 149.959111-543.061334 0zM631.324444 249.742222c-105.358222-105.386667-276.195556-105.386667-381.582222 0a269.824 269.824 0 0 0 0 381.610667A269.824 269.824 0 1 0 631.324444 249.742222z" fill="#ffffff"></path><path d="M959.488 920.32l-39.168 39.168a27.676444 27.676444 0 0 1-39.139556 0l-162.133333-162.133333a27.648 27.648 0 0 1 0-39.168l39.139556-39.139556a27.648 27.648 0 0 1 39.168 0l162.133333 162.104889c10.808889 10.837333 10.808889 28.359111 0 39.168zM169.358222 712.419556c-149.959111-149.959111-149.959111-393.102222 0-543.061334 149.959111-149.959111 393.073778-149.959111 543.061334 0 149.959111 149.959111 149.959111 393.102222 0 543.061334-149.987556 149.959111-393.102222 149.959111-543.061334 0zM631.324444 249.742222c-105.358222-105.386667-276.195556-105.386667-381.582222 0a269.824 269.824 0 0 0 0 381.610667A269.824 269.824 0 1 0 631.324444 249.742222z" fill="#ffffff"></path></svg>
</button>
)
: null
}
<button className="btn btn-circle btn-gray" onClick={this.handleCancel}>
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M597.333333 512l284.444445 284.444444c2.929778 3.328 2.929778 22.357333 0 28.444445l-56.888889 56.888889c-6.087111 2.929778-25.116444 2.929778-28.444445 0L512 597.333333 227.555556 881.777778c-3.328 2.929778-22.328889 2.929778-28.444445 0l-56.888889-56.888889c-2.929778-6.087111-2.929778-25.116444 0-28.444445l284.444445-284.444444L142.222222 227.555556c-2.929778-3.328-2.929778-22.328889 0-28.444445l56.888889-56.888889c6.115556-2.929778 25.116444-2.929778 28.444445 0l284.444444 284.444445L796.444444 142.222222c3.328-2.929778 22.357333-2.929778 28.444445 0l56.888889 56.888889c2.929778 6.115556 2.929778 25.116444 0 28.444445L597.333333 512z" fill="#4a4a4a"></path></svg>
</button>
</div>
</Fragment>
)
: null
}
</div>
<div className={this.state.isScaled ? 'scaled-wrap open' : 'scaled-wrap'} onClick={this.unscale}>
<img className="scaled-image" src={this.state.src} alt="压缩后的图片" style={{ transform: this.state.transform }} />
</div>
</div>
);
}
}