<div id="root"></div>
body {
	margin: 0;
}
const { useState, useRef, useCallback, useEffect } = React;

const BG = "https://images.ctfassets.net/w6gireivdm0c/3Kvwx5lqr71qZeY970C8yi/0e29e05c6d4fe5008c22b4017ffdfb11/cat458A8400_TP_V4.jpg";
const MAX_CANVAS_SIZE = 600;
const RANGE_SLIDER_STEP = 20;

const loadImage = (url) => {
	return new Promise((resolve) => {
		const img = new Image();
		img.src = url;
		img.crossOrigin = "Anonymous";
		img.addEventListener("load", e => {
			resolve(e.target);
		});
	});
};
const clamp = (value, min, max) => {
	if (value < min) return min;
	else if (value > max) return max;
	return value;
};

const useDragAndDrop = () => {
	const [elementPosition, setElementPosition] = useState({ top: 0, left: 0 });
	const [elementOffset, setElementOffset] = useState({ x: 0, y: 0 });
	
	const pointerStartPosition = useRef({ x: null, y: null });
	const pointerMovePosition = useRef({ x: null, y: null });

	const currentDragElement = useRef(null);

	const prevElementOffset = useRef({ x: 0, y: 0 });
	
	const prevElementOffsetX = useRef(0);
	const prevElementOffsetY = useRef(0);

	const getCurrentPosition = (elem) => {
		const { top, left } = elem.getBoundingClientRect();
		return { top, left };
	};
	
	const moveDistance = (distance) =>
		setElementOffset({
			x: prevElementOffset.current.x + distance.x,
			y: prevElementOffset.current.y + distance.y
		});

	const resetElementOffset = () => {
		setElementOffset({
			x: 0,
			y: 0
		});

		prevElementOffset.current = {
			x: 0,
			y: 0
		};
	};

	const resetPointerStartPosition = () => {
		if (
			pointerStartPosition.current.x === null ||
			pointerStartPosition.current.y === null
		)
			return;

		pointerStartPosition.current.x = null;
		pointerStartPosition.current.y = null;
	};

	const handleMouseDown = (e) => {
		e.preventDefault();

		pointerStartPosition.current.x = e.clientX;
		pointerStartPosition.current.y = e.clientY;

		currentDragElement.current = e.target;

		const elementCurrentPosition = getCurrentPosition(currentDragElement.current);

		setElementPosition({
			top: elementCurrentPosition.top,
			left: elementCurrentPosition.left
		});
	};

	const handleMouseMove = (e) => {
		e.preventDefault();
		
		if (!currentDragElement.current) return;
console.log('suga move: ', currentDragElement.current)
		if (
			pointerStartPosition.current.x === null ||
			pointerStartPosition.current.y === null
		)
			return;

		pointerMovePosition.current.x = e.clientX;
		pointerMovePosition.current.y = e.clientY;

		const pointerMoveDistance = {
			x: pointerMovePosition.current.x - pointerStartPosition.current.x,
			y: pointerMovePosition.current.y - pointerStartPosition.current.y
		};

		moveDistance(pointerMoveDistance);
	};

	const handleMouseUp = (e) => {
		e.preventDefault();

		if (!currentDragElement.current) return;
		console.log('suga up: ', currentDragElement.current)
		resetPointerStartPosition();

		const elementCurrentPosition = getCurrentPosition(currentDragElement.current);

		setElementPosition({
			top: elementCurrentPosition.top,
			left: elementCurrentPosition.left
		});

		currentDragElement.current = null;
	};

	useEffect(() => {
		document.body.addEventListener("mousemove", handleMouseMove);
		document.body.addEventListener("mouseup", handleMouseUp);
		document.body.addEventListener("mouseleave", handleMouseUp);

		return () => {
			document.body.removeEventListener("mousemove", handleMouseMove);
			document.body.removeEventListener("mouseup", handleMouseUp);
			document.body.removeEventListener("mouseleave", handleMouseUp);
		};
	}, []);

	useEffect(() => {
		prevElementOffset.current = {
			x: elementOffset.x,
			y: elementOffset.y
		};
	}, [elementPosition.left, elementPosition.top]);

	return [
		{
			currentDragElement,
			elementPosition,
			elementOffset,
		},
	  	{
			pointerStartPosition,
			pointerMovePosition
		},
		handleMouseDown,
		setElementOffset,
		resetElementOffset,
		setElementPosition
	];
};
const useResizeObserver = (elements, callback) => {
	useEffect(() => {
		console.log('mari')
		const resizeObserver = new ResizeObserver((entries) => callback(entries));

		for (const elem of elements) {
			//console.log(elem);
			elem.current && resizeObserver.observe(elem.current);
		}

		return () => resizeObserver.disconnect();
	}, []);
};
const useMutationObserver = (elements, callback, config) => {
	useEffect(() => {
		const mutationObserver = new MutationObserver((mutations) => {
			mutationObserver.disconnect();
			callback(mutations);
			for (const elem of elements) {
				elem.current && mutationObserver.observe(elem.current, config);
			}
		});

		for (const elem of elements) {
			elem.current && mutationObserver.observe(elem.current, config);
		}

		return () => mutationObserver.disconnect();
	}, []);
};
const useRangeSlider = () => {
	const [
		element,
		pointer,
		handleMouseDown,
		setElementOffset,
		resetElementOffset,
		setElementPosition
	] = useDragAndDrop();
	
	const { 
		currentDragElement,
		elementPosition,
		elementOffset
	} = element
	
	const { 
		pointerStartPosition,
		pointerMovePosition
	} = pointer
	
	const [rangeSliderHandleOffsetX, setRangeSliderHandleOffsetX] = useState(0);

	const [rangeSliderHandleWidth, setRangeSliderHandleWidth] = useState(0);
	const [rangeSliderBarWidth, setRangeSliderBarWidth] = useState(0);

	const [rangeSliderBarPosition, setRangeSliderBarPosition] = useState({
		left: 0,
		top: 0
	});
	const [rangeSliderHandlePosition, setRangeSliderHandlePosition] = useState(0);

	const rangeSliderBarScale = useRef(1)

	const rangeSliderHandleElement = useRef(null);
	const rangeSliderBarElement = useRef(null);

	const previousRangeSliderBarWidth = useRef(0)
	
	const isDetectionResizeRangeSliderBar = useRef(false)
	
	/*const handleUpdateRangeSliderBar = (mutations) => {
		console.log('observe mutation bar')
		if(isDetectionResizeRangeSliderBar.current) {
			isDetectionResizeRangeSliderBar.current = false
			//return
		}
	}*/
	
	const handleUpdateRangeSliderHandlePosition = (mutations) => {
		/*for (const mutation of mutations) {
			if (mutation.target === rangeSliderHandleElement.current) {
				const left = mutation.target.getBoundingClientRect().left;
				setRangeSliderHandlePosition(left);
			}
		}*/
		/*if(isDetectionResizeRangeSliderBar.current) {
			isDetectionResizeRangeSliderBar.current = false
			return
		}*/
		console.log('observe mutation handle')
		
		const left = mutations[0].target.getBoundingClientRect().left;
		setRangeSliderHandlePosition(left);
	};
	
	const handleResize = (entries) => {
		for (const entry of entries) {
			if (entry.target === rangeSliderBarElement.current) {
				const width = entry.contentRect.width;
				const height = entry.contentRect.height;
				
				// バーの幅が0であれば、ハンドルが右側に位置した状態でバーの幅が0になってから再び0以上になると、ハンドルの位置がp * 0 = 0となり先頭に戻ってしまう。バーの幅が0になった後でもハンドルの現在位置が維持されるように、バーの幅か高さが0になったら前回のオフセットの状態が維持されるように抜ける
				if(width === 0 || height === 0) return
				
				// useFrontBackRatioフックだとresizeObserverの検知の速さに対応できないため、ここでスケールを取得
				rangeSliderBarScale.current =
					previousRangeSliderBarWidth.current === 0
						? 1
						: width / previousRangeSliderBarWidth.current;
				
				setRangeSliderBarWidth(width);
				
				previousRangeSliderBarWidth.current = width
				
				//setRangeSliderHandleOffsetX((p) => p * rangeSliderBarScale.current)
				
				const rect = entry.target.getBoundingClientRect();
				setRangeSliderBarPosition({
					left: rect.left,
					top: rect.top
				});
			}

			if (entry.target === rangeSliderHandleElement.current) {
				const width = entry.contentRect.width;
				setRangeSliderHandleWidth(width);

				const left = entry.target.getBoundingClientRect().left;
				setRangeSliderHandlePosition(left);
			}
		}
	}
	
	/*useResizeObserver(
		[rangeSliderBarElement, rangeSliderHandleElement],
		handleResize
	);*/
	
	useResizeObserver(
		[rangeSliderBarElement],
		(entries) => {
			isDetectionResizeRangeSliderBar.current = true
			console.log('observe resize bar')
			const width = entries[0].contentRect.width;
			const height = entries[0].contentRect.height;

			// バーの幅が0であれば、ハンドルが右側に位置した状態でバーの幅が0になってから再び0以上になると、ハンドルの位置がp * 0 = 0となり先頭に戻ってしまう。バーの幅が0になった後でもハンドルの現在位置が維持されるように、バーの幅か高さが0になったら前回のオフセットの状態が維持されるように抜ける
			if(width === 0 || height === 0) return

			// useFrontBackRatioフックだとresizeObserverの検知の速さに対応できないため、ここでスケールを取得
			rangeSliderBarScale.current =
				previousRangeSliderBarWidth.current === 0
				? 1
			: width / previousRangeSliderBarWidth.current;

			setRangeSliderBarWidth(width);

			previousRangeSliderBarWidth.current = width

			setRangeSliderHandleOffsetX((p) => p * rangeSliderBarScale.current)

			const rect = entries[0].target.getBoundingClientRect();
			setRangeSliderBarPosition({
				left: rect.left,
				top: rect.top
			});
		}
	)
	
	useResizeObserver(
		[rangeSliderHandleElement],
		(entries) => {
			const width = entries[0].contentRect.width;
			setRangeSliderHandleWidth(width);

			const left = entries[0].target.getBoundingClientRect().left;
			setRangeSliderHandlePosition(left);
		}
	);
	
	/*useMutationObserver(
		[rangeSliderBarElement],
		handleUpdateRangeSliderBar,
		{ attributes: true }
	)*/
	
	useMutationObserver(
		[rangeSliderHandleElement],
		handleUpdateRangeSliderHandlePosition,
		{ attributes: true, subtree: false, childList: false, attributeFilter: ["class"] }
	);

	useEffect(() => {
		if (!currentDragElement.current) return;

		// rangeSliderBarPosition.leftだと、バーの位置が途中から変化しなくなったときに、そこから先の変更値が取得できなくなるため、押し込まれたときのバーの位置を取得する
		const rect = rangeSliderBarElement.current.getBoundingClientRect()
			setRangeSliderBarPosition({
				left: rect.left,
				top: rect.top
			});
		const startX = pointerStartPosition.current.x - rect.left//rangeSliderBarPosition.left;

		setRangeSliderHandleOffsetX(clamp(startX, 0, rangeSliderBarWidth));
	}, [elementPosition]);

	useEffect(() => {
		const rect = rangeSliderBarElement.current.getBoundingClientRect()
		setRangeSliderBarPosition({
				left: rect.left,
				top: rect.top
		});
		const moveX = pointerMovePosition.current.x - rect.left//rangeSliderBarPosition.left;

		setRangeSliderHandleOffsetX((p) => clamp(moveX, 0, rangeSliderBarWidth));
	}, [elementOffset.x, elementOffset.y]);

	return [
		{
			handle: {
				element: rangeSliderHandleElement,
				offsetX: rangeSliderHandleOffsetX,
				position: rangeSliderHandlePosition,
				width: rangeSliderHandleWidth
			},
			bar: {
				element: rangeSliderBarElement,
				position: rangeSliderBarPosition,
				width: rangeSliderBarWidth,
				scale: rangeSliderBarScale
			},
			handleMouseDown
		},
		setRangeSliderHandleOffsetX,
		isDetectionResizeRangeSliderBar
	];
};
const useIconImageGenerator = (imageUrl) => {
	const [
		{ 
			currentDragElement,
			elementPosition,
			elementOffset
		},
		{ 
			pointerStartPosition,
			pointerMovePosition
		},
		handleMouseDown,
		setElementOffset,
		resetElementOffset,
		setElementPosition
	] = useDragAndDrop();
	const [
		rangeSlider,
		setRangeSliderHandleOffsetX,
		isDetectionResizeRangeSliderBar
	] = useRangeSlider();
	
	const { handle, bar } = rangeSlider
	const { 
		element: rangeSliderHandleElement,
		offsetX:rangeSliderHandleOffsetX,
		position: rangeSliderHandlePosition,
		width: rangeSliderHandleWidth
	} = handle
	const {
		element: rangeSliderBarElement,
		position: rangeSliderBarPosition,
		width: rangeSliderBarWidth,
		scale: rangeSliderBarScale
	} = bar
	
	const canvasElement = useRef(null);
	const draggableImageElement = useRef(null);
	const imageElement = useRef(null);
	const dragAreaElement = useRef(null);
	const frameElement = useRef(null);

	const initialImageSize = useRef({ height: 0, width: 0 })

	const draggableImagePosition = useRef({ top: 0, left: 0, right: 0, bottom: 0 })
	const [draggableImageSize, setDraggableImageSize] = useState({ height: 0, width: 0 })
	
	const framePosition = useRef({ left: 0, top: 0, right: 0, bottom: 0 })
	const [frameSize, setFrameSize] = useState({ height: 0, width: 0 })
	const frameScale = useRef(1);
	
	const [generatedImage, setGeneratedImage] = useState(null);
	
	const isFirstResize = useRef(false)
	const isDetectionResizeFrame = useRef(false)
	
	const fitImage = async (h, w) => {
        const image = await loadImage(imageUrl).then((r) => r);

        const width = image.width;
        const height = image.height;

        const ratio = height / width;

        if (ratio >= 1) {
            setDraggableImageSize({
                height: height * (w / width),
                width: w
            })

            initialImageSize.current.height = height * (w / width)
            initialImageSize.current.width = w
        } else {//if (ratio < 1) {
            setDraggableImageSize({
                height: h,
                width: width * (h / height)
            })

            initialImageSize.current.height = h
            initialImageSize.current.width = width * (h / height)
        } 
		/*else {
            setDraggableImageSize({
                height: h,
                width: w
            })

            initialImageSize.current.height = h
            initialImageSize.current.width = w
        }*/
    }
	
	const pushBackDraggableImage = () => {
		if (draggableImagePosition.current.left >= framePosition.current.left) {
			setElementOffset((p) => ({
				x: p.x - Math.abs(draggableImagePosition.current.left -  framePosition.current.left),
				y: p.y
			}));
		}

		if (draggableImagePosition.current.right <=  framePosition.current.right) {
			setElementOffset((p) => ({
				x: p.x + Math.abs(draggableImagePosition.current.right -  framePosition.current.right),
				y: p.y
			}));
		}

		if (draggableImagePosition.current.top >=  framePosition.current.top) {
			setElementOffset((p) => ({
				x: p.x,
				y: p.y - Math.abs(draggableImagePosition.current.top -  framePosition.current.top)
			}));
		}

		if (draggableImagePosition.current.bottom <=  framePosition.current.bottom) {
			setElementOffset((p) => ({
				x: p.x,
				y: p.y + Math.abs(draggableImagePosition.current.bottom -  framePosition.current.bottom)
			}));
		}
	}

	const updateDraggableImageScale = () => {
		const rangeSliderStatus = clamp(
			rangeSliderHandleOffsetX / rangeSliderBarWidth,
			0,
			rangeSliderBarWidth
		);
		
		const imageScale = 1 + rangeSliderStatus;

		setDraggableImageSize({
			height: initialImageSize.current.height * imageScale,
			width: initialImageSize.current.width * imageScale
		})
	};

	const handleMouseWheel = useCallback((e) => {
		if (!rangeSliderBarWidth) return;

		const deltaY = e.deltaY;
		
		if (e.deltaY > 0) {
			setRangeSliderHandleOffsetX((p) => 
				clamp(p - RANGE_SLIDER_STEP * rangeSliderBarScale.current, 0, rangeSliderBarWidth)
			);
		} else {
			setRangeSliderHandleOffsetX((p) => 
				clamp(p + RANGE_SLIDER_STEP * rangeSliderBarScale.current, 0, rangeSliderBarWidth)
			);
		}
	});

	const getGeneratedImage = async () => {
		if (!canvasElement.current) return;
		const windowHeight = window.innerHeight
		const windowWidth = window.innerWidth
		
		const windowRatio = windowHeight / windowWidth

		const canvasSize = (windowRatio >= 1) ? clamp(windowWidth, 0, MAX_CANVAS_SIZE) : clamp(windowHeight, 0, MAX_CANVAS_SIZE)
		canvasElement.current.width = canvasSize;
		canvasElement.current.height = canvasSize;

		const ctx = canvasElement.current.getContext("2d");

		const canvasRect = canvasElement.current.getBoundingClientRect();

		const image = await loadImage(imageElement.current.src).then((r) => r);

		const frameRect = frameElement.current.getBoundingClientRect()
		
		const imageRect = draggableImageElement.current.getBoundingClientRect()
		
		const ratio =
			initialImageSize.current.height >= initialImageSize.current.width//draggableImageSize.height > draggableImageSize.width
				? canvasRect.width / frameRect.width
				: canvasRect.height / frameRect.height;

		ctx.drawImage(
			image,
			(imageRect.left - frameRect.left) * ratio,
			(imageRect.top - frameRect.top) * ratio,
			imageRect.width * ratio,
			imageRect.height * ratio
		);

		if (canvasElement.current) {
			const generatedImage = canvasElement.current.toDataURL("image/jpeg");
			console.log('generatedImage: ', generatedImage)
			//const img = new Image()
			//img.src = generatedImage
			//document.body.appendChild(img)
			setGeneratedImage(generatedImage);
		}
	};

	const resetIconImageGenerator = () => {
		setElementOffset({
			x: 0,
			y: 0
		});

		setRangeSliderHandleOffsetX(0);

		// bg選択画面に戻ったあとに再度この画面が表示されたときにimageWidth(imageHeight)がトリガーとなってgetGeneratedImageが実行されるようにするため
		
		setDraggableImageSize({
			height: 0,
			width: 0
		})
	};

	useMutationObserver(
		[draggableImageElement],
		(mutations) => {
			console.log('naze1: ', isDetectionResizeFrame.current, isDetectionResizeRangeSliderBar.current)
			// 枠のリサイズ時は画像がずれるのを防ぐため、pushBackDraggableImage関数を実行しない
			// 画像がドラッグで移動したときと、レンジスライダーの操作によって画像が拡縮したときに実行する
			const frameRect = frameElement.current.getBoundingClientRect()
			
			const rect = mutations[0].target.getBoundingClientRect();
			draggableImagePosition.current = {
				left: rect.left,
				right: rect.right,
				top: rect.top,
				bottom: rect.bottom
			}
			
			// リサイズされなくなったあとにドラッグしたときに画像がずれるのを防ぐために、ここでも枠の位置を更新する
			framePosition.current = {
				left: frameRect.left,
				right: frameRect.right,
				top: frameRect.top,
				bottom: frameRect.bottom
			}
			
	 		if(isDetectionResizeRangeSliderBar.current) {
				isDetectionResizeRangeSliderBar.current = false
		 		isDetectionResizeFrame.current = false // trueのままとなり、次の判定がtrueになってしまう
				return
			}
			
			//いくらリサイズされても↑でreturnし続けるので、先に進まず、isDetectionResizeFrame.currentはtrueのまま!!!その状態で、バーをクリックすると、isDetectionResizeFrame.currentがtrueなので↓でreturnとなり、クリック時はpushBackDraggableImage関数は実行されない。続けてドラッグするとそのときはfalseだから実行されるようになる
			
			if(isDetectionResizeFrame.current) {
				isDetectionResizeFrame.current = false
				isDetectionResizeRangeSliderBar.current = false
				return
			}
			
			console.log('mura: ', isDetectionResizeRangeSliderBar.current, isDetectionResizeFrame.current)

			console.log('observe mutation image')

			pushBackDraggableImage()
			
			getGeneratedImage()
		},
		{
			attributes: true,
			subtree: false,
			childList: false,
			attributeFilter: ["class"]
		}
	)
	
	useResizeObserver(
		[frameElement],
		(entries) => {
			isDetectionResizeFrame.current = true

			console.log('observe resize frame')
	
			const rect = entries[0].target.getBoundingClientRect();
			const resizedFrameHeight = rect.height;
			const resizedFrameWidth = rect.width;
			
			// ブラウザウィンドウのサイズが0pxになったあとに再び0より大きくなったときに画像のサイズが0pxになってしまうのを防ぐために、ブラウザウィンドウのサイズが0pxのときは処理を抜ける
			if(resizedFrameHeight === 0 || resizedFrameWidth === 0) {
				return
			}
			
			framePosition.current = {
				left: rect.left,
				right: rect.right,
				top: rect.top,
				bottom: rect.bottom
			}

			// コンポーネントのレンダリング後に一度だけ呼び出す
			if(!isFirstResize.current) {
				isFirstResize.current = true
				fitImage(resizedFrameHeight, resizedFrameWidth)
			}
			
			console.log('aib1: ', initialImageSize.current.height, resizedFrameHeight)
			
			const ratio = initialImageSize.current.height / initialImageSize.current.width;
			
			console.log('aib0: ', ratio)
			
			if (ratio >= 1) {
				frameScale.current =
					initialImageSize.current.width === 0//previousFrameSize.current.width === 0
						? 1
						: resizedFrameWidth / initialImageSize.current.width//resizedFrameWidth / previousFrameSize.current.width;
			} else {
				frameScale.current =
					initialImageSize.current.height === 0//previousFrameSize.current.height === 0
						? 1
						: resizedFrameHeight / initialImageSize.current.height //resizedFrameHeight / previousFrameSize.current.height;
			}

			initialImageSize.current.height *= frameScale.current
			initialImageSize.current.width *= frameScale.current
			
console.log('aib2: ', initialImageSize.current.height, resizedFrameHeight)
			
			console.log('aib3: ', frameScale.current)
			
			setFrameSize({
				height: resizedFrameHeight,
				width: resizedFrameWidth
			})

			setElementOffset((p) => ({
				x: p.x * frameScale.current,
				y: p.y * frameScale.current
			}));
			
			getGeneratedImage()
		}
	)
	
	useResizeObserver(
		[draggableImageElement],
		(entries) => {			
			console.log('observe resize draggable')
			/*const rect = entries[0].target.getBoundingClientRect();
	
			draggableImagePosition.current = {
				left: rect.left,
				right: rect.right,
				top: rect.top,
				bottom: rect.bottom
			}*/

			/*setElementPosition({
				left: draggableImagePosition.current.left,
				top: draggableImagePosition.current.top
			})*/
			
			//getGeneratedImage()
		}
	)
	
/*	useEffect(() => getGeneratedImage(), [
		elementPosition.left,
		elementPosition.top,
		rangeSliderHandlePosition.left,
		rangeSliderHandlePosition.top,
		draggableImageSize.height,
		draggableImageSize.width,
		frameSize.height,
		frameSize.width
	]);*/
	
	useEffect(() => {
		//ページ表示時、imageWidth(imageHeight)がNaNにならないように
		//if (!draggableImageSize.width) return;
		//if (!draggableImageSize.height) return;

		updateDraggableImageScale();
	}, [rangeSliderHandleOffsetX, frameSize.height, frameSize.width]);

	return [
		{
			dragAreaElement,
			draggableImage: {
				element: draggableImageElement,
				size: draggableImageSize,
				offset: elementOffset
			},
			imageElement,
			frameElement,
			canvasElement,
			rangeSlider,
			handleMouseDown,
			handleMouseWheel
		},
		generatedImage,
		resetIconImageGenerator
	];
};

