Extract canvas blur to separate component
This commit is contained in:
parent
93a1571128
commit
2fc2fdf5ba
76
src/components/CanvasBlurCapture.tsx
Normal file
76
src/components/CanvasBlurCapture.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export default function CanvasBlurCapture({
|
||||||
|
imageUrl,
|
||||||
|
onCapture,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
hidden = true,
|
||||||
|
edgeCompensation = 10,
|
||||||
|
scale = 0.5,
|
||||||
|
quality = 0.9,
|
||||||
|
}: {
|
||||||
|
imageUrl: string
|
||||||
|
onCapture: (blurData: string) => void
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
hidden?: boolean
|
||||||
|
edgeCompensation?: number
|
||||||
|
scale?: number
|
||||||
|
quality?: number
|
||||||
|
}) {
|
||||||
|
const ref = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeout: NodeJS.Timeout;
|
||||||
|
|
||||||
|
const image = new Image();
|
||||||
|
image.crossOrigin = 'anonymous';
|
||||||
|
image.src = imageUrl;
|
||||||
|
image.onload = () => {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
const canvas = ref.current;
|
||||||
|
if (canvas) {
|
||||||
|
canvas.width = width * scale;
|
||||||
|
canvas.height = height * scale;
|
||||||
|
canvas.style.width = `${width}px`;
|
||||||
|
canvas.style.height = `${height}px`;
|
||||||
|
const context = ref.current?.getContext('2d');
|
||||||
|
if (context) {
|
||||||
|
context.scale(scale, scale);
|
||||||
|
context.filter =
|
||||||
|
'contrast(1.2) saturate(1.2)' +
|
||||||
|
`blur(${scale * 10}px)`;
|
||||||
|
context.drawImage(
|
||||||
|
image,
|
||||||
|
-edgeCompensation,
|
||||||
|
-edgeCompensation,
|
||||||
|
width + edgeCompensation * 2,
|
||||||
|
width * image.height / image.width
|
||||||
|
+ edgeCompensation * 2,
|
||||||
|
);
|
||||||
|
onCapture(canvas.toDataURL('image/jpeg', quality));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Cannot generate blur data: canvas not found');
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout);
|
||||||
|
}, [
|
||||||
|
imageUrl,
|
||||||
|
onCapture,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
edgeCompensation,
|
||||||
|
scale,
|
||||||
|
quality,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas ref={ref} className={hidden ? 'hidden' : undefined} />
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FORM_METADATA_ENTRIES,
|
FORM_METADATA_ENTRIES,
|
||||||
PhotoFormData,
|
PhotoFormData,
|
||||||
@ -11,12 +11,10 @@ import { createPhotoAction, updatePhotoAction } from './actions';
|
|||||||
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { cc } from '@/utility/css';
|
import { cc } from '@/utility/css';
|
||||||
|
import CanvasBlurCapture from '@/components/CanvasBlurCapture';
|
||||||
|
|
||||||
const THUMBNAIL_WIDTH = 300;
|
const THUMBNAIL_WIDTH = 300;
|
||||||
const THUMBNAIL_HEIGHT = 200;
|
const THUMBNAIL_HEIGHT = 200;
|
||||||
const EDGE_BLUR_COMPENSATION = 10;
|
|
||||||
const BLUR_SCALE = 0.5;
|
|
||||||
const BLUE_JPEG_QUALITY = 0.9;
|
|
||||||
|
|
||||||
export default function PhotoForm({
|
export default function PhotoForm({
|
||||||
initialPhotoForm,
|
initialPhotoForm,
|
||||||
@ -30,55 +28,16 @@ export default function PhotoForm({
|
|||||||
const [formData, setFormData] =
|
const [formData, setFormData] =
|
||||||
useState<Partial<PhotoFormData>>(initialPhotoForm);
|
useState<Partial<PhotoFormData>>(initialPhotoForm);
|
||||||
|
|
||||||
const [showBlur, setShowBlur] = useState(debugBlur);
|
|
||||||
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
|
|
||||||
const url = formData.url ?? '';
|
const url = formData.url ?? '';
|
||||||
|
|
||||||
useEffect(() => {
|
const updateBlurData = useCallback((blurData: string) => {
|
||||||
let timeout: NodeJS.Timeout;
|
if (type === 'create') {
|
||||||
|
setFormData(data => ({
|
||||||
const image = new Image();
|
...data,
|
||||||
image.crossOrigin = 'anonymous';
|
blurData,
|
||||||
image.src = url;
|
}));
|
||||||
image.onload = () => {
|
}
|
||||||
timeout = setTimeout(() => {
|
}, [type]);
|
||||||
const canvas = canvasRef.current;
|
|
||||||
if (canvas) {
|
|
||||||
canvas.width = THUMBNAIL_WIDTH * BLUR_SCALE;
|
|
||||||
canvas.height = THUMBNAIL_HEIGHT * BLUR_SCALE;
|
|
||||||
canvas.style.width = `${THUMBNAIL_WIDTH}px`;
|
|
||||||
canvas.style.height = `${THUMBNAIL_HEIGHT}px`;
|
|
||||||
const context = canvasRef.current?.getContext('2d');
|
|
||||||
if (context) {
|
|
||||||
context.scale(BLUR_SCALE, BLUR_SCALE);
|
|
||||||
context.filter =
|
|
||||||
'contrast(1.2) saturate(1.2)' +
|
|
||||||
`blur(${BLUR_SCALE * 10}px)`;
|
|
||||||
context.drawImage(
|
|
||||||
image,
|
|
||||||
-EDGE_BLUR_COMPENSATION,
|
|
||||||
-EDGE_BLUR_COMPENSATION,
|
|
||||||
THUMBNAIL_WIDTH + EDGE_BLUR_COMPENSATION * 2,
|
|
||||||
THUMBNAIL_WIDTH * image.height / image.width
|
|
||||||
+ EDGE_BLUR_COMPENSATION * 2,
|
|
||||||
);
|
|
||||||
if (type === 'create') {
|
|
||||||
setFormData(data => ({
|
|
||||||
...data,
|
|
||||||
blurData: canvas.toDataURL('image/jpeg', BLUE_JPEG_QUALITY),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('Cannot generate blur data: canvas not found');
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
}, [url, type]);
|
|
||||||
|
|
||||||
const isFormValid = FORM_METADATA_ENTRIES.every(([key, { required }]) =>
|
const isFormValid = FORM_METADATA_ENTRIES.every(([key, { required }]) =>
|
||||||
!required || Boolean(formData[key]));
|
!required || Boolean(formData[key]));
|
||||||
@ -96,12 +55,13 @@ export default function PhotoForm({
|
|||||||
height={THUMBNAIL_HEIGHT}
|
height={THUMBNAIL_HEIGHT}
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<canvas
|
<CanvasBlurCapture
|
||||||
ref={canvasRef}
|
imageUrl={url}
|
||||||
className="hidden"
|
width={THUMBNAIL_WIDTH}
|
||||||
onClick={() => setShowBlur(!showBlur)}
|
height={THUMBNAIL_HEIGHT}
|
||||||
|
onCapture={updateBlurData}
|
||||||
/>
|
/>
|
||||||
{showBlur && formData.blurData &&
|
{debugBlur && formData.blurData &&
|
||||||
<img
|
<img
|
||||||
alt="blur"
|
alt="blur"
|
||||||
src={formData.blurData}
|
src={formData.blurData}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user