1. 概要
前回はPDFを表示&ダウンロードする内容についてでした。今回は画像を切り取りし、保存する内容になります。
対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。
2. nodeのインストール
こちらを参考
3. プロジェクトを作成
3-1-1. こちらを参考
4. 必要なライブラリをインストール
4-1-1. こちらを参考
npm install --save react-cropper
5. ソースコード
※前回より差分のみを記載
5-1-1. src/app/components/component12/image-cropper.tsx
import React, { useState, RefObject, useRef } from "react";
import Image from "next/image";
import { Stack, Button, Fab } from "@mui/material";
import CropIcon from "@mui/icons-material/Crop";
import DownloadIcon from "@mui/icons-material/Download";
import UndoIcon from "@mui/icons-material/Undo";
import Cropper, { ReactCropperElement } from "react-cropper";
import "cropperjs/dist/cropper.css";
import scss from "./page.module.scss";
const imageLoader = ({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) => {
return `${src}?w=${width}&q=${quality || 75}`;
};
const ImageCropper = ({
defaultSrc,
downloadImgName,
}: {
defaultSrc: string;
downloadImgName: string;
}) => {
const [image, setImage] = useState<string>(defaultSrc);
const [cropData, setCropData] = useState<string>("#");
const cropperRef: RefObject<ReactCropperElement> =
useRef<ReactCropperElement>(null);
const onChange = (e: any) => {
e.preventDefault();
let files;
if (e.dataTransfer) {
files = e.dataTransfer.files;
} else if (e.target) {
files = e.target.files;
}
const reader = new FileReader();
reader.onload = () => {
setImage(reader.result as any);
};
reader.readAsDataURL(files[0]);
};
const getCropData = () => {
if (typeof cropperRef.current?.cropper !== "undefined") {
const cropper = cropperRef.current?.cropper;
setCropData(cropper.getCroppedCanvas().toDataURL());
}
};
const downloadImage = () => {
if (cropData === "#") {
alert(`Please crop image first.`);
return;
}
const aLink = document.createElement("a");
aLink.href = cropData;
aLink.download = downloadImgName;
aLink.click();
};
return (
<div>
<Stack direction="column" spacing={1} sx={{ width: "90%" }}>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
sx={{ width: "100%" }}
>
<input type="file" onChange={onChange} />
<Button
variant="contained"
startIcon={<UndoIcon />}
size="small"
color="secondary"
onClick={() => setImage(defaultSrc)}
>
Use Default Image
</Button>
</Stack>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
sx={{ width: "100%" }}
>
<Cropper
ref={cropperRef}
style={{ width: "100%", height: 400 }}
zoomTo={0.5}
initialAspectRatio={1}
preview="#img_preview"
src={image}
viewMode={1}
minCropBoxHeight={10}
minCropBoxWidth={10}
background={true}
responsive={true}
autoCropArea={1}
checkOrientation={false}
guides={true}
/>
</Stack>
<Stack
direction="row"
spacing={1}
justifyContent="center"
alignItems="center"
sx={{ width: "100%" }}
>
<Stack
direction="column"
spacing={1}
justifyContent="center"
alignItems="center"
sx={{ width: "42%" }}
>
<h2>Preview</h2>
<div
id="img_preview"
className={scss.img_preview}
style={{
width: "100%",
float: "left",
height: 300,
}}
/>
</Stack>
<Stack
direction="column"
spacing={1}
justifyContent="center"
alignItems="center"
sx={{ width: "16%" }}
>
<Fab
color="primary"
aria-label="Crop image"
onClick={() => getCropData()}
>
<CropIcon fontSize="large" />
</Fab>
<Fab
color="primary"
aria-label="Download image"
onClick={() => downloadImage()}
>
<DownloadIcon fontSize="large" />
</Fab>
</Stack>
<Stack
direction="column"
spacing={1}
justifyContent="center"
alignItems="center"
sx={{ width: "42%" }}
>
<h2>Cropped</h2>
{cropData === "#" ? (
<div>Not yet</div>
) : (
<Image
loader={imageLoader}
src={cropData}
alt="Cropped image"
width={0}
height={0}
sizes="100vw"
style={{ width: "100%", height: "auto" }}
/>
)}
</Stack>
</Stack>
</Stack>
<br style={{ clear: "both" }} />
</div>
);
};
export default ImageCropper;
5-1-2. src/app/components/component12/client-page.tsx
"use client";
import ImageCropper from "./image-cropper";
const ClientPage = () => {
const defaultSrc: string = "/panko-lineup.png";
const downloadImgName: string = "cropped-image.png";
return (
<div>
<ImageCropper defaultSrc={defaultSrc} downloadImgName={downloadImgName} />
</div>
);
};
export default ClientPage;
5-1-3. src/app/components/component12/page.module.scss
.component {
& ul {
margin-left: 20px;
& li {
list-style: disc;
}
}
& h2 {
font-size: 20px;
font-weight: bold;
}
& .img_preview {
overflow: hidden;
}
}
5-1-4. src/app/components/component12/page.tsx
import Divider from "@mui/material/Divider";
import GoBack from "@/lib/components/go-back";
import scss from "./page.module.scss";
import ClientPage from "./client-page";
const Component12 = () => {
return (
<div className={scss.component}>
<GoBack />
<br />
<br />
<ul>
<li>画像トリミング</li>
<ul>
<li>react-cropper</li>
</ul>
</ul>
<Divider sx={{ marginTop: 2, marginBottom: 2 }} />
<div>
<ClientPage />
</div>
</div>
);
};
export default Component12;
5-1-5. src/app/components/page.module.scss
.components {
color: blue;
& ul {
margin-left: 20px;
& li {
list-style: disc;
}
}
}
5-1-6. src/app/components/page.tsx
"use client";
import React from "react";
import { Link } from "@mui/material";
import scss from "./page.module.scss";
const Components = () => {
return (
<div className={scss.components}>
<ul>
<li>
<Link href="/components/component01" underline="hover">
Component01
</Link>
</li>
<li>
<Link href="/components/component02" underline="hover">
Component02
</Link>
</li>
<li>
<Link href="/components/component03" underline="hover">
Component03
</Link>
</li>
<li>
<Link href="/components/component04" underline="hover">
Component04
</Link>
</li>
<li>
<Link href="/components/component05" underline="hover">
Component05
</Link>
</li>
<li>
<Link href="/components/component06" underline="hover">
Component06
</Link>
</li>
<li>
<Link href="/components/component07" underline="hover">
Component07
</Link>
</li>
<li>
<Link href="/components/component08" underline="hover">
Component08
</Link>
</li>
<li>
<Link href="/components/component09" underline="hover">
Component09
</Link>
</li>
<li>
<Link href="/components/component10" underline="hover">
Component10
</Link>
</li>
<li>
<Link href="/components/component11" underline="hover">
Component11
</Link>
</li>
<li>
<Link href="/components/component12" underline="hover">
Component12
</Link>
</li>
</ul>
</div>
);
};
export default Components;
6. サーバーを起動
npm run dev
7. ブラウザで確認
- http://localhost:3000
7-1-1. 切り取り

7-2-1. ダウンロード

8. ディレクトリの構造
省略
9. 備考
今回は画像を切り取りし、保存する内容についてでした。
10. 参考
- Docs | Next.js (nextjs.org)
- Quick Start – React
- Getting Started with Redux | Redux
- Getting Started with React Redux | React Redux (react-redux.js.org)
- Material UI: React components based on Material Design (mui.com)
- react-cropper – npm
- Home | Cropper.js
投稿者プロフィール

-
開発好きなシステムエンジニアです。
卓球にハマってます。
最新の投稿
【Next.js】2025年3月8日【NextJS】Cropping a portion of an image with React Cropper
【Next.js】2025年2月9日【NextJS】View and Download PDF
【AWS】2025年2月1日【AWS】Github ActionsやAWS SAMを使ってAWS S3・CloudFrontにウェブコンテンツをデプロイし、サブドメインにアクセスできるようにする
【AWS】2025年1月25日【AWS】Deploy Serverless NextJS app with AWS Lambda Web Adapter using AWS SAM