const StyledRangeSlider = styled.div`
	display: flex;
	align-items: center;
	height: 6vmin;
	position: relative;
	width: 100%;
`;

const Bar = styled.div`
	background-color: #f0f0f0;
	border-radius: 2vmin;
	cursor: pointer;
	height: 2vmin;
	position: absolute;
	width: 100%;
`;

const Handle = styled.div`
	background-color: #ffa44a;
	border-radius: 100%;
	cursor: pointer;
	height: 6vmin;
	position: absolute;
	left: -3vmin;
	transform: translate3d(
		${({ offsetX }) => offsetX}px,
		0,
		0
	);
	width: 6vmin;
`;

const RangeSlider = ({ rangeSlider }) => (
	<StyledRangeSlider className="range-slider">
		<Bar
			className="range-slider-bar"
			ref={rangeSlider.bar.element}
			onMouseDown={rangeSlider.handleMouseDown}
		/>
		<Handle
			className="range-slider-handle"
			ref={rangeSlider.handle.element}
			onMouseDown={rangeSlider.handleMouseDown}
			offsetX={rangeSlider.handle.offsetX}
		/>
	</StyledRangeSlider>
);

const StyledIconImageGenerator = styled.div`
	margin: 3vmin auto 0;
	width: 50vmin;
`;

