Input Image คือ view ใช้สำหรับรับค่า file รูป เพื่อนำไปอัพโหลดขึ้น server
import { useEffect, useRef, useState } from "react";
import ReactCrop, { centerCrop, makeAspectCrop } from "react-image-crop";
import "react-image-crop/dist/ReactCrop.css";
import styled from "styled-components";
import { fileToBase64, resizeImageBase64 } from "../../util/common";
import Modal from "../layout/Modal";
import IconCaption from "../element/icon/IconCaption";
import IconImage from "../element/icon/IconImage";
import IconPlus from "../element/icon/IconPlus";
import InputButton from "./InputButton";
import InputText from "./InputText";
export default function InputImage({
initValue,
onChange,
placeHolder = "Choose image file",
maxSize = 1500,
width = "200px",
height = "200px",
className,
borderRadius = "10px",
aspect = 600 / 300,
cropModalMaxWidth = "600px",
cropModalMaxHeight = "500px",
isCrop,
isCaption,
style,
}) {
const inputRef = useRef(null);
const [imgSrc, setImgSrc] = useState("");
const [isActiveFileChange, setIsActiveFileChange] = useState(true);
const [captionText, setCaptionText] = useState("");
const [isShowCaption, setIsShowCaption] = useState(false);
// crop
const imgRef = useRef(null);
const [isShowModalCrop, setIsShowModalCrop] = useState(false);
const [completedCrop, setCompletedCrop] = useState(null);
const [crop, setCrop] = useState();
useEffect(() => {
if (initValue) {
setImgSrc(initValue.file);
setCaptionText(initValue.caption);
}
}, [initValue]);
const imageChange = async (e) => {
let file = e?.target?.files[0];
if (file) {
if (isCrop) {
setCrop(null);
const fileBase64 = await fileToBase64(file);
setImgSrc(fileBase64);
setIsShowModalCrop(true);
} else {
const fileBase64 = await fileToBase64(file);
const resize = await resizeImageBase64(fileBase64, maxSize, maxSize);
if (resize) {
setImgSrc(resize);
}
if (onChange) {
onChange({
file: resize,
caption: captionText,
});
}
}
}
};
const onBlurHandler = () => {
if (onChange) {
onChange({ file: imgSrc, caption: captionText });
}
};
const handleCaptionChange = (e) => {
setCaptionText(e.target.value);
};
// crop
const onClickConfirmCrop = async () => {
if (imgRef.current && crop.width && crop.height) {
const croppedImageUrl = await getCroppedImg(imgRef.current);
if (croppedImageUrl) {
setImgSrc(croppedImageUrl);
}
if (onChange) {
onChange({ file: croppedImageUrl, caption: captionText });
}
setIsShowModalCrop(false);
}
};
const getCroppedImg = async (image) => {
const canvas = document.createElement("canvas");
const crop = completedCrop;
if (!crop || !canvas || !image) {
return;
}
const scaleX = image.naturalWidth / image.width;
const scaleY = image.naturalHeight / image.height;
const ctx = canvas.getContext("2d");
const pixelRatio = window.devicePixelRatio;
canvas.width = crop.width * pixelRatio * scaleX;
canvas.height = crop.height * pixelRatio * scaleY;
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
ctx.imageSmoothingQuality = "high";
ctx.drawImage(
image,
crop.x * scaleX,
crop.y * scaleY,
crop.width * scaleX,
crop.height * scaleY,
0,
0,
crop.width * scaleX,
crop.height * scaleY
);
const base64Image = canvas.toDataURL("image/png", 1);
return await resizeImageBase64(base64Image, maxSize, maxSize);
};
const centerAspectCrop = (mediaWidth, mediaHeight, aspect) => {
return centerCrop(
makeAspectCrop(
{
unit: "%",
width: 90,
},
aspect,
mediaWidth,
mediaHeight
),
mediaWidth,
mediaHeight
);
};
const onCloseModal = () => {
setIsShowModalCrop(false);
setImgSrc("");
};
const onImageLoad = (e) => {
if (aspect) {
const { width, height } = e.currentTarget;
setCrop(centerAspectCrop(width, height, aspect));
}
};
return (
<Styled
style={style}
className={className}
onBlur={onBlurHandler}
$width={width}
$height={height}
$borderRadius={borderRadius}
>
<div className={"outer-bg"}>
<div className={"outer-wrp"}>
<div className={"wrp-input-image"}>
{isActiveFileChange && (
<input
type="file"
ref={inputRef}
accept={"image/*"}
style={{ display: "none" }}
onBlur={onBlurHandler}
onChange={imageChange}
/>
)}
{!imgSrc && (
<>
<div className={"img_file_placeholder"}>
<IconImage width="50" color="#888" />
<InputButton
className={"btn_choose_file"}
text={placeHolder}
onClick={() => {
inputRef.current.click();
}}
/>
</div>
</>
)}
{(isCrop ? !isShowModalCrop : true) && imgSrc && (
<div className={"wrp_img"}>
<img src={imgSrc?.file ? imgSrc.file : imgSrc} />
</div>
)}
{imgSrc && (
<div className="wrp-cc-remove">
{isCaption && (
<InputButton
onClick={() => {
setIsShowCaption(true);
}}
design="none"
className={"btn-caption"}
icon={<IconCaption width="20" />}
/>
)}
<InputButton
design="none"
onClick={() => {
setIsActiveFileChange(false);
setTimeout(function () {
setImgSrc("");
setIsActiveFileChange(true);
if (onChange) {
onChange({
file: "",
caption: captionText,
});
}
}, 100);
}}
className={"btn-remove"}
icon={<IconPlus degree="45" width="20" />}
/>
</div>
)}
{isShowCaption && (
<div className="wrp-caption">
<InputText
value={captionText}
onChange={handleCaptionChange}
className={"input-caption " + (isShowCaption ? "active" : "")}
/>
<InputButton
onClick={() => {
setIsShowCaption((prev) => !prev);
}}
design="solid"
className={"btn-save"}
text={"Save"}
/>
</div>
)}
</div>
</div>
</div>
{isShowModalCrop && (
<Modal
className="modal-crop"
width="100%"
maxWidth={cropModalMaxWidth}
height="100%"
maxHeight={cropModalMaxHeight}
onClosed={onCloseModal}
body={
<div className="crop-editor">
<ReactCrop
crop={crop}
onChange={(_, percentCrop) => setCrop(percentCrop)}
aspect={aspect}
onComplete={(c) => {
setCompletedCrop(c);
}}
>
<img ref={imgRef} src={imgSrc} onLoad={onImageLoad} />
</ReactCrop>
</div>
}
footer={
<div className="wrp-btn">
<InputButton
text="Confirm"
design="solid"
className={"btn-confirm"}
onClick={onClickConfirmCrop}
/>
<InputButton
design="outline"
text="Cancel"
onClick={onCloseModal}
/>
</div>
}
/>
)}
</Styled>
);
}
const Styled = styled.div`
.wrp-input-image {
position: relative;
display: inline-flex;
width: ${({ $width }) => $width};
height: ${({ $height }) => $height};
border-radius: ${({ $borderRadius }) => $borderRadius};
justify-content: center;
align-items: center;
padding: 20px;
overflow: hidden;
background-color: var(--background);
.wrp-cc-remove {
position: absolute;
bottom: 0;
right: 0;
display: flex;
align-items: center;
background-color: var(--background);
border-radius: 8px;
.btn-caption {
padding: 10px;
}
.btn-remove {
padding: 10px;
}
}
.wrp-caption {
position: absolute;
padding: 10px;
background-color: var(--background);
inset: 0;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
.btn-save {
padding: 10px 20px;
margin-top: 20px;
}
.input-caption {
display: none;
}
.input-caption.active {
display: flex;
input {
flex: 1;
width: inherit;
}
}
}
.has_image {
display: flex;
position: absolute;
background-color: var(--background);
padding: 5px;
align-items: center;
justify-content: center;
bottom: 0;
}
.inner_change_wrp {
position: relative;
display: inline-block;
}
.wrp_upload_image {
position: relative;
width: 200px;
margin: 0 auto;
}
.wrp_bg_image {
position: relative;
}
.wrp_img {
position: absolute;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
padding: 10px;
width: 100%;
height: 100%;
img {
display: block;
max-width: 100%;
object-fit: contain;
max-height: 100%;
}
}
.img_file_placeholder {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.btn_choose_file {
padding: 10px 20px;
margin-top: 10px;
font-weight: bold;
span {
font-size: 15px !important;
}
}
.or {
margin: 0 0 20px 0;
}
}
.crop-editor,
.ReactCrop,
.ReactCrop__child-wrapper,
.ReactCrop__child-wrapper > img {
width: 100%;
position: relative;
}
.crop-editor {
display: flex;
align-items: center;
justify-content: center;
}
.ReactCrop__child-wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.content {
display: flex;
}
.btn-confirm {
margin-right: 40px;
}
`;
import InputImage from "../../example/InputImage";
export default function TestView() {
return (
<div className="test-view">
<InputImage
width="250px"
height="200px"
isCrop
onChange={(e) => {
console.log("e " + JSON.stringify(e));
}}
/>
</div>
);
}
Property | Description | Type | Default |
---|---|---|---|
initValue | ค่าเริ่มต้นของ view | string | |
onChange | ฟังก์ชันจะทำงานเมื่อทำการเปลี่ยนค่า | function | |
placeHolder | ข้อความตัวอย่าง | string | "Choose image file" |
maxSize | ขนาดกว้างที่สุดของรูป | number | 1500 |
width | ความกว้างภายในเนื้อหาของ view | string | "200px" |
height | ความสูงภายในเนื้อหาของ view | string | "200px" |
className | กำหนดลักษณะเฉพาะของ view | string | |
borderRadius | ความโค้งมนของ view | string | |
aspect | สัดส่วนของรูป | number | |
cropModalMaxWidth | ความกว้างของ Crop Modal View | string | "600px" |
cropModalMaxHeight | ความสูงของ Crop Modal View | string | "500px" |
isCrop | กำหนดค่า เป็น true เพื่อใช้งานโหมด Crop Image | boolean | |
isCaption | กำหนดค่า เป็น true เพื่อกรอก Caption Image | boolean | |
style | inline style ของ view | CssProperties |