🔎
react-image-cropを使って、プロフィールの丸い画像を作ってみる
色々探してみて以下2つが良さそうだと感じました。
Weekly Downloadsを見てみると、react-image-cropの方がダウンロード数が多そうなので、今回はこちらを使用します。
作ってみたもの
まず画像をアップロードして、切り取りたいところを選択して、切り取りボタンを押すと、
選択部分を切り取れるものになります。
ソースコード
import React, { ChangeEvent, FormEvent, useEffect, useState } from "react";
import ReactCrop from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
import { Crop } from "react-image-crop/dist/types";
const CropImg = () => {
const [fileData, setFileData] = useState<File | undefined>();
const [objectUrl, setObjectUrl] = useState<string | undefined>();
//プロフィールイメージ
const [profileImg, setProfileImg] = useState<string>("");
//Crop
const [crop, setCrop] = useState<Crop>({
unit: "px", // 'px' または '%' にすることができます
x: 0,
y: 0,
width: 200,
height: 200,
});
//アップロードした画像のObjectUrlをステイトに保存する
useEffect(() => {
if (fileData instanceof File) {
objectUrl && URL.revokeObjectURL(objectUrl);
setObjectUrl(URL.createObjectURL(fileData));
} else {
setObjectUrl(undefined);
}
}, [fileData]);
//切り取った画像のObjectUrlを作成し、ステイトに保存する
const makeProfileImgObjectUrl = async () => {
if (objectUrl) {
const canvas = document.createElement("canvas");
canvas.width = crop.width;
canvas.height = crop.height;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
ctx.beginPath();
ctx.arc(
canvas.width / 2,
canvas.height / 2,
canvas.width / 2,
0,
2 * Math.PI,
false
);
ctx.clip();
const img = await loadImage(objectUrl);
console.log(img.width, img.naturalWidth);
ctx.drawImage(
img,
crop.x,
crop.y,
crop.width,
crop.height,
0,
0,
crop.width,
crop.height
);
canvas.toBlob((result) => {
if (result instanceof Blob) setProfileImg(URL.createObjectURL(result));
});
}
};
// canvasで画像を扱うため
// アップロードした画像のObjectUrlをもとに、imgのHTMLElementを作る
const loadImage = (src: string): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
const img = new Image();
img.src = src;
img.onload = () => resolve(img);
});
};
return (
<div>
<form
onSubmit={(e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
makeProfileImgObjectUrl();
}}
>
<input
type="file"
onChange={(e: ChangeEvent<HTMLInputElement>) => {
e.target.files && setFileData(e.target.files[0]);
}}
/>
<button>切り取り</button>
</form>
<div>
{objectUrl && (
<ReactCrop
crop={crop}
onChange={(c) => setCrop(c)}
aspect={1}
circularCrop={true}
keepSelection={true}
>
<img src={objectUrl} alt="" style={{ width: "100%" }} />
</ReactCrop>
)}
</div>
<div>
{profileImg ? <img src={profileImg} alt="プロフィール画像" /> : ""}
</div>
</div>
);
};
export default CropImg;
Discussion
画像がずれるということですが、おそらくImageのnaturalWidth, naturalHeightとwidth, heightの比率を考えてないからだと思われます。
また(あんまりないと思いますが)デバイスによってピクセルの縦横比が違うことも考慮に入れないとユーザーのデバイスによってはずれたりずれなかったりなどがあると思うため、このあたりもケアしてあげないと結果的にずれるといったことになると思います。
私は次のようなコードになりました…(一部抜粋です)