const DragArea = styled.div`
	border-radius: 2vmin;
	box-sizing: border-box;
	cursor: move;
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: center;
	margin-bottom: 2vmin;
	overflow: hidden;
	padding: 2vmin;
	position: relative;
	height: 50vmin;
	width: 100%;
`;

const DraggableImage = styled.div`
	height: ${({ size }) => size.height}px;
	position: absolute;
	transform: translate3d(
		${({ offsetX }) => offsetX}px,
		${({ offsetY }) => offsetY}px,
		0
	);
	width: ${({ size }) => size.width}px;
	z-index: 0;
`;

const Frame = styled.div`
	box-sizing: border-box;
	box-shadow: rgba(230, 236, 240, 0.7) 0px 0px 0px 4vmin;
	border: 0.6vmin solid #ff4a59;
	height: 100%;
	position: relative;
	pointer-events: none;// これがないとドラッグしたときに画像が正しく移動しない。なぜなのかは現在検証中
	width: 100%;
	z-index: 1;
`;

const Img = styled.img`
	bottom: 0;
	height: 100%;
	left: 0;
	position: absolute;
	right: 0;
	top: 0;
	width: 100%;
	z-index: -1;
`;

const Canvas = styled.canvas`
	background-color: red;
	position: absolute;
	z-index: -1000;
`;

