Reputation: 765
How do I get the cropped image from canvas with DominicTobias/react-image-crop
,
and set it as file input value for a form to submit to backend?
I followed this tutorial: https://levelup.gitconnected.com/crop-images-on-upload-in-your-react-app-with-react-image-crop-5f3cd0ad2b35
but it does not work, the code is outdated, onImageLoaded
does not exist
here is my code:
import Head from 'next/head'
import header_style from '../../styles/header/header.module.css'
import desktop_style from '../../styles/desktop/desktop.module.css'
import loading_bar_style from '../../styles/loading_bar/loading_bar.module.css'
import React, { Component, useEffect, useState, useRef } from "react";
import {withStore} from "react-context-hook";
import ReactCrop, {
centerCrop,
makeAspectCrop,
Crop,
PixelCrop,
} from 'react-image-crop'
import { canvasPreview } from '../../components/user/edit/avatar/crop/canvas_preview.js'
import { useDebounceEffect } from '../../services/useDebounceEffect'
import 'react-image-crop/dist/ReactCrop.css'
import axios from "axios";
// This is to demonstate how to make and center a % aspect crop
// which is a bit trickier so we use some helper functions.
/*
|--------------------------------------------------------------------------
| Helper : Center Aspect Crop
|--------------------------------------------------------------------------
*/
function centerAspectCrop(
mediaWidth, // : number
mediaHeight, // : number
aspect, // : number
) {
return centerCrop(
makeAspectCrop(
{
unit: '%',
width: 90,
},
aspect,
mediaWidth,
mediaHeight,
),
mediaWidth,
mediaHeight,
)
}
/*
|--------------------------------------------------------------------------
| Class : Crop Avatar
|--------------------------------------------------------------------------
*/
function CropAvatar({ results }) {
const [imgSrc, setImgSrc] = useState('');
const smallPreviewCanvasRef = useRef(null); // <HTMLCanvasElement>
const mediumPreviewCanvasRef = useRef(null); // <HTMLCanvasElement>
const bigPreviewCanvasRef = useRef(null); // <HTMLCanvasElement>
const imgRef = useRef(null); // <HTMLImageElement>
const [crop, setCrop] = useState(); // <Crop>
const [completedCrop, setCompletedCrop] = useState(); // <PixelCrop>
const [scale, setScale] = useState(1);
const [rotate, setRotate] = useState(0);
const [aspect, setAspect] = useState(1); // <number | undefined>
const [blobImgFile, setBlobImgFile] = useState(null); // <number | undefined>
const imageRef = useRef(null);
const [croppedImageUrl, setCroppedImageUrl] = useState(null);
const [croppedImage, setCroppedImage] = useState(null);
/*
|--------------------------------------------------------------------------
| Function : On Selected Input File
|--------------------------------------------------------------------------
*/
function onSelectFile(e) { // : React.ChangeEvent<HTMLInputElement>
if (e.target.files && e.target.files.length > 0) {
setCrop(undefined) // Makes crop preview update between images.
const reader = new FileReader()
reader.addEventListener('load', () =>
setImgSrc(reader.result?.toString() || ''),
)
reader.readAsDataURL(e.target.files[0])
}
}
/*
|--------------------------------------------------------------------------
| Function : On Image Load
|--------------------------------------------------------------------------
*/
function onImageLoad(e) { // : React.SyntheticEvent<HTMLImageElement>
if (aspect) {
const { width, height } = e.currentTarget
setCrop(centerAspectCrop(width, height, aspect))
}
}
/*
|--------------------------------------------------------------------------
| Function : Image Source To File
|--------------------------------------------------------------------------
*/
async function imageSrcToFile(src, fileName) {
axios.get(src, {
responseType: 'blob'
}).then(async response => {
const mimeType = response.headers['content-type'];
const file = new File([response.data], fileName, { type: mimeType });
// access file here
setCrop(undefined) // Makes crop preview update between images.
const reader = new FileReader()
reader.addEventListener('load', () =>
setImgSrc(reader.result?.toString() || ''),
)
reader.readAsDataURL(file)
}).catch((error) => {
// Error
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
// console.log(error.response.data);
// console.log(error.response.status);
// console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the
// browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
});
}
const onImageLoaded = (image) => {
alert('onImageLoaded');
imageRef.current = image
};
/*
|--------------------------------------------------------------------------
| Function : Make Client Crop
|--------------------------------------------------------------------------
*/
async function makeClientCrop(crop) {
// if (this.imageRef && crop.width && crop.height) {
//
// const croppedImageUrl = await this.getCroppedImg(
// this.imageRef,
// crop,
// "newFile.jpeg"
// );
//
// this.setState({ croppedImageUrl });
// }
}
const getCroppedImg = (imageFile, pixelCrop) => {
const canvas = document.createElement("canvas");
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width,
crop.height
)
const reader = new FileReader()
canvas.toBlob(blob => {
reader.readAsDataURL(blob)
reader.onloadend = () => {
dataURLtoFile(reader.result, 'cropped.jpg')
}
})
}
function dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
let croppedImage = new File([u8arr], filename, {type:mime});
setCroppedImage(croppedImage);
}
const handleOnCropComplete = (pixelCrop) => {
console.log('TTTT > handleOnCropComplete > Called');
if (imageRef.current && pixelCrop.width && pixelCrop.height) {
console.log('TTTT > handleOnCropComplete > imageRef.current && pixelCrop.width && pixelCrop.height');
const croppedImageUrl = getCroppedImg(imageRef, crop)
setCroppedImageUrl(croppedImageUrl);
}
}
/*
|--------------------------------------------------------------------------
| Use Effect 1
|--------------------------------------------------------------------------
*/
useEffect(() => {
const url = 'http://localhost/lounge55/public/storage/uploads/images/avatars/1/WTlvNXKdTJAIYLzEnGySAj0ZltV9UHFRspPCEskB.jpeg'
// const url = 'https://w315.luscious.net/DirtyOldMan/479276/fhntorwucauqlad_01GHJ7JBDZCE5DWE1JAR8H382Q.315x0.jpg'
// const url = 'https://cdn.shopify.com/s/files/1/0234/8017/2591/products/young-man-in-bright-fashion_925x_f7029e2b-80f0-4a40-a87b-834b9a283c39.jpg?v=1572867553'
const fileName = 'myFile.jpg'
imageSrcToFile(url, fileName).then();
}, []);
/*
|--------------------------------------------------------------------------
| Use Effect 2
|--------------------------------------------------------------------------
*/
useEffect(() => {
console.log('croppedImageUrl: ' + JSON.stringify(croppedImageUrl, null, 2));
if(bigPreviewCanvasRef.current) {
bigPreviewCanvasRef.current.toBlob(
(blob) => {
const newImage = new File([blob], blob.name, {type: blob.type,});
console.log('bigPreviewCanvasRef.current > newImage: ' + JSON.stringify(newImage, null, 2));
},
'image/jpg',
1
)
}
}, [croppedImageUrl]);
/*
|--------------------------------------------------------------------------
| Use Debounce Effect
|--------------------------------------------------------------------------
*/
useDebounceEffect(
async () => {
if (
completedCrop?.width &&
completedCrop?.height &&
imgRef.current &&
smallPreviewCanvasRef.current &&
mediumPreviewCanvasRef.current &&
bigPreviewCanvasRef.current
) {
// We use canvasPreview as it's much faster than imgPreview.
canvasPreview(
imgRef.current,
smallPreviewCanvasRef.current,
completedCrop,
scale,
rotate,
)
// We use canvasPreview as it's much faster than imgPreview.
canvasPreview(
imgRef.current,
mediumPreviewCanvasRef.current,
completedCrop,
scale,
rotate,
)
// We use canvasPreview as it's much faster than imgPreview.
canvasPreview(
imgRef.current,
bigPreviewCanvasRef.current,
completedCrop,
scale,
rotate,
)
}
},
100,
[completedCrop, scale, rotate],
)
/*
|--------------------------------------------------------------------------
| handle Toggle Aspect Click
|--------------------------------------------------------------------------
*/
function handleToggleAspectClick() {
if (aspect) {
setAspect(undefined)
} else if (imgRef.current) {
const { width, height } = imgRef.current
setAspect(1)
setCrop(centerAspectCrop(width, height, 1))
}
}
/*
|--------------------------------------------------------------------------
| Return
|--------------------------------------------------------------------------
*/
return (
<main className="main-padding">
<React.Fragment>
<div className="c-page-container">
<a id="top" />
<div id="main">
<div style={{display: 'flex'}}>
{/*|-------------------------------------------------------------------------- */}
{/*| Left Column */}
{/*|-------------------------------------------------------------------------- */}
<div style={{margin: '20px', flex: 1}}>
<h1> Crop Avatar </h1>
<div id="picture">
{/*|-------------------------------------------------------------------------- */}
{/*| Image Canvas */}
{/*|-------------------------------------------------------------------------- */}
{JSON.stringify(completedCrop, null, 2)}
{!!imgSrc && (
<ReactCrop
crop={crop}
onImageLoaded={(image) =>
alert('onImageLoaded')
}
onChange={(_, percentCrop) => setCrop(percentCrop)}
onComplete={(c) => {
setCompletedCrop(c)
handleOnCropComplete(c)
}}
aspect={aspect}
style={{ width: '100%' }}
>
<img
ref={imgRef}
alt="Crop me"
src={imgSrc}
style={{ transform: `scale(${scale}) rotate(${rotate}deg)`, width: '100%' }}
onLoad={onImageLoad}
/>
</ReactCrop>
)}
</div>
<form id="crop_form" action="." method="post">
<input type="hidden" name="top" id="id_top" />
<input type="hidden" name="left" id="id_left" />
<input type="hidden" name="right" id="id_right" />
<input type="hidden" name="bottom" id="id_bottom" />
<input type="submit" defaultValue="Crop" /> | <a href="/users/1735407/">Cancel</a>
</form>
</div>
{/*|-------------------------------------------------------------------------- */}
{/*| Right Column */}
{/*|-------------------------------------------------------------------------- */}
<div style={{margin: '20px', flex: 1}}>
<div className="panel" style={{alignItems: 'stretch', height: 'auto', minHeight: '50px', border: '1px solid #3a3a3a', margin: '-1px 0', padding: '1rem', backgroundColor: '#1d1d1d'}}>
<h2>Small preview</h2>
<div className="img_preview" id="img_small_preview">
{/*<img src="//cdna.luscious.net/avatars/sputznik3030/8e1c5e7a8aed28bcc7799f046770764c.jpg" alt="Small preview" />*/}
{!!completedCrop && (
<canvas
ref={smallPreviewCanvasRef}
style={{
border: '1px solid black',
objectFit: 'contain',
width: 32,
height: 32,
}}
/>
)}
</div>
</div>
<div className="panel" style={{alignItems: 'stretch', height: 'auto', minHeight: '50px', border: '1px solid #3a3a3a', margin: '-1px 0', padding: '1rem', backgroundColor: '#1d1d1d'}}>
<h2>Medium preview</h2>
<div className="img_preview" id="img_medium_preview">
{/*<img src="//cdna.luscious.net/avatars/sputznik3030/8e1c5e7a8aed28bcc7799f046770764c.jpg" alt="Medium preview" />*/}
{!!completedCrop && (
<canvas
ref={mediumPreviewCanvasRef}
style={{
border: '1px solid black',
objectFit: 'contain',
width: 80,
height: 80,
}}
/>
)}
</div>
</div>
<div className="panel" style={{alignItems: 'stretch', height: 'auto', minHeight: '50px', border: '1px solid #3a3a3a', margin: '-1px 0', padding: '1rem', backgroundColor: '#1d1d1d'}}>
<h2>Large preview</h2>
<div className="img_preview" id="img_big_preview">
{/*<img src="//cdna.luscious.net/avatars/sputznik3030/8e1c5e7a8aed28bcc7799f046770764c.jpg" alt="Large preview" />*/}
{!!completedCrop && (
<canvas
ref={bigPreviewCanvasRef}
style={{
border: '1px solid black',
objectFit: 'contain',
width: 320,
height: 320,
}}
/>
)}
</div>
</div>
</div>
</div>
<div>
{/*|-------------------------------------------------------------------------- */}
{/*| Section : Crop Controls */}
{/*|-------------------------------------------------------------------------- */}
<div className="Crop-Controls">
{/*|-------------------------------------------------------------------------- */}
{/*| Input : File */}
{/*|-------------------------------------------------------------------------- */}
<input type="file" accept="image/*" onChange={onSelectFile} />
{/*|-------------------------------------------------------------------------- */}
{/*| Input : Scale */}
{/*|-------------------------------------------------------------------------- */}
<div>
<label htmlFor="scale-input">Scale: </label>
<input
id="scale-input"
type="number"
step="0.1"
value={scale}
disabled={!imgSrc}
onChange={(e) => setScale(Number(e.target.value))}
/>
</div>
{/*|-------------------------------------------------------------------------- */}
{/*| Input : Rotate */}
{/*|-------------------------------------------------------------------------- */}
<div>
<label htmlFor="rotate-input">Rotate: </label>
<input
id="rotate-input"
type="number"
value={rotate}
disabled={!imgSrc}
onChange={(e) =>
setRotate(Math.min(180, Math.max(-180, Number(e.target.value))))
}
/>
</div>
{/*|-------------------------------------------------------------------------- */}
{/*| Button : Toggle Aspect Ratio */}
{/*|-------------------------------------------------------------------------- */}
<div>
<button onClick={handleToggleAspectClick}>
Toggle aspect {aspect ? 'off' : 'on'}
</button>
</div>
</div>
{/*/!*|-------------------------------------------------------------------------- *!/*/}
{/*/!*| Image Canvas *!/*/}
{/*/!*|-------------------------------------------------------------------------- *!/*/}
{/*{!!imgSrc && (*/}
{/* <ReactCrop*/}
{/* crop={crop}*/}
{/* onChange={(_, percentCrop) => setCrop(percentCrop)}*/}
{/* onComplete={(c) => setCompletedCrop(c)}*/}
{/* aspect={aspect}*/}
{/* >*/}
{/* <img*/}
{/* ref={imgRef}*/}
{/* alt="Crop me"*/}
{/* src={imgSrc}*/}
{/* style={{ transform: `scale(${scale}) rotate(${rotate}deg)` }}*/}
{/* onLoad={onImageLoad}*/}
{/* />*/}
{/* </ReactCrop>*/}
{/*)}*/}
{/*|-------------------------------------------------------------------------- */}
{/*| Cropped Area */}
{/*|-------------------------------------------------------------------------- */}
<div>
{/*{!!completedCrop && (*/}
{/* <canvas*/}
{/* ref={bigPreviewCanvasRef}*/}
{/* style={{*/}
{/* border: '1px solid black',*/}
{/* objectFit: 'contain',*/}
{/* width: completedCrop.width,*/}
{/* height: completedCrop.height,*/}
{/* }}*/}
{/* />*/}
{/*)}*/}
</div>
</div>
</div>
</div>
</React.Fragment>
</main>
)
}
/*
|--------------------------------------------------------------------------
| SSR : Server Side Rendering
|--------------------------------------------------------------------------
*/
// This gets called on every request
export async function getServerSideProps( context ) {
// Pass data to the page via props
return {
props: {
results: JSON.stringify(context.query, null, 2)
},
};
}
export default CropAvatar;
Upvotes: 2
Views: 515