难以将裁剪的图像上传到使用混音和反应 - 杂乱
使用工作解决方案更新的代码
我试图做的事情:
就像标题所说的那样,我试图使用React-Easy-crop和Remix上传裁剪的图像到云。
让我从有效的内容开始:
将图像上传到云的功能非常完美。在remix上有一个很棒的示例。将图像上传到云。
上下文:我将Twitter克隆作为一个人项目。图像上传到云,并响应指向图像的链接,该链接被设置为用户对象上的属性。为什么?所以我不用图像填充蒙古德。
应该发生的事情/流动:
选择图像后,裁剪对话弹出。 裁剪图像后,
此时有一个裁剪图像的预览,裁剪图像为假定的上传至云,响应链接并将链接发布到我的数据库。
我认为正在进行的事情
i think ,我有从斑点转换回图像或base64文件的问题。
如果我没有意义,我深表歉意,我对此我仍然想解决它。这是代码:
另外,请尝试忽略所有“任何”类型。在这一点上只是试图让这项工作。 uploadbannerimage.tsx
import type { SyntheticEvent } from 'react'
import { useEffect, useState } from 'react';
import type { ActionFunction, UploadHandler } from "@remix-run/node";
import {
json,
unstable_composeUploadHandlers as composeUploadHandlers,
unstable_createMemoryUploadHandler as createMemoryUploadHandler,
unstable_parseMultipartFormData as parseMultipartFormData,
} from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { uploadImage } from "~/utils/utils.server";
import Cropper from 'react-easy-crop'
import getCroppedImg from '~/utils/getCroppedImg';
import type { Area, Point } from 'react-easy-crop'
type ActionData = {
errorMsg?: string;
imgSrc?: string;
};
export const action: ActionFunction = async ({ request }) => {
const uploadHandler: UploadHandler = composeUploadHandlers(
async ({ name, data }) => {
if (name !== "img") return null
const uploadedImage = await uploadImage(data)
return uploadedImage.secure_url;
},
createMemoryUploadHandler()
);
const formData = await parseMultipartFormData(request, uploadHandler);
const imgSrc = formData.get("img");
// placeholder function to log the src of the image
// in the main app the imgSrc will be the link posted to the database
async function logger(src: FormDataEntryValue): Promise<any> {
console.log('imgSrc: ', src.toString())
}
if (!imgSrc) return json({ error: "something wrong" });
return json({ imgSrc }, await logger(imgSrc));
};
export default function Index() {
const data = useActionData<ActionData>();
const [file, setFile] = useState<File | null>(null)
const [fileToCrop, setFileToCrop] = useState<string>('')
const [crop, setCrop] = useState<Point>({ x: 2, y: 2 });
const [zoom, setZoom] = useState(1);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area>()
const [croppedImage, setCroppedImage] = useState<Blob | null>(null)
const [imageToUpload, setImageToUpload] = useState<string>()
const [previewImage, setPreviewImage] = useState<string>()
useEffect(() => {
if (!croppedImage) return;
let cropped: Blob | string = URL.createObjectURL(croppedImage)
setPreviewImage(cropped)
const convertCropped = () => {
const reader = new FileReader()
reader.readAsDataURL(croppedImage)
reader.onloadend = () => {
setImageToUpload(reader.result as string)
}
reader.onerror = () => {
console.error('error')
}
}
convertCropped()
}, [file, croppedImage])
const onSelectFile = async (e: SyntheticEvent) => {
const target = e.target as HTMLInputElement
if (!target.files || target.files?.length === 0) {
setFile(null)
return
}
setFile(target.files[0])
setFileToCrop(URL.createObjectURL(target.files[0]))
}
const onCropComplete = (_croppedArea: Area, croppedAreaPixels: Area) => {
setCroppedAreaPixels(croppedAreaPixels);
};
const onCrop = async () => {
setCroppedImage(await getCroppedImg(fileToCrop, croppedAreaPixels as Area))
setFile(null)
};
const cancelImage = () => setFile(null)
return (
<div className="text-center mt-56">
<label htmlFor="img-field"></label>
<input id="img-field" type="file" name="img" accept="image/*" onChange={onSelectFile} />
{file && (
<>
<div className="fixed bg-black top-0 left-0 right-0 bottom-0 z-10 opacity-50"></div>
<div className="fixed top-0 left-0 right-0 bottom-20 z-20">
<Cropper
image={fileToCrop}
crop={crop}
zoom={zoom}
aspect={1}
onCropChange={setCrop}
onCropComplete={onCropComplete}
onZoomChange={setZoom}
/>
</div>
<div className="fixed bottom-0 w-full h-[100px] z-20 mb-10">
<div className="place-content-center">
<input
type="range"
min={1}
max={3}
step={0.05}
value={zoom}
onInput={(e: any) => {
setZoom(e.target.value);
}}
className="w-1/2"
></input>
</div>
<div className="place-content-center mt-12 mb-10">
<button
type='button'
className="bg-rose-400 m-5"
onClick={() => cancelImage()}
>
clear image
</button>
<button
type='button'
className="bg-purple-800 m-5"
onClick={onCrop}
>
Crop
</button>
</div>
</div>
</>
)}
{croppedAreaPixels && !data?.imgSrc ? (
<>
<Form method="post" encType="multipart/form-data">
<input
name="img"
type='hidden'
value={imageToUpload}
/>
<img
src={previewImage}
alt=''
/>
<button
type="submit"
className="bg-slate-400 m-5"
>
upload banner
</button>
</Form>
</>
) : null}
{data?.errorMsg && <h2>{data.errorMsg}</h2>}
{data?.imgSrc && (
<>
<h2>uploaded image</h2>
<img src={data.imgSrc} alt={'' || "Upload result"} />
</>
)}
</div>
);
}
utils.server.ts
import cloudinary from "cloudinary";
import { writeAsyncIterableToWritable } from "@remix-run/node";
cloudinary.v2.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET_KEY,
});
async function uploadImage(data: AsyncIterable<Uint8Array>) { // AsyncIterable<Uint8Array>
const uploadPromise = new Promise(async (resolve, reject) => {
const uploadStream = cloudinary.v2.uploader.upload_stream({
folder: "randy-demo",
},
(error, result) => {
if (error) {
reject(error); return;
} resolve(result);
});
await writeAsyncIterableToWritable(data, uploadStream);
});
return uploadPromise as Promise<{ secure_url: string }>
}
// console.log("configs", cloudinary.v2.config());
export { uploadImage };
**getCroppedImg.ts**
import type { Area } from 'react-easy-crop'
const createImage = (url: string): Promise<HTMLImageElement> =>
new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener("load", () => resolve(image));
image.addEventListener("error", (error) => reject(error));
image.setAttribute("crossOrigin", "anonymous"); // needed to avoid cross-origin issues on CodeSandbox
image.src = url;
});
function getRadianAngle(degreeValue: number) {
return (degreeValue * Math.PI) / 180;
}
export default async function getCroppedImg(imageSrc: string, pixelCrop: Area, rotation = 0): Promise<Blob | null> {
const image = await createImage(imageSrc)
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const maxSize = Math.max(image.width, image.height);
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea;
canvas.height = safeArea;
// translate canvas context to a central location on image to allow rotating around the center.
ctx?.translate(safeArea / 2, safeArea / 2);
ctx?.rotate(getRadianAngle(rotation));
ctx?.translate(-safeArea / 2, -safeArea / 2);
// draw rotated image and store data.
ctx?.drawImage(
image,
safeArea / 2 - image.width * 0.5,
safeArea / 2 - image.height * 0.5
);
const data = ctx?.getImageData(0, 0, safeArea, safeArea) as ImageData
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
// paste generated rotate image with correct offsets for x,y crop values.
ctx?.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
);
// ! this is the one that kind of works
return new Promise((resolve) => {
canvas.toBlob((file) => {
// console.log(file);
resolve(file);
//returns file which is the blob
}, "image/jpeg");
});
}
感谢您的查看。任何帮助都将受到赞赏。
Code updated with a working solution
I want to apologize ahead of time if im asking the wrong questions here, if it isnt apparent im lacking in experience. moving forward
what im trying to do:
like the title says, im trying to upload a cropped image to cloudinary using react-easy-crop and remix.
let me start with whats working:
uploading an image to cloudinary works perfectly. there is a fantastic example on the remix.run github on uploading images to cloudinary.
context: im making a twitter clone as a person project. the image uploads to cloudinary and responds with a link to the image which gets set as an attribute on a user object. why? so i dont fill mongodb with images.
whats supposed to be happening/the flow:
after selecting the image, the crop dialogue pops up.
after cropping the image there is a preview of the cropped image
at this point the cropped image is supposed to upload to cloudinary, respond with the link and post the link to my database.
what i think is going on
i think, im having issues converting from a blob back to an image or base64 file.
i apologize if im making no sense, im burnet out with this but still want to solve it. heres the code:
also, try to ignore all the 'any' types. just trying to get this working at this point.
UploadBannerImage.tsx
import type { SyntheticEvent } from 'react'
import { useEffect, useState } from 'react';
import type { ActionFunction, UploadHandler } from "@remix-run/node";
import {
json,
unstable_composeUploadHandlers as composeUploadHandlers,
unstable_createMemoryUploadHandler as createMemoryUploadHandler,
unstable_parseMultipartFormData as parseMultipartFormData,
} from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";
import { uploadImage } from "~/utils/utils.server";
import Cropper from 'react-easy-crop'
import getCroppedImg from '~/utils/getCroppedImg';
import type { Area, Point } from 'react-easy-crop'
type ActionData = {
errorMsg?: string;
imgSrc?: string;
};
export const action: ActionFunction = async ({ request }) => {
const uploadHandler: UploadHandler = composeUploadHandlers(
async ({ name, data }) => {
if (name !== "img") return null
const uploadedImage = await uploadImage(data)
return uploadedImage.secure_url;
},
createMemoryUploadHandler()
);
const formData = await parseMultipartFormData(request, uploadHandler);
const imgSrc = formData.get("img");
// placeholder function to log the src of the image
// in the main app the imgSrc will be the link posted to the database
async function logger(src: FormDataEntryValue): Promise<any> {
console.log('imgSrc: ', src.toString())
}
if (!imgSrc) return json({ error: "something wrong" });
return json({ imgSrc }, await logger(imgSrc));
};
export default function Index() {
const data = useActionData<ActionData>();
const [file, setFile] = useState<File | null>(null)
const [fileToCrop, setFileToCrop] = useState<string>('')
const [crop, setCrop] = useState<Point>({ x: 2, y: 2 });
const [zoom, setZoom] = useState(1);
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area>()
const [croppedImage, setCroppedImage] = useState<Blob | null>(null)
const [imageToUpload, setImageToUpload] = useState<string>()
const [previewImage, setPreviewImage] = useState<string>()
useEffect(() => {
if (!croppedImage) return;
let cropped: Blob | string = URL.createObjectURL(croppedImage)
setPreviewImage(cropped)
const convertCropped = () => {
const reader = new FileReader()
reader.readAsDataURL(croppedImage)
reader.onloadend = () => {
setImageToUpload(reader.result as string)
}
reader.onerror = () => {
console.error('error')
}
}
convertCropped()
}, [file, croppedImage])
const onSelectFile = async (e: SyntheticEvent) => {
const target = e.target as HTMLInputElement
if (!target.files || target.files?.length === 0) {
setFile(null)
return
}
setFile(target.files[0])
setFileToCrop(URL.createObjectURL(target.files[0]))
}
const onCropComplete = (_croppedArea: Area, croppedAreaPixels: Area) => {
setCroppedAreaPixels(croppedAreaPixels);
};
const onCrop = async () => {
setCroppedImage(await getCroppedImg(fileToCrop, croppedAreaPixels as Area))
setFile(null)
};
const cancelImage = () => setFile(null)
return (
<div className="text-center mt-56">
<label htmlFor="img-field"></label>
<input id="img-field" type="file" name="img" accept="image/*" onChange={onSelectFile} />
{file && (
<>
<div className="fixed bg-black top-0 left-0 right-0 bottom-0 z-10 opacity-50"></div>
<div className="fixed top-0 left-0 right-0 bottom-20 z-20">
<Cropper
image={fileToCrop}
crop={crop}
zoom={zoom}
aspect={1}
onCropChange={setCrop}
onCropComplete={onCropComplete}
onZoomChange={setZoom}
/>
</div>
<div className="fixed bottom-0 w-full h-[100px] z-20 mb-10">
<div className="place-content-center">
<input
type="range"
min={1}
max={3}
step={0.05}
value={zoom}
onInput={(e: any) => {
setZoom(e.target.value);
}}
className="w-1/2"
></input>
</div>
<div className="place-content-center mt-12 mb-10">
<button
type='button'
className="bg-rose-400 m-5"
onClick={() => cancelImage()}
>
clear image
</button>
<button
type='button'
className="bg-purple-800 m-5"
onClick={onCrop}
>
Crop
</button>
</div>
</div>
</>
)}
{croppedAreaPixels && !data?.imgSrc ? (
<>
<Form method="post" encType="multipart/form-data">
<input
name="img"
type='hidden'
value={imageToUpload}
/>
<img
src={previewImage}
alt=''
/>
<button
type="submit"
className="bg-slate-400 m-5"
>
upload banner
</button>
</Form>
</>
) : null}
{data?.errorMsg && <h2>{data.errorMsg}</h2>}
{data?.imgSrc && (
<>
<h2>uploaded image</h2>
<img src={data.imgSrc} alt={'' || "Upload result"} />
</>
)}
</div>
);
}
utils.server.ts
import cloudinary from "cloudinary";
import { writeAsyncIterableToWritable } from "@remix-run/node";
cloudinary.v2.config({
cloud_name: process.env.CLOUDINARY_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET_KEY,
});
async function uploadImage(data: AsyncIterable<Uint8Array>) { // AsyncIterable<Uint8Array>
const uploadPromise = new Promise(async (resolve, reject) => {
const uploadStream = cloudinary.v2.uploader.upload_stream({
folder: "randy-demo",
},
(error, result) => {
if (error) {
reject(error); return;
} resolve(result);
});
await writeAsyncIterableToWritable(data, uploadStream);
});
return uploadPromise as Promise<{ secure_url: string }>
}
// console.log("configs", cloudinary.v2.config());
export { uploadImage };
**getCroppedImg.ts**
import type { Area } from 'react-easy-crop'
const createImage = (url: string): Promise<HTMLImageElement> =>
new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener("load", () => resolve(image));
image.addEventListener("error", (error) => reject(error));
image.setAttribute("crossOrigin", "anonymous"); // needed to avoid cross-origin issues on CodeSandbox
image.src = url;
});
function getRadianAngle(degreeValue: number) {
return (degreeValue * Math.PI) / 180;
}
export default async function getCroppedImg(imageSrc: string, pixelCrop: Area, rotation = 0): Promise<Blob | null> {
const image = await createImage(imageSrc)
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const maxSize = Math.max(image.width, image.height);
const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2));
// set each dimensions to double largest dimension to allow for a safe area for the
// image to rotate in without being clipped by canvas context
canvas.width = safeArea;
canvas.height = safeArea;
// translate canvas context to a central location on image to allow rotating around the center.
ctx?.translate(safeArea / 2, safeArea / 2);
ctx?.rotate(getRadianAngle(rotation));
ctx?.translate(-safeArea / 2, -safeArea / 2);
// draw rotated image and store data.
ctx?.drawImage(
image,
safeArea / 2 - image.width * 0.5,
safeArea / 2 - image.height * 0.5
);
const data = ctx?.getImageData(0, 0, safeArea, safeArea) as ImageData
// set canvas width to final desired crop size - this will clear existing context
canvas.width = pixelCrop.width;
canvas.height = pixelCrop.height;
// paste generated rotate image with correct offsets for x,y crop values.
ctx?.putImageData(
data,
Math.round(0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x),
Math.round(0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y)
);
// ! this is the one that kind of works
return new Promise((resolve) => {
canvas.toBlob((file) => {
// console.log(file);
resolve(file);
//returns file which is the blob
}, "image/jpeg");
});
}
thanks for looking. any help is appreciated.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论