const Flex = styled.div`
	display: flex;
	align-items: center;
	justify-content: space-between;
`

const Margin = styled.div`
	margin-top : ${({ top }) => top ? top : `0`};
	margin-right : ${({ right }) => right ? right : `0`};
	margin-bottom : ${({ bottom }) => bottom ? bottom : `0`};
	margin-left : ${({ left }) => left ? left : `0`};
	width: 100%;
`

const StyledIcon = styled.span`
	color: ${({ color }) => color};
	font-size: ${({ size }) => size};
`

const Icon = ({ name, color, size }) => (
	<StyledIcon className="icon" color={color} size={size}>
		<i class={`fas fa-${name}`}></i>
	</StyledIcon>
)

const IconImageGenerator = ({
	className,
	imageUrl,
	iconImageGenerator
}) => (
	<StyledIconImageGenerator>
		<DragArea
			ref={iconImageGenerator.dragAreaElement}
			onMouseDown={iconImageGenerator.handleMouseDown}
			onWheel={iconImageGenerator.handleMouseWheel}
		>
			<DraggableImage
				ref={iconImageGenerator.draggableImage.element}
				size={iconImageGenerator.draggableImage.size}
				offsetX={iconImageGenerator.draggableImage.offset.x}
				offsetY={iconImageGenerator.draggableImage.offset.y}
			>
				<Img
					src={imageUrl}
					ref={iconImageGenerator.imageElement}
					draggable="false"
				/>
			</DraggableImage>
			<Frame ref={iconImageGenerator.frameElement} />
			<Canvas ref={iconImageGenerator.canvasElement} draggable="false" />
		</DragArea>
		<Flex>
			<Icon name="search-minus" color="white" size="4vmin" />
			<Margin left="4vmin" right="4vmin">
				<RangeSlider rangeSlider={iconImageGenerator.rangeSlider} />
			</Margin>
			<Icon name="search-plus" color="white" size="4vmin" />
		</Flex>
	</StyledIconImageGenerator>
)

