react-image-crop: unable to convert cropped image from canvas & set it as file input val in form

406 Views Asked by At

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;

0

There are 0 best solutions below