const StyledApp = styled.div`
	background-color: #55ab70;
	display: flex;
	align-items: center;
	justify-content: center;
	height: 100vh;
	width: 100%;
`;

const App = () => {
	const [
		iconImageGenerator,
		generatedImage,
		resetIconImageGenerator
	] = useIconImageGenerator(BG);

	return (
		<StyledApp>
			<IconImageGenerator
				imageUrl={BG}
				iconImageGenerator={iconImageGenerator}
			/>
		</StyledApp>
	);
};

ReactDOM.render(<App />, document.getElementById("root"));

/*
const getGeneratedImage = async () => {
		if (!canvasElement.current) return;
		const windowHeight = window.innerHeight
		const windowWidth = window.innerWidth
		
		const windowRatio = windowHeight / windowWidth

		const canvasSize = (windowRatio >= 1) ? clamp(windowWidth, 0, MAX_CANVAS_SIZE) : clamp(windowHeight, 0, MAX_CANVAS_SIZE)
		canvasElement.current.width = canvasSize;
		canvasElement.current.height = canvasSize;

		const ctx = canvasElement.current.getContext("2d");

		const canvasRect = canvasElement.current.getBoundingClientRect();

		const image = await loadImage(imageElement.current.src).then((r) => r);

		const ratio =
			initialImageSize.current.height >= initialImageSize.current.width//draggableImageSize.height > draggableImageSize.width
				? canvasRect.width / frameSize.width
				: canvasRect.height / frameSize.height;

		ctx.drawImage(
			image,
			(elementPosition.left - framePosition.current.left) * ratio,
			(elementPosition.top - framePosition.current.top) * ratio,
			draggableImageSize.width * ratio,
			draggableImageSize.height * ratio
		);

		if (canvasElement.current) {
			const generatedImage = canvasElement.current.toDataURL("image/jpeg");
			console.log('generatedImage: ', generatedImage)
			//const img = new Image()
			//img.src = generatedImage
			//document.body.appendChild(img)
			setGeneratedImage(generatedImage);
		}
	};
*/
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/styled-components/4.3.1/styled-components.min.js
  4. https://use.fontawesome.com/releases/v5.1.0/js